// Active-thread dock with Sigil header, conversation bubbles, numbered options.
//
// M6: live thread loaded from /api/projects/[id]/socrates. The reply input
// POSTs each user turn and renders Socrates' response when it arrives.
// Numbered options pre-fill the input when clicked / pressed (1–3).

"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { Sigil } from "./Sigil";
import { ProposalCard, type ProposalPayload } from "./ProposalCard";
import { useApply } from "../../lib/sync/ModelStore";

export type SocratesPresence = "subtle" | "default" | "prominent";
export type Density = "comfortable" | "compact";

export interface DockMessage {
  id: string;
  role: "user" | "assistant" | "proposal" | "system";
  text: string;
  options?: Array<{ n: number; label: string; sub?: string }>;
  pending?: boolean;
  proposal?: ProposalPayload;
}

interface SocratesDockProps {
  projectId: string;
  presence: SocratesPresence;
  density: Density;
}

interface ApiMessage {
  id: string;
  role: "user" | "assistant";
  text: string;
  options?: Array<{ n: number; label: string; sub?: string }>;
  createdAt: string;
}

export function SocratesDock({ projectId, presence }: SocratesDockProps) {
  const [messages, setMessages] = useState<DockMessage[]>([]);
  const [input, setInput] = useState("");
  const [sending, setSending] = useState(false);
  const [proposing, setProposing] = useState(false);
  const [loaded, setLoaded] = useState(false);
  const [meta, setMeta] = useState<{ provider?: string; model?: string }>({});
  const threadEndRef = useRef<HTMLDivElement | null>(null);
  const apply = useApply();

  // Load thread on mount and trigger an opening turn if the thread is empty.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await fetch(`/api/projects/${encodeURIComponent(projectId)}/socrates`);
        if (!res.ok) throw new Error(`load thread: ${res.status}`);
        const data = (await res.json()) as { messages?: ApiMessage[] };
        if (cancelled) return;
        const initial: DockMessage[] = (data.messages ?? []).map(m => ({
          id: m.id,
          role: m.role,
          text: m.text,
          options: m.options,
        }));
        setMessages(initial);
        setLoaded(true);

        if (initial.length === 0 && !cancelled) {
          // Trigger Socrates' opening turn
          await sendImpl("", true);
        }
      } catch {
        if (!cancelled) setLoaded(true);
      }
    })();
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [projectId]);

  // Auto-scroll on new turns
  useEffect(() => {
    threadEndRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
  }, [messages]);

  const sendImpl = useCallback(
    async (text: string, isOpening = false) => {
      setSending(true);
      try {
        // Add the user bubble locally (optimistic) — unless this is the opening
        if (!isOpening) {
          setMessages(curr => [
            ...curr,
            { id: `tmp-u-${Date.now()}`, role: "user", text },
          ]);
        }
        // Show a pending Socrates bubble
        const pendingId = `tmp-a-${Date.now()}`;
        setMessages(curr => [
          ...curr,
          { id: pendingId, role: "assistant", text: "thinking…", pending: true },
        ]);

        const res = await fetch(`/api/projects/${encodeURIComponent(projectId)}/socrates`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ text }),
        });
        if (!res.ok) {
          const err = await res.json().catch(() => ({}));
          throw new Error(err.error ?? `${res.status}`);
        }
        const data = await res.json();
        const a = data.assistant as { id: string; text: string; options?: Array<{ n: number; label: string; sub?: string }> };
        if (data.meta) setMeta({ provider: data.meta.provider, model: data.meta.model });

        // Replace the pending bubble with the real one
        setMessages(curr => curr.map(m =>
          m.id === pendingId
            ? { id: a.id, role: "assistant", text: a.text, options: a.options }
            : m
        ));
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        setMessages(curr => curr.map(m =>
          m.pending ? { ...m, text: `⚠ ${msg}`, pending: false } : m
        ));
      } finally {
        setSending(false);
      }
    },
    [projectId]
  );

  const onSubmit = useCallback(async () => {
    const text = input.trim();
    if (!text || sending) return;
    setInput("");
    await sendImpl(text);
  }, [input, sending, sendImpl]);

  const pickOption = useCallback(async (option: { n: number; label: string; sub?: string }) => {
    if (sending) return;
    const text = `[${option.n}] ${option.label}${option.sub ? ` — ${option.sub}` : ""}`;
    await sendImpl(text);
  }, [sending, sendImpl]);

  // Ask Socrates to propose a model change.
  const requestProposal = useCallback(async () => {
    if (proposing || sending) return;
    setProposing(true);
    const pendingId = `tmp-p-${Date.now()}`;
    setMessages(curr => [
      ...curr,
      { id: pendingId, role: "proposal", text: "Drafting a proposal…", pending: true },
    ]);
    try {
      const res = await fetch(`/api/projects/${encodeURIComponent(projectId)}/socrates/propose`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({}),
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.error ?? `${res.status}`);
      }
      const data = (await res.json()) as ProposalPayload;
      setMessages(curr => curr.map(m =>
        m.id === pendingId
          ? { id: pendingId, role: "proposal", text: data.reasoning, proposal: data }
          : m
      ));
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      setMessages(curr => curr.map(m =>
        m.id === pendingId
          ? { ...m, text: `⚠ propose failed: ${msg}`, pending: false }
          : m
      ));
    } finally {
      setProposing(false);
    }
  }, [proposing, sending, projectId]);

  const acceptProposal = useCallback(async (messageId: string, proposal: ProposalPayload) => {
    const result = apply(proposal.ops);
    if (!result.applied) {
      setMessages(curr => [
        ...curr,
        {
          id: `tmp-sys-${Date.now()}`,
          role: "system",
          text: `⚠ Couldn't apply: ${result.errors.map(e => `${e.code}: ${e.message}`).join("; ")}`,
        },
      ]);
      return;
    }
    // Mark the proposal accepted (drop the live card; keep a summary line)
    setMessages(curr => curr.map(m =>
      m.id === messageId
        ? {
            id: messageId,
            role: "system",
            text: `✓ Applied · ${proposal.ops.length} op${proposal.ops.length === 1 ? "" : "s"} · ${truncate(proposal.reasoning, 90)}`,
          }
        : m
    ));
  }, [apply]);

  const rejectProposal = useCallback((messageId: string) => {
    setMessages(curr => curr.map(m =>
      m.id === messageId
        ? { id: messageId, role: "system", text: "Proposal dismissed." }
        : m
    ));
  }, []);

  // Number-key shortcuts on the most recent assistant turn with options
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      // Don't hijack number keys when typing in any input/contenteditable
      const target = e.target as HTMLElement | null;
      if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) return;
      if (sending) return;
      const last = [...messages].reverse().find(m => m.role === "assistant" && m.options?.length);
      if (!last?.options) return;
      const n = parseInt(e.key, 10);
      if (Number.isNaN(n) || n < 1 || n > last.options.length) return;
      e.preventDefault();
      const opt = last.options.find(o => o.n === n);
      if (opt) void pickOption(opt);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [messages, pickOption, sending]);

  if (presence === "subtle") {
    return (
      <div className="dock dock-subtle" title={meta.model ? `Σ via ${meta.model}` : "Σ Socrates"}>
        <Sigil size={36} />
        {messages.filter(m => m.role === "assistant").length > 0 && (
          <div className="dock-subtle-count">{messages.filter(m => m.role === "assistant").length}</div>
        )}
      </div>
    );
  }

  return (
    <aside className={`dock ${presence === "prominent" ? "dock-prominent" : "dock-default"}`}>
      <header className="dock-header">
        <Sigil size={28} />
        <div className="dock-header-text">
          <div className="dock-name">Socrates</div>
          <div className="dock-status">
            <span className="dock-dot" />
            {meta.provider ? `${meta.provider}${meta.model ? ` · ${meta.model.split("/").pop()}` : ""}` : "ready"}
          </div>
        </div>
        <button className="dock-header-action" title="New thread (coming soon)" type="button" disabled>
          +
        </button>
      </header>

      <section className="dock-thread-wrap">
        <div className="dock-section-label dock-section-label-inline">Active thread</div>
        <div className="dock-thread">
          {!loaded && (
            <div className="bubble bubble-assistant">
              <span className="bubble-sigil">Σ</span>
              <span className="bubble-body">
                <span className="bubble-text" style={{ opacity: 0.6 }}>loading…</span>
              </span>
            </div>
          )}
          {messages.map(m => {
            // Proposal card — render in place of a normal bubble
            if (m.role === "proposal") {
              if (m.proposal) {
                return (
                  <ProposalCard
                    key={m.id}
                    proposal={m.proposal}
                    onAccept={() => acceptProposal(m.id, m.proposal!)}
                    onReject={() => rejectProposal(m.id)}
                  />
                );
              }
              // Pending or errored proposal — show a thin status line
              return (
                <div key={m.id} className="bubble bubble-socrates">
                  <span className="bubble-sigil">Σ</span>
                  <span className="bubble-body">
                    <span className="bubble-text" style={m.pending ? { opacity: 0.55, fontStyle: "italic" } : undefined}>
                      {m.text}
                    </span>
                  </span>
                </div>
              );
            }
            // System notes (apply confirmation, errors)
            if (m.role === "system") {
              return (
                <div key={m.id} className="bubble bubble-system">
                  <span className="bubble-text">{m.text}</span>
                </div>
              );
            }
            // Normal user / assistant bubble
            return (
              <div key={m.id} className={`bubble bubble-${m.role === "assistant" ? "socrates" : "user"}`}>
                {m.role === "assistant" && <span className="bubble-sigil">Σ</span>}
                <span className="bubble-body">
                  <span className="bubble-text" style={m.pending ? { opacity: 0.55, fontStyle: "italic" } : undefined}>
                    {m.text}
                  </span>
                  {m.options && m.options.length > 0 && (
                    <div className="bubble-options">
                      {m.options.map(o => (
                        <button
                          key={o.n}
                          className="bubble-option"
                          type="button"
                          onClick={() => pickOption(o)}
                          disabled={sending}
                        >
                          <span className="bubble-option-num">{o.n}</span>
                          <span className="bubble-option-text">
                            <span className="bubble-option-label">{o.label}</span>
                            {o.sub && <span className="bubble-option-sub">{o.sub}</span>}
                          </span>
                          <span className="bubble-option-key">{o.n}</span>
                        </button>
                      ))}
                      <div className="bubble-options-hint">
                        Press <kbd>1</kbd>–<kbd>{m.options.length}</kbd>, or type a reply
                      </div>
                    </div>
                  )}
                </span>
              </div>
            );
          })}
          <div ref={threadEndRef} />
        </div>
      </section>

      <footer className="dock-input-wrap">
        <form
          className="dock-input"
          onSubmit={e => {
            e.preventDefault();
            void onSubmit();
          }}
        >
          <span className="dock-input-prompt">›</span>
          <input
            className="dock-input-field"
            placeholder={sending ? "Socrates is thinking…" : "Reply to Socrates…"}
            value={input}
            onChange={e => setInput(e.target.value)}
            disabled={sending}
          />
          <button
            type="button"
            className="dock-input-propose"
            onClick={() => void requestProposal()}
            disabled={proposing || sending}
            title="Ask Socrates to propose a model change"
          >
            propose
          </button>
          <button
            type="submit"
            className="dock-input-send"
            disabled={sending || !input.trim()}
            title="Send (⌘↵)"
          >
            ⌘↵
          </button>
        </form>
      </footer>
    </aside>
  );
}

function truncate(s: string, n: number): string {
  return s.length > n ? s.slice(0, n - 1) + "…" : s;
}
