'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import { saveLastSessionId } from './use-dashboard-sessions';
import { clearSavedDraft } from './use-draft-persist';

export function useStreamCommand({
  chat,
  setChat,
  selectedModel,
  sessionId,
  setSessionId,
  sessionArtifactId,
  setSessionArtifactId,
  setUsage,
  refresh,
  refreshSessions,
  bringToFront,
  projectId,
}) {
  const [busy, setBusy] = useState(false);
  const [liveText, setLiveText] = useState('');
  const [liveTool, setLiveTool] = useState(null);
  const [liveExpanded, setLiveExpanded] = useState(false);
  const [liveSteps, setLiveSteps] = useState([]);
  const [busyStartTime, setBusyStartTime] = useState(null);
  const [agentPhase, setAgentPhase] = useState('idle');
  // Approvals harvested from tool_result events with status='approval_required'.
  // Drained into pending_approvals on the next outgoing request so the server
  // can inject approval_id into the retried tool call.
  const pendingApprovalsRef = useRef([]);
  const abortRef = useRef(null);
  const stoppedRef = useRef(false);
  const feedRef = useRef(null);

  useEffect(() => {
    const el = feedRef.current;
    if (!el) return;
    const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 200;
    if (busy || nearBottom) el.scrollTop = el.scrollHeight;
  }, [chat, liveText, busy]);

  // Ref-dispatch pattern keeps `command`/`stop` referentially stable even
  // though their bodies close over every render's `busy`, `chat`, etc. The
  // exported `useCallback` wrappers below have `[]` deps and forward to the
  // ref. This prevents downstream consumers (ChatPanel.send useCallback,
  // CanvasSection's `mainAgent.command` capture) from busting their own
  // memoization on every agent-state tick.
  const commandRef = useRef(null);
  const stopRef = useRef(null);

  async function commandImpl(text, attachments) {
    if (busy) return;
    const clean = (text ?? '').trim();
    const files = Array.isArray(attachments) ? attachments : [];
    // Allow an attachment-only send (no text) — the model still gets the files.
    if (!clean && !files.length) return;
    setChat(c => [...c, { role: 'user', text: clean, attachments: files }]);
    clearSavedDraft();
    setLiveSteps([]);
    setAgentPhase('thinking');
    setBusyStartTime(Date.now());
    setBusy(true);

    const rawHistory = chat
      .filter(m => m.role === 'user' || m.role === 'agent')
      .map(m => ({ role: m.role === 'agent' ? 'assistant' : 'user', content: m.text }));
    while (rawHistory.length && rawHistory[0].role === 'assistant') rawHistory.shift();
    const history = rawHistory;
    history.push({ role: 'user', content: clean, attachments: files });

    const abort = new AbortController();
    abortRef.current = abort;
    stoppedRef.current = false;

    try {
      // Drain approvals collected since the last send. The server injects
      // approval_id into the matching tool call before dispatching it.
      const draining = pendingApprovalsRef.current.slice();
      pendingApprovalsRef.current = [];

      const res = await fetch('/api/orchestrator/stream', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          messages: history,
          model: selectedModel,
          session_id: sessionId,
          session_artifact_id: sessionArtifactId,
          project_id: projectId || 'personal',
          pending_approvals: draining,
        }),
        signal: abort.signal,
      });

      if (!res.ok || !res.body) {
        const err = await res.text();
        setChat(c => [...c, { role: 'agent', text: `Stream error: ${err}` }]);
        return;
      }

      const reader = res.body.getReader();
      const dec = new TextDecoder();
      let buf = '';
      let agentText = '';

      setChat(c => [...c, { role: 'agent', text: '' }]);

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += dec.decode(value, { stream: true });

        const lines = buf.split('\n');
        buf = lines.pop();

        let eventName = '';
        for (const line of lines) {
          if (line.startsWith('event:')) {
            eventName = line.slice(6).trim();
          } else if (line.startsWith('data:')) {
            const raw = line.slice(5).trim();
            try {
              const payload = JSON.parse(raw);
              if (eventName === 'session' && payload.id) {
                saveLastSessionId(payload.id);
                if (payload.id !== sessionId) setSessionId(payload.id);
              } else if (eventName === 'cli_stream' && payload.text) {
                setAgentPhase(p => p === 'thinking' ? 'responding' : p);
                setChat(c => {
                  const next = [...c];
                  next[next.length - 1] = { role: 'agent', text: payload.text, streaming: true };
                  return next;
                });
              } else if (eventName === 'text' && payload.delta) {
                agentText += payload.delta;
                setAgentPhase(p => p === 'thinking' ? 'responding' : p);
                setLiveText(t => t + payload.delta);
                setChat(c => {
                  const next = [...c];
                  next[next.length - 1] = { role: 'agent', text: agentText };
                  return next;
                });
              } else if (eventName === 'tool_start') {
                setLiveTool(payload.name);
                setAgentPhase('tool');
                setLiveSteps(s => [...s, {
                  id: `${payload.name}-${Date.now()}`,
                  name: payload.name,
                  input: payload.input,
                  status: 'running',
                }]);
              } else if (eventName === 'tool_result') {
                setLiveTool(null);
                setAgentPhase('synthesis');
                const isApprovalRequired =
                  payload.output?.status === 'approval_required' &&
                  typeof payload.output?.approval_request_id === 'string';
                setLiveSteps(s => {
                  const next = [...s];
                  for (let i = next.length - 1; i >= 0; i--) {
                    if (next[i].name === payload.name && next[i].status === 'running') {
                      next[i] = {
                        ...next[i],
                        status: isApprovalRequired
                          ? 'awaiting_approval'
                          : (payload.isError ? 'error' : 'done'),
                        approval_id: isApprovalRequired
                          ? payload.output.approval_request_id
                          : next[i].approval_id || null,
                        approval_reason: isApprovalRequired
                          ? (payload.output.reason || null)
                          : null,
                      };
                      break;
                    }
                  }
                  return next;
                });
                if ((payload.name === 'create_artifact' || payload.name === 'generate_ui' || payload.name === 'send_to_hermes' || payload.name === 'start_hermes_job') && payload.output?.artifact_id) {
                  bringToFront(payload.output.artifact_id);
                }
              } else if (eventName === 'artifacts' && payload.created?.length) {
                for (const a of payload.created) {
                  if (a.artifact_id) bringToFront(a.artifact_id);
                }
                refresh();
              } else if (eventName === 'agent_created' && payload.agent) {
                refresh();
              } else if (eventName === 'session_artifact' && payload.artifact_id) {
                setSessionArtifactId(payload.artifact_id);
              } else if (eventName === 'artifact_updated' && payload.artifact_id) {
                setSessionArtifactId(payload.artifact_id);
                bringToFront(payload.artifact_id);
                refresh();
              } else if (eventName === 'usage') {
                setUsage(payload);
              } else if (eventName === 'pending_approvals' && Array.isArray(payload?.approvals)) {
                // Server returned unused approvals (adapter errored + refunded).
                // Put them back into the ref so the next outgoing message
                // carries them again.
                for (const a of payload.approvals) {
                  if (a?.approval_id && a?.tool_name) {
                    pendingApprovalsRef.current.push({ approval_id: a.approval_id, tool_name: a.tool_name });
                  }
                }
              } else if (eventName === 'error') {
                agentText += `\nError: ${payload.message}`;
                setChat(c => {
                  const next = [...c];
                  next[next.length - 1] = { role: 'agent', text: agentText };
                  return next;
                });
              }
            } catch {
              // non-JSON data line — ignore
            }
            eventName = '';
          }
        }
      }

      if (!agentText) {
        setChat(c => {
          const next = [...c];
          next[next.length - 1] = { role: 'agent', text: 'Done.' };
          return next;
        });
      }

      await refresh();
      await refreshSessions();
    } catch (error) {
      const wasAborted = stoppedRef.current || error?.name === 'AbortError';
      if (wasAborted) {
        setChat(c => {
          const next = [...c];
          const last = next[next.length - 1];
          if (last && last.role === 'agent') {
            const body = (last.text || '').trim();
            next[next.length - 1] = {
              role: 'agent',
              text: body ? `${body}\n\n_(stopped by user)_` : '_(stopped by user)_',
            };
          } else {
            next.push({ role: 'agent', text: '_(stopped by user)_' });
          }
          return next;
        });
      } else {
        setChat(c => [...c, { role: 'agent', text: `Action failed: ${error.message}` }]);
      }
    } finally {
      abortRef.current = null;
      stoppedRef.current = false;
      setBusy(false);
      setBusyStartTime(null);
      setAgentPhase('idle');
      setLiveText('');
      setLiveTool(null);
      // Don't auto-clear when an approval chip needs to stay visible. The
      // user needs to see and act on the Approve / Deny buttons.
      setTimeout(() => {
        setLiveSteps(s => s.some(step => step.status === 'awaiting_approval') ? s : []);
      }, 1200);
      setLiveExpanded(false);
    }
  }

  async function decideApprovalImpl(approvalId, decision, toolName) {
    if (!approvalId) return;
    try {
      const res = await fetch(`/api/approvals/${approvalId}`, {
        method: 'PATCH',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ decision }),
      });
      if (!res.ok) {
        const err = await res.text().catch(() => '');
        setChat(c => [...c, { role: 'agent', text: `Approval failed: ${err || res.status}` }]);
        return;
      }
    } catch (err) {
      setChat(c => [...c, { role: 'agent', text: `Approval error: ${err.message}` }]);
      return;
    }

    setLiveSteps(s => s.map(step => step.approval_id === approvalId
      ? { ...step, status: decision === 'approved' ? 'approved' : 'denied' }
      : step));

    if (decision === 'approved' && toolName) {
      pendingApprovalsRef.current.push({ approval_id: approvalId, tool_name: toolName });
      commandRef.current?.('Approved — please continue.');
    } else if (decision === 'denied') {
      commandRef.current?.('Denied — try a different approach.');
    }
  }

  function stopImpl() {
    if (!busy) return;
    stoppedRef.current = true;
    try { abortRef.current?.abort(); } catch { /* already aborted */ }
  }

  commandRef.current = commandImpl;
  stopRef.current = stopImpl;
  const decideApprovalRef = useRef(null);
  decideApprovalRef.current = decideApprovalImpl;
  const command = useCallback((text, attachments) => commandRef.current(text, attachments), []);
  const stop = useCallback(() => stopRef.current(), []);
  const decideApproval = useCallback(
    (id, decision, toolName) => decideApprovalRef.current(id, decision, toolName),
    [],
  );

  return {
    busy,
    liveText,
    liveTool,
    liveExpanded,
    setLiveExpanded,
    liveSteps,
    busyStartTime,
    agentPhase,
    feedRef,
    command,
    stop,
    decideApproval,
  };
}
