import { useCallback, useMemo, useRef, useState } from 'react'

import { Button } from '@/components/ui/button'
import { Dialog,
	 DialogContent,
	 DialogDescription,
	 DialogFooter,
	 DialogHeader,
	 DialogTitle } from '@/components/ui/dialog'
import { DialogueContext } from '@/lib/dialogues/useDialogue'

import type { FC, ReactNode } from 'react'
import type { AlertOptions,
	      ChoiceOptions,
	      ConfirmOptions,
	      DialogueAPI,
	      DialogueFormAction,
	      DialogueFormControls,
	      DialogueFormOptions } from '@/lib/dialogues/useDialogue'

type DialogueRequest =
    | { id:       number
	kind:     'confirm'
	options:  ConfirmOptions
	resolve:  (value: boolean) => void }
    | { id:       number
	kind:     'alert'
	options:  AlertOptions
	resolve:  () => void }
    | { id:       number
	kind:     'choice'
	options:  ChoiceOptions<string>
	resolve:  (value: string | null) => void }
    | { id:       number
	kind:     'form'
	options:  DialogueFormOptions
	resolve:  () => void }

let nextDialogueId = 1

type Props = { children: ReactNode }


const DialogueProvider: FC<Props> = ({ children }) => {
  const [queue, setQueue] = useState<DialogueRequest[]> ([])
  const [pendingIds, setPendingIds] = useState<number[]> ([])
  const [formActions, setFormActions] = useState<Record<number, DialogueFormAction[]>> ({ })
  const formControls = useRef<Record<number, DialogueFormControls>> ({ })
  const [nestedConfirm, setNestedConfirm] = useState<{
      parentId: number
      options:  ConfirmOptions
      resolve:  (value: boolean) => void } | null> (null)

  const push = useCallback ((request: Omit<DialogueRequest, 'id'>) => {
    const id = nextDialogueId
    ++nextDialogueId

    setQueue (current => [...current, { ...request, id } as DialogueRequest])
  }, [])

  const closeRequest = useCallback ((id: number, result?: unknown) => {
    setQueue (current => {
      const active = current.find (request => request.id === id)

      if (active == null)
	return current

      switch (active.kind)
      {
      case 'confirm':
	active.resolve (Boolean (result))
	break

      case 'alert':
	active.resolve ()
	break

      case 'choice':
	active.resolve ((result ?? null) as string | null)
	break

      case 'form':
	active.resolve ()
	break
      }

      return current.filter (request => request.id !== id)
    })
    setPendingIds (current => current.filter (_1 => _1 !== id))
    setFormActions (current => {
	const { [id]: _, ...rest } = current
	return rest
    })
    delete formControls.current[id]
  }, [])

  const setRequestActions = useCallback (
      (id: number, actions: DialogueFormAction[]) => {
	setFormActions (current => ({ ...current, [id]: actions }))
      },
      [])

  const openNestedConfirm = useCallback (
      (parentId: number, options: ConfirmOptions) =>
	  new Promise<boolean> (resolve => {
	    setNestedConfirm ({ parentId, options, resolve })
	  }),
      [])

  const closeNestedConfirm = useCallback ((result: boolean) => {
    setNestedConfirm (current => {
      if (current == null)
	return current

      current.resolve (result)
      return null
    })
  }, [])

  const api = useMemo<DialogueAPI> (() => ({
      confirm: options => new Promise<boolean> (resolve => {
	push ({ kind: 'confirm', options, resolve })
      }),
      alert: options => new Promise<void> (resolve => {
	push ({ kind: 'alert', options, resolve })
      }),
      choice: options => new Promise (resolve => {
	push ({ kind: 'choice',
		options: options as ChoiceOptions<string>,
		resolve: resolve as (value: string | null) => void })
      }),
      form: options => new Promise<void> (resolve => {
	push ({ kind: 'form', options, resolve })
      }) }), [push])

  const handleFormAction = useCallback (
      async (id: number, action: DialogueFormAction) => {
	if (pendingIds.includes (id))
	  return

	setPendingIds (current => [...current, id])
	try
	{
	  const shouldClose = await action.onSelect ()
	  if (shouldClose !== false)
	    closeRequest (id)
	}
	finally
	{
	  setPendingIds (current => current.filter (_1 => _1 !== id))
	}
      },
      [closeRequest, pendingIds])

  const active = queue[0]
  const startActions =
      active?.kind === 'form'
      ? (formActions[active.id] ?? []).filter (action => action.placement === 'start')
      : []
  const endActions =
      active?.kind === 'form'
      ? (formActions[active.id] ?? []).filter (action =>
	  action.placement == null || action.placement === 'end')
      : []

  return (
      <DialogueContext.Provider value={api}>
	{children}

	{active && (
	    <Dialog
		open
		onOpenChange={open => {
		  const blocked =
		      nestedConfirm?.parentId === active.id
		      || pendingIds.includes (active.id)
		  if (!(open) && !(blocked))
		    closeRequest (active.id, active.kind !== 'confirm' && null)
		}}>
	      <DialogContent
		  className={
		      active.kind === 'form'
		      ? `flex max-h-[calc(100dvh-1rem)] ${ active.options.size === 'large'
			  ? 'max-w-3xl'
			  : 'max-w-lg' } flex-col overflow-hidden gap-0`
		      : 'px-6 pb-6 pt-7'
		  }
		  onEscapeKeyDown={event => {
		    if (nestedConfirm?.parentId === active.id || pendingIds.includes (active.id))
		      event.preventDefault ()
		  }}
		  onPointerDownOutside={event => {
		    if (nestedConfirm?.parentId === active.id || pendingIds.includes (active.id))
		      event.preventDefault ()
		  }}>
		{active.kind === 'form'
		 ? (
		     <>
		       <DialogHeader className="shrink-0 pb-4 pl-8 pr-0 pt-1 text-left">
			 <DialogTitle>{active.options.title}</DialogTitle>

			 {active.options.description && (
			     <DialogDescription asChild>
			       <div>{active.options.description}</div>
			     </DialogDescription>)}
		       </DialogHeader>

		       <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain">
			 {active.options.body (
			     formControls.current[active.id] ??= {
				 close: () => closeRequest (active.id),
				 setActions: actions => setRequestActions (active.id, actions),
				 confirm: options => openNestedConfirm (active.id, options) })}
		       </div>

		       <DialogFooter
			   className="shrink-0 flex-col gap-2 pt-4
				      sm:flex-row sm:justify-between sm:gap-0 sm:space-x-0">
			 <div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
			   {startActions.map (action => (
			       <Button
				   key={action.label}
				   className="w-full sm:w-auto"
				   variant={action.variant === 'danger'
				       ? 'destructive'
				       : 'default'}
				   onClick={() => void handleFormAction (active.id, action)}
				   disabled={pendingIds.includes (active.id)
				       || nestedConfirm?.parentId === active.id
				       || action.disabled}>
				 {action.label}
			       </Button>))}
			 </div>

			 <div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
			   <Button
			       className="w-full sm:w-auto"
			       variant="outline"
			       onClick={() => closeRequest (active.id)}
			       disabled={pendingIds.includes (active.id)
				   || nestedConfirm?.parentId === active.id}>
			     {active.options.cancelText ?? '取消'}
			   </Button>

			   {endActions.map (action => (
			       <Button
				   key={action.label}
				   className="w-full sm:w-auto"
				   variant={action.variant === 'danger'
				       ? 'destructive'
				       : 'default'}
				   onClick={() => void handleFormAction (active.id, action)}
				   disabled={pendingIds.includes (active.id)
				       || nestedConfirm?.parentId === active.id
				       || action.disabled}>
				 {action.label}
			       </Button>))}
			 </div>
		       </DialogFooter>
		     </>)
		 : (
		     <>
		       <DialogHeader className="pl-8">
			 <DialogTitle>{active.options.title}</DialogTitle>

			 {active.options.description && (
			     <DialogDescription asChild>
			       <div>{active.options.description}</div>
			     </DialogDescription>)}
		       </DialogHeader>

		       <DialogFooter>
			 {active.kind === 'confirm' && (
			     <>
			       <Button
				   variant="outline"
				   onClick={() => closeRequest (active.id, false)}>
				 {active.options.cancelText ?? '取消'}
			       </Button>

			       <Button
				   variant={(active.options.variant === 'danger')
					    ? 'destructive'
					    : 'default'}
				   onClick={() => closeRequest (active.id, true)}>
				 {active.options.confirmText ?? '確定'}
			       </Button>
			     </>)}

			 {active.kind === 'alert' && (
			     <Button onClick={() => closeRequest (active.id)}>
			       {active.options.okText ?? '確定'}
			     </Button>)}

			 {active.kind === 'choice' && (
			     <>
			       <Button
				   variant="outline"
				   onClick={() => closeRequest (active.id, null)}>
				 {active.options.cancelText ?? '取消'}
			       </Button>

			       {active.options.choices.map (choice => (
				   <Button
				       key={choice.value}
				       variant={(choice.variant === 'danger')
						? 'destructive'
						: 'default'}
				       onClick={() => closeRequest (active.id, choice.value)}>
				     {choice.label}
				   </Button>))}
			     </>)}
		       </DialogFooter>
		     </>)}
	      </DialogContent>
	    </Dialog>)}

	{nestedConfirm && (
	    <Dialog
		open
		onOpenChange={open => {
		  if (!(open))
		    closeNestedConfirm (false)
		}}>
	      <DialogContent className="px-6 pb-6 pt-7">
		<DialogHeader className="pl-8">
		  <DialogTitle>{nestedConfirm.options.title}</DialogTitle>

		  {nestedConfirm.options.description && (
		      <DialogDescription asChild>
			<div>{nestedConfirm.options.description}</div>
		      </DialogDescription>)}
		</DialogHeader>

		<DialogFooter>
		  <Button
		      variant="outline"
		      onClick={() => closeNestedConfirm (false)}>
		    {nestedConfirm.options.cancelText ?? '取消'}
		  </Button>

		  <Button
		      variant={nestedConfirm.options.variant === 'danger'
			  ? 'destructive'
			  : 'default'}
		      onClick={() => closeNestedConfirm (true)}>
		    {nestedConfirm.options.confirmText ?? '確定'}
		  </Button>
		</DialogFooter>
	      </DialogContent>
	    </Dialog>)}
      </DialogueContext.Provider>)
}
export { useDialogue } from '@/lib/dialogues/useDialogue'
export default DialogueProvider
