import { useCallback, useEffect, useRef, useState } from 'react'
import { api, type DocSummary, type Document, type Suggestion } from './api/client'
import { useAutoSave } from './hooks/useAutoSave'
import { useCheckpoint } from './hooks/useCheckpoint'
import { useSpellChecker } from './hooks/useSpellChecker'
import { DocList } from './components/DocList/DocList'
import { EditorCore, type EditorChange } from './components/Editor/EditorCore'
import { ToneSelect } from './components/Editor/ToneSelect'
import { StatusBar } from './components/StatusBar/StatusBar'
import { PetalCompanion } from './components/Companion/PetalCompanion'
import { UpdateBanner } from './components/UpdateBanner/UpdateBanner'
import { useVersionWatch } from './hooks/useVersionWatch'

export default function App() {
  const updateAvailable = useVersionWatch()
  const [docs, setDocs] = useState<DocSummary[]>([])
  const [currentDoc, setCurrentDoc] = useState<Document | null>(null)
  const [title, setTitle] = useState('')
  const [wordCount, setWordCount] = useState(0)
  // The current document's target tone (steers checkpoint advice) and its live
  // plain text (drives the expanded writing-stats panel in the StatusBar).
  const [tone, setTone] = useState('general')
  const [docText, setDocText] = useState('')
  const [ready, setReady] = useState(false)
  // Distraction-free mode: entered on editor focus, collapses the doc-list
  // sidebar. Escape or a click outside the editor canvas restores it.
  const [focusMode, setFocusMode] = useState(false)
  const canvasRef = useRef<HTMLDivElement>(null)
  // Monotonic counters the companion watches to react to writing + accepts.
  const [editTick, setEditTick] = useState(0)
  const [acceptTick, setAcceptTick] = useState(0)

  const { status, schedule, saveNow } = useAutoSave(currentDoc?.id ?? null)
  const {
    suggestions,
    checking,
    voicing,
    schedule: scheduleCheckpoint,
    runVoice,
    removeSuggestion,
  } = useCheckpoint(currentDoc?.id ?? null)
  // Browser-side spell checker — loads the en-US dictionary once per session.
  const { checker: spellChecker, addWord } = useSpellChecker()

  // Patch a summary in the sidebar list (optimistic title / word-count updates).
  const patchSummary = useCallback((id: string, patch: Partial<DocSummary>) => {
    setDocs((prev) => prev.map((d) => (d.id === id ? { ...d, ...patch } : d)))
  }, [])

  const openDoc = useCallback(
    async (id: string) => {
      await saveNow() // flush any pending edits to the doc we're leaving
      const doc = await api.getDoc(id)
      setCurrentDoc(doc)
      setTitle(doc.title)
      setWordCount(doc.word_count)
      setTone(doc.tone || 'general')
      setDocText(doc.content_text)
    },
    [saveNow],
  )

  // Initial load: fetch the list, opening the first doc (or creating one).
  const bootedRef = useRef(false)
  useEffect(() => {
    if (bootedRef.current) return
    bootedRef.current = true
    ;(async () => {
      try {
        let list = await api.listDocs()
        if (list.length === 0) {
          const fresh = await api.createDoc()
          list = [{ id: fresh.id, title: fresh.title, word_count: 0, updated_at: fresh.updated_at }]
          setDocs(list)
          setCurrentDoc(fresh)
          setTitle(fresh.title)
          setWordCount(0)
          setTone(fresh.tone || 'general')
          setDocText('')
        } else {
          setDocs(list)
          await openDoc(list[0].id)
        }
      } catch (err) {
        console.error('failed to load documents', err)
      } finally {
        setReady(true)
      }
    })()
  }, [openDoc])

  const handleCreate = useCallback(async () => {
    await saveNow()
    const fresh = await api.createDoc()
    setDocs((prev) => [
      { id: fresh.id, title: fresh.title, word_count: 0, updated_at: fresh.updated_at },
      ...prev,
    ])
    setCurrentDoc(fresh)
    setTitle(fresh.title)
    setWordCount(0)
    setTone(fresh.tone || 'general')
    setDocText('')
  }, [saveNow])

  const handleDelete = useCallback(
    async (id: string) => {
      await api.deleteDoc(id)
      setDocs((prev) => {
        const remaining = prev.filter((d) => d.id !== id)
        if (currentDoc?.id === id) {
          if (remaining.length > 0) {
            void openDoc(remaining[0].id)
          } else {
            setCurrentDoc(null)
            setTitle('')
            setWordCount(0)
            setTone('general')
            setDocText('')
          }
        }
        return remaining
      })
    },
    [currentDoc, openDoc],
  )

  const handleTitleChange = useCallback(
    (value: string) => {
      setTitle(value)
      if (currentDoc) {
        patchSummary(currentDoc.id, { title: value })
        schedule({ title: value })
      }
    },
    [currentDoc, patchSummary, schedule],
  )

  const handleEditorChange = useCallback(
    (change: EditorChange) => {
      setWordCount(change.word_count)
      setDocText(change.content_text)
      setEditTick((n) => n + 1)
      if (currentDoc) {
        patchSummary(currentDoc.id, { word_count: change.word_count })
        schedule(change)
        scheduleCheckpoint()
      }
    },
    [currentDoc, patchSummary, schedule, scheduleCheckpoint],
  )

  // Changing the tone persists it and re-runs the checkpoint so Petal's advice
  // re-tunes to the new register. The auto-save (1.5s) lands before the
  // checkpoint debounce (4s), so the server reads the updated tone.
  const handleToneChange = useCallback(
    (value: string) => {
      setTone(value)
      if (currentDoc) {
        schedule({ tone: value })
        scheduleCheckpoint()
      }
    },
    [currentDoc, schedule, scheduleCheckpoint],
  )

  // Accept applies the replacement in the editor (handled in EditorCore) and
  // marks the suggestion accepted; dismiss just rejects it. Both drop it locally.
  const handleAccept = useCallback(
    async (s: Suggestion) => {
      removeSuggestion(s.id)
      setAcceptTick((n) => n + 1)
      try {
        await api.acceptSuggestion(s.id)
      } catch (err) {
        console.error('accept failed', err)
      }
    },
    [removeSuggestion],
  )

  // Escape always restores the sidebar while in distraction-free mode.
  useEffect(() => {
    if (!focusMode) return
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setFocusMode(false)
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [focusMode])

  // A pointer-down outside the centered editor canvas (the cream gutters, the
  // header, the status bar) also restores the sidebar.
  const handleChromeDown = useCallback((e: React.MouseEvent) => {
    if (!canvasRef.current?.contains(e.target as Node)) setFocusMode(false)
  }, [])

  const handleDismiss = useCallback(
    async (s: Suggestion) => {
      removeSuggestion(s.id)
      try {
        await api.dismissSuggestion(s.id)
      } catch (err) {
        console.error('dismiss failed', err)
      }
    },
    [removeSuggestion],
  )

  return (
    <div className="flex h-full flex-col">
      <header
        onMouseDown={handleChromeDown}
        className="flex h-12 shrink-0 items-center gap-2 px-5"
        style={{ borderBottom: '1px solid var(--color-border)' }}
      >
        <span className="text-xl">🌸</span>
        <span className="text-lg font-extrabold text-plum">Petal</span>
      </header>

      <div className="flex min-h-0 flex-1">
        <div className={`petal-sidebar shrink-0${focusMode ? ' petal-sidebar-hidden' : ''}`}>
          <DocList
            docs={docs}
            selectedId={currentDoc?.id ?? null}
            onSelect={openDoc}
            onCreate={handleCreate}
            onDelete={handleDelete}
          />
        </div>

        <main className="flex min-w-0 flex-1 flex-col">
          {currentDoc ? (
            <>
              <div
                onMouseDown={handleChromeDown}
                className="flex flex-1 flex-col overflow-y-auto px-6 py-8"
              >
                <div ref={canvasRef} className="mx-auto flex w-full max-w-[720px] flex-1 flex-col">
                  <div className="mb-5 flex items-center gap-3">
                    <input
                      value={title}
                      onChange={(e) => handleTitleChange(e.target.value)}
                      placeholder="Untitled"
                      aria-label="Document title"
                      className="min-w-0 flex-1 bg-transparent text-3xl font-extrabold text-plum focus:outline-none"
                      style={{ fontFamily: 'var(--font-ui)' }}
                    />
                    <ToneSelect value={tone} onChange={handleToneChange} />
                  </div>
                  <EditorCore
                    key={currentDoc.id}
                    docId={currentDoc.id}
                    initialContent={currentDoc.content}
                    onChange={handleEditorChange}
                    suggestions={suggestions}
                    onAccept={handleAccept}
                    onDismiss={handleDismiss}
                    onVoiceCheck={runVoice}
                    voicing={voicing}
                    onFocusMode={() => setFocusMode(true)}
                    spellChecker={spellChecker}
                    onAddWord={addWord}
                  />
                </div>
              </div>
              <div onMouseDown={handleChromeDown}>
                <StatusBar
                  wordCount={wordCount}
                  text={docText}
                  saveStatus={status}
                  checking={checking}
                  voicing={voicing}
                />
              </div>
            </>
          ) : (
            <div
              className="flex flex-1 items-center justify-center text-sm"
              style={{ color: 'var(--color-muted)' }}
            >
              {ready ? 'Create a document to start writing.' : 'Loading…'}
            </div>
          )}
        </main>
      </div>

      {updateAvailable && <UpdateBanner />}

      <PetalCompanion
        wordCount={wordCount}
        saveStatus={status}
        editTick={editTick}
        acceptTick={acceptTick}
      />
    </div>
  )
}
