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

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

import type { FC, ReactNode } from 'react'

type DialogueVariant = 'default' | 'danger'

type ConfirmOptions = { title:        string
			description?: ReactNode
			confirmText?: string
			cancelText?:  string
			variant?:     DialogueVariant }

type AlertOptions = { title:        string
		      description?: ReactNode
		      okText?:      string }

type Choice<T extends string> = { value:    T
				  label:    string
				  variant?: DialogueVariant }

type ChoiceOptions<T extends string> = { title:         string
					 description?:  ReactNode
					 choices:       Choice<T>[]
					 cancelText?:   string }

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 }

type DialogueAPI =
    { confirm:  (options: ConfirmOptions) => Promise<boolean>
      alert:    (options: AlertOptions) => Promise<void>
      choice:   <T extends string> (options: ChoiceOptions<T>) => Promise<T | null> }

const DialogueContext = createContext<DialogueAPI | null> (null)

let nextDialogueId = 1

type Props = { children: ReactNode }


const DialogueProvider: FC<Props> = ({ children }) => {
  const [queue, setQueue] = useState<DialogueRequest[]> ([])

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

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

  const closeActive = useCallback ((result?: unknown) => {
    setQueue (q => {
      const [active, ...rest] = q

      if (!(active))
	return rest

      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
      }

      return rest
    })
  }, [])

  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 })
      }) }), [push])

  const active = queue[0]

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

	<Dialog
	    open={Boolean (active)}
	    onOpenChange={open => {
	      if (!(open))
		closeActive (active?.kind !== 'confirm' && null)
	    }}>
	  {active && (
	      <DialogContent className="px-6 pb-6 pt-7">
		<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={() => closeActive (false)}>
			  {active.options.cancelText ?? '取消'}
			</Button>

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

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

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

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


export const useDialogue = () => {
  const dialogue = useContext (DialogueContext)

  if (!(dialogue))
    throw new Error ('useDialogue must be used inside DialogueProvider')

  return dialogue
}

export default DialogueProvider
