import { useEffect, useMemo, useState } from 'react'

import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import {
    getConflictingShortcutActionIds,
    formatKeyBinding,
    isSpaceBinding,
    keyBindingFromKeyboardEvent,
    SHORTCUT_DEFINITIONS,
    SHORTCUT_SCOPE_LABELS,
} from '@/lib/keyboardShortcuts'
import { getEffectiveKeyBindings } from '@/lib/settings'
import { useKeyboardShortcutSettings } from '@/lib/useKeyboardShortcuts'

import type { ShortcutActionId } from '@/lib/keyboardShortcuts'
import type { ClientKeyboardSettings } from '@/lib/settings'
import type { FC } from 'react'

type Props = {
    sectionClassName: string
    onDirtyStateChange?: (
	dirty: boolean,
	discard: () => void,
    ) => void
}


const KeyboardSettingsSection: FC<Props> = (
    { sectionClassName,
      onDirtyStateChange },
) => {
  const {
      keyboardSettings,
      saveKeyboardSettings,
  } = useKeyboardShortcutSettings ()
  const [capturingActionId, setCapturingActionId] =
      useState<ShortcutActionId | null> (null)
  const [draftKeyboardSettings, setDraftKeyboardSettings] =
      useState<ClientKeyboardSettings> (keyboardSettings)

  useEffect (() => {
    setDraftKeyboardSettings (keyboardSettings)
  }, [keyboardSettings])

  const draftEffectiveBindings = useMemo (
      () => getEffectiveKeyBindings (draftKeyboardSettings.bindings),
      [draftKeyboardSettings.bindings],
  )
  const draftConflictActionIds = useMemo (
      () => getConflictingShortcutActionIds (draftEffectiveBindings),
      [draftEffectiveBindings],
  )
  const hasUnsavedChanges = useMemo (
      () => JSON.stringify (draftKeyboardSettings) !== JSON.stringify (keyboardSettings),
      [draftKeyboardSettings, keyboardSettings],
  )

  useEffect (() => {
    if (capturingActionId == null)
      return

    const onKeyDown = (event: KeyboardEvent) => {
      if (event.isComposing || event.key === 'Process')
	return

      event.preventDefault ()
      event.stopPropagation ()

      if (event.key === 'Escape')
	{
	  setCapturingActionId (null)
	  return
	}

      if (event.key === 'Backspace' || event.key === 'Delete')
	{
	  setDraftKeyboardSettings (current => ({
	      ...current,
	      bindings: {
		  ...(current.bindings ?? { }),
		  [capturingActionId]: null,
	      },
	  }))
	  setCapturingActionId (null)
	  return
	}

      const binding = keyBindingFromKeyboardEvent (event)
      if (binding == null)
	return

      if (isSpaceBinding (binding))
	{
	  toast ({ title: 'Space は今は割り当てられません．' })
	  return
	}

      setDraftKeyboardSettings (current => ({
	  ...current,
	  bindings: {
	      ...(current.bindings ?? { }),
	      [capturingActionId]: binding,
	  },
      }))
      setCapturingActionId (null)
    }

    document.addEventListener ('keydown', onKeyDown, true)
    return () => document.removeEventListener ('keydown', onKeyDown, true)
  }, [capturingActionId])

  const rows = useMemo (() => SHORTCUT_DEFINITIONS.map (definition => ({
      ...definition,
      currentBinding:    draftEffectiveBindings[definition.id],
      hasConflict:       draftConflictActionIds.has (definition.id),
      hasCustomBinding:  Object.prototype.hasOwnProperty.call (
	  draftKeyboardSettings.bindings ?? { },
	  definition.id,
      ),
  })), [
      draftConflictActionIds,
      draftEffectiveBindings,
      draftKeyboardSettings.bindings,
  ])

  const handleSave = () => {
    saveKeyboardSettings (draftKeyboardSettings)
    toast ({ title: 'キーボード設定を保存しました' })
  }

  const handleDiscard = () => {
    setDraftKeyboardSettings (keyboardSettings)
    setCapturingActionId (null)
  }

  useEffect (() => {
    onDirtyStateChange?.(hasUnsavedChanges, handleDiscard)

    return () => {
      onDirtyStateChange?.(false, () => { })
    }
  }, [handleDiscard, hasUnsavedChanges, onDirtyStateChange])

  return (
      <section className={sectionClassName}>
	<div className="flex flex-wrap items-start justify-between gap-3">
	  <div className="space-y-1">
	    <h2 className="text-xl font-bold">キーボード</h2>
	  </div>

	  <div className="flex flex-wrap gap-2">
	    <Button
		type="button"
		variant="outline"
		onClick={() => {
		  setDraftKeyboardSettings (current => ({
		      ...current,
		      bindings: { },
		  }))
		}}>
	      すべて既定に戻す
	    </Button>
	    <Button
		type="button"
		variant="destructive"
		disabled={!(hasUnsavedChanges)}
		onClick={handleDiscard}>
	      変更を破棄
	    </Button>
	    <Button
		type="button"
		disabled={!(hasUnsavedChanges) || draftConflictActionIds.size > 0}
		onClick={handleSave}>
	      保存
	    </Button>
	  </div>
	</div>

	<label className="flex items-center gap-2 text-sm">
	  <input
	      type="checkbox"
	      checked={draftKeyboardSettings.enabled !== false}
	      onChange={event => {
		setDraftKeyboardSettings (current => ({
		    ...current,
		    enabled: event.target.checked,
		}))
	      }}/>
	  <span>キーボード・ショートカットを有効にする</span>
	</label>

	{capturingActionId != null && (
	    <p className="text-sm text-muted-foreground">
	      キー入力待ちです．Escape で取消し，Backspace / Delete で未割当にできます．
	    </p>)}

	<div className="overflow-x-auto">
	  <table className="w-full min-w-[52rem] table-fixed border-collapse text-sm">
	    <thead className="border-b border-border">
	      <tr>
		<th className="p-2 text-left">操作</th>
		<th className="p-2 text-left">適用範囲</th>
		<th className="p-2 text-left">現在のキー</th>
		<th className="p-2 text-left"></th>
		<th className="p-2 text-left"></th>
		<th className="p-2 text-left">競合状態</th>
	      </tr>
	    </thead>
	    <tbody>
	      {rows.map (row => (
		  <tr key={row.id} className="border-b border-border/60 last:border-b-0">
		    <td className="p-2">{row.label}</td>
		    <td className="p-2">{SHORTCUT_SCOPE_LABELS[row.scope]}</td>
		    <td className="p-2 font-mono">
		      {formatKeyBinding (row.currentBinding)}
		    </td>
		    <td className="p-2">
		      <Button
			  type="button"
			  variant={capturingActionId === row.id ? 'default' : 'outline'}
			  className="h-8"
			  onClick={() => {
			    setCapturingActionId (
				capturingActionId === row.id ? null : row.id,
			    )
			  }}>
			{capturingActionId === row.id ? '入力待ち…' : '変更'}
		      </Button>
		    </td>
		    <td className="p-2">
		      <Button
			  type="button"
			  variant="ghost"
			  className="h-8"
			  disabled={!(row.hasCustomBinding)}
			  onClick={() => {
			    setDraftKeyboardSettings (current => {
				const nextBindings = { ...(current.bindings ?? { }) }
				delete nextBindings[row.id]
				return {
				    ...current,
				    bindings: nextBindings,
				}
			    })
			  }}>
			既定に戻す
		      </Button>
		    </td>
		    <td className="p-2">
		      {row.hasConflict
		       ? <span className="text-red-600 dark:text-red-400">競合あり</span>
		       : 'なし'}
		    </td>
		  </tr>))}
	    </tbody>
	  </table>
	</div>
      </section>)
}

export default KeyboardSettingsSection
