import { useCallback, useEffect } from 'react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'

import DialogueProvider from '@/components/dialogues/DialogueProvider'
import useDialogue from '@/lib/dialogues/useDialogue'

import type { DialogueFormControls } from '@/lib/dialogues/useDialogue'

const FormBody = (
    { controls,
      onSelect }: { controls: DialogueFormControls
		    onSelect: () => Promise<boolean> | boolean },
) => {
  useEffect (() => {
    controls.setActions ([{
      label: '左操作',
      placement: 'start',
      onSelect }, {
      label: '保存',
      onSelect: () => true }])
  }, [controls, onSelect])

  return <div>長いフォーム本文</div>
}

const NestedConfirmFormBody = ({ controls }: { controls: DialogueFormControls }) => {
  const reset = useCallback (async () => {
    await controls.confirm ({
      title: '変更をリセットしますか?',
      confirmText: 'リセット' })
    return false
  }, [controls])

  return <FormBody controls={controls} onSelect={reset}/>
}

describe ('DialogueProvider', () => {
  it ('keeps ordinary dialogues in FIFO order', async () => {
    const Launcher = () => {
      const dialogue = useDialogue ()
      return (
	  <button
	      onClick={() => {
		void dialogue.confirm ({ title: '一件目' })
		void dialogue.confirm ({ title: '二件目' })
	      }}>
	    開く
	  </button>)
    }

    render (<DialogueProvider><Launcher/></DialogueProvider>)
    fireEvent.click (screen.getByRole ('button', { name: '開く' }))

    expect (screen.getByText ('一件目')).toBeInTheDocument ()
    expect (screen.queryByText ('二件目')).not.toBeInTheDocument ()
    fireEvent.click (screen.getByRole ('button', { name: '確定' }))
    expect (await screen.findByText ('二件目')).toBeInTheDocument ()
  })

  it ('keeps a large form open when an action returns false', async () => {
    const action = vi.fn ().mockResolvedValue (false)
    const Launcher = () => {
      const dialogue = useDialogue ()
      return (
	  <button
	      onClick={() => void dialogue.form ({
		title: '投稿を編輯',
		description: '説明',
		size: 'large',
		body: controls => <FormBody controls={controls} onSelect={action}/> })}>
	    開く
	  </button>)
    }

    render (<DialogueProvider><Launcher/></DialogueProvider>)
    fireEvent.click (screen.getByRole ('button', { name: '開く' }))

    const dialogue = screen.getByRole ('dialog')
    expect (dialogue).toHaveClass ('max-h-[calc(100dvh-1rem)]', 'flex-col', 'max-w-3xl')
    await waitFor (() => expect (screen.getByRole ('button', { name: '左操作' }))
	.toBeInTheDocument ())
    expect (screen.getByRole ('button', { name: '左操作' })).toHaveClass (
	'w-full',
	'md:w-auto')
    fireEvent.click (screen.getByRole ('button', { name: '左操作' }))

    await waitFor (() => expect (action).toHaveBeenCalledTimes (1))
    expect (screen.getByText ('投稿を編輯')).toBeInTheDocument ()
  })

  it ('opens a nested confirmation over a form and returns to the same form', async () => {
    const Launcher = () => {
      const dialogue = useDialogue ()
      return (
	  <button
	      onClick={() => void dialogue.form ({
		title: '投稿を編輯',
		body: controls => <NestedConfirmFormBody controls={controls}/> })}>
	    開く
	  </button>)
    }

    render (<DialogueProvider><Launcher/></DialogueProvider>)
    fireEvent.click (screen.getByRole ('button', { name: '開く' }))
    const action = await screen.findByRole ('button', { name: '左操作' })
    fireEvent.click (action)

    expect (await screen.findByText ('変更をリセットしますか?')).toBeInTheDocument ()
    fireEvent.click (screen.getByRole ('button', { name: 'リセット' }))
    await waitFor (() => {
      expect (screen.queryByText ('変更をリセットしますか?')).not.toBeInTheDocument ()
    })
    expect (screen.getByText ('投稿を編輯')).toBeInTheDocument ()
  })
})
