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

import type { ChangeProposal, ChatMessage as WireMessage, IssueChange } from '@commitea/core'

import { CANNED_REPLY, CHAT, type ChatMessage } from '../data/fixtures.js'

const LIVE_GREETING: ChatMessage = {
  from: 'agent',
  text: 'Morning. Ask me anything about the project — I check the real board before I answer.',
}

export interface ChatState {
  msgs: ChatMessage[]
  thinking: boolean
  /** true once a model endpoint is confirmed; otherwise the panel echoes the demo reply. */
  live: boolean
  /** The loaded model's id when live (for the header). */
  model: string | null
  /** Tools Reginald consulted on the last turn (for a subtle activity line). */
  steps: string[]
  /** Changes Reginald has proposed and is awaiting approval on. */
  proposals: ChangeProposal[]
  send: (text: string) => void
  approve: (p: ChangeProposal) => void
  dismiss: (p: ChangeProposal) => void
}

/**
 * Reginald's conversation. When a model is configured, `send` drives one agent
 * turn through the main-process bridge (which runs the tool loop). Otherwise it
 * echoes the scripted fixture reply, so the layout stays real with no model and
 * fixture e2e is unaffected. The fixture greeting is display-only — only real
 * turns (`convo`) are sent to the model as history. `onApplyChange` performs an
 * approved write (the same handler the Issue screen uses — it refetches).
 */
export function useChat(onApplyChange?: (change: IssueChange) => Promise<{ ok: boolean }>): ChatState {
  const [seed, setSeed] = useState<ChatMessage[]>(CHAT)
  const [convo, setConvo] = useState<ChatMessage[]>([])
  const [thinking, setThinking] = useState(false)
  const [live, setLive] = useState(false)
  const [model, setModel] = useState<string | null>(null)
  const [steps, setSteps] = useState<string[]>([])
  const [proposals, setProposals] = useState<ChangeProposal[]>([])
  const convoRef = useRef(convo)
  convoRef.current = convo

  const proposalKey = (p: ChangeProposal) => `${p.change.issue}:${p.change.kind}`
  const drop = (p: ChangeProposal) => setProposals((ps) => ps.filter((x) => proposalKey(x) !== proposalKey(p)))

  useEffect(() => {
    let alive = true
    window.commitea.model
      .status()
      .then((s) => {
        if (alive && s.configured) {
          setLive(true)
          setModel(s.model)
          setSeed([LIVE_GREETING])
        }
      })
      .catch(() => {})
    return () => {
      alive = false
    }
  }, [])

  const send = useCallback(
    (raw: string) => {
      const text = raw.trim()
      if (!text) return
      const nextConvo: ChatMessage[] = [...convoRef.current, { from: 'user', text }]
      setConvo(nextConvo)
      setThinking(true)
      setSteps([])
      setProposals([])

      if (!live) {
        window.setTimeout(() => {
          setThinking(false)
          setConvo((c) => [...c, { from: 'agent', text: CANNED_REPLY }])
        }, 900)
        return
      }

      const wire: WireMessage[] = nextConvo.map((m) => ({
        role: m.from === 'user' ? 'user' : 'assistant',
        content: m.text,
      }))
      window.commitea.model
        .chat(wire)
        .then((res) => {
          setThinking(false)
          if (res.ok) {
            setSteps(res.steps.map((s) => s.tool))
            setProposals(res.proposals)
            setConvo((c) => [...c, { from: 'agent', text: res.content || '…' }])
          } else {
            setConvo((c) => [
              ...c,
              {
                from: 'agent',
                text: res.reason === 'error' ? `I hit a snag: ${res.message ?? 'unknown error'}` : 'No model is configured.',
              },
            ])
          }
        })
        .catch(() => {
          setThinking(false)
          setConvo((c) => [...c, { from: 'agent', text: 'I could not reach the model.' }])
        })
    },
    [live],
  )

  const approve = useCallback(
    (p: ChangeProposal) => {
      if (!onApplyChange) return
      drop(p)
      const label = p.plan.added[0] ?? p.plan.removed[0] ?? 'change'
      void onApplyChange(p.change).then((res) => {
        setConvo((c) => [
          ...c,
          {
            from: 'agent',
            text: res.ok
              ? `Done — #${p.change.issue} is now ${label}. The plan's been re-run.`
              : `That didn't take — #${p.change.issue} is unchanged.`,
          },
        ])
      })
    },
    [onApplyChange],
  )

  const dismiss = useCallback((p: ChangeProposal) => {
    drop(p)
    setConvo((c) => [...c, { from: 'agent', text: `Left #${p.change.issue} as it was.` }])
  }, [])

  return { msgs: [...seed, ...convo], thinking, live, model, steps, proposals, send, approve, dismiss }
}
