import { createContext, useCallback, useContext, useMemo, useState } from 'react'

import { useDialogue } from '@/components/dialogues/DialogueProvider'

import type { FC, PropsWithChildren } from 'react'

type UnsavedChangesSource = {
    dirty:    boolean
    discard:  () => void | Promise<void> }

type UnsavedChangesGuardContextValue = {
    hasUnsavedChanges:             boolean
    registerUnsavedChangesSource:  (
	source: UnsavedChangesSource | null,
    ) => void
    confirmDiscardNavigation:      () => Promise<boolean> }

const UnsavedChangesGuardContext =
    createContext<UnsavedChangesGuardContextValue | null> (null)


export const UnsavedChangesGuardProvider: FC<PropsWithChildren> = ({ children }) => {
  const dialogue = useDialogue ()
  const [source, setSource] = useState<UnsavedChangesSource | null> (null)

  const registerUnsavedChangesSource = useCallback ((
      nextSource: UnsavedChangesSource | null,
  ) => {
    setSource (nextSource)
  }, [])

  const confirmDiscardNavigation = useCallback (async (): Promise<boolean> => {
    if (!(source?.dirty))
      return true

    const confirmed = await dialogue.confirm ({
	title: '未保存の変更があります',
	description: 'このまま移動すると、保存していない変更は失われます。',
	cancelText: 'このページに残る',
	confirmText: '変更を破棄して移動',
	variant: 'danger' })
    if (!(confirmed))
      return false

    await source.discard ()
    return true
  }, [dialogue, source])

  const value = useMemo<UnsavedChangesGuardContextValue> (() => ({
      hasUnsavedChanges: source?.dirty === true,
      registerUnsavedChangesSource,
      confirmDiscardNavigation,
  }), [confirmDiscardNavigation, registerUnsavedChangesSource, source?.dirty])

  return (
      <UnsavedChangesGuardContext.Provider value={value}>
	{children}
      </UnsavedChangesGuardContext.Provider>)
}


export const useUnsavedChangesGuard = (): UnsavedChangesGuardContextValue => {
  const context = useContext (UnsavedChangesGuardContext)

  if (context == null)
    throw new Error ('UnsavedChangesGuardProvider が必要です．')

  return context
}
