/**
 * Simplified web chat for Forge — no extension required.
 *
 * Talks to the chat-standalone server through /api/chat-proxy. Full
 * featureset (per-tab connector hints, browser-side scripts, unread
 * badges, fork UI) stays in the browser extension; this page is a
 * fallback so users can chat from any device with a browser.
 *
 * Routes used:
 *   GET    /api/chat-proxy/sessions
 *   GET    /api/chat-proxy/sessions/main
 *   POST   /api/chat-proxy/sessions          (new temp session)
 *   GET    /api/chat-proxy/sessions/:id       (messages for a session)
 *   DELETE /api/chat-proxy/sessions/:id/messages  (clear)
 *   POST   /api/chat-proxy/sessions/:id/messages  (send user turn)
 *   GET    /api/chat-proxy/sessions/:id/events    (SSE)
 *   DELETE /api/chat-proxy/sessions/:id           (delete — refused for main)
 */

'use client';

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import MarkdownContent from '@/components/MarkdownContent';
import type { ContentBlock, Message, Session } from '@/lib/chat/types';

const PROXY = '/api/chat-proxy';

// The global body font is monospace (see globals.css). The chat page
// is a reading surface — override to a UI-friendly system stack and
// let MarkdownContent's <code> elements switch back to mono inline.
const SANS_FONT =
  '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif';

interface MemoryStatus {
  backend?: 'temper' | 'local';
  pinnedCount?: number;
  blocksCount?: number;
  hitsCount?: number;
}

interface ChatSession extends Session {
  meta?: { kind?: 'main' | 'temp'; [k: string]: unknown };
}

export default function ChatPage() {
  const [sessions, setSessions] = useState<ChatSession[]>([]);
  const [activeId, setActiveId] = useState<string>('');
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [streaming, setStreaming] = useState(false);
  const [partial, setPartial] = useState('');
  const [memory, setMemory] = useState<MemoryStatus | null>(null);
  const [error, setError] = useState('');

  const eventSrcRef = useRef<EventSource | null>(null);
  const scrollRef = useRef<HTMLDivElement>(null);
  const composerRef = useRef<HTMLTextAreaElement>(null);

  // ─── Load sessions ────────────────────────────────────────
  const refreshSessions = useCallback(async () => {
    try {
      const [listResp, mainResp] = await Promise.all([
        fetch(`${PROXY}/sessions?limit=200`),
        fetch(`${PROXY}/sessions/main`),
      ]);
      const listJson = (await listResp.json()) as { sessions: ChatSession[] };
      const list = listJson.sessions || [];
      const mainJson = (await mainResp.json()) as { session: ChatSession };
      const mainId = mainJson?.session?.id;
      const ordered = list.slice().sort((a, b) => {
        if (a.id === mainId) return -1;
        if (b.id === mainId) return 1;
        return (b.updated_at || 0) - (a.updated_at || 0);
      });
      setSessions(ordered);
      if (!activeId && mainId) setActiveId(mainId);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  }, [activeId]);

  useEffect(() => {
    refreshSessions();
  }, [refreshSessions]);

  // ─── Load messages on session change ──────────────────────
  const loadMessages = useCallback(async (id: string) => {
    if (!id) return;
    try {
      const r = await fetch(`${PROXY}/sessions/${id}?limit=1000`);
      const j = (await r.json()) as { messages?: Message[] };
      setMessages(j.messages || []);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  }, []);

  useEffect(() => {
    if (activeId) loadMessages(activeId);
  }, [activeId, loadMessages]);

  // ─── SSE subscription ─────────────────────────────────────
  useEffect(() => {
    if (!activeId) return;
    eventSrcRef.current?.close();
    const src = new EventSource(`${PROXY}/sessions/${activeId}/events`);
    eventSrcRef.current = src;

    src.onmessage = (ev) => {
      let payload: { type?: string; data?: any; message_id?: string };
      try { payload = JSON.parse(ev.data); } catch { return; }
      const type = payload.type;
      const data = payload.data || {};
      if (type === 'text_delta') {
        setPartial((p) => p + (data.delta || ''));
      } else if (type === 'message_saved') {
        loadMessages(activeId);
        setPartial('');
      } else if (type === 'memory_status') {
        setMemory({
          backend: data.backend,
          pinnedCount: data.pinnedCount,
          blocksCount: data.blocksCount,
          hitsCount: data.hitsCount,
        });
      } else if (type === 'turn_done') {
        setStreaming(false);
        setPartial('');
        loadMessages(activeId);
        refreshSessions();
      } else if (type === 'error') {
        setStreaming(false);
        setError(String(data.error || 'unknown error'));
      }
    };
    return () => {
      src.close();
    };
  }, [activeId, loadMessages, refreshSessions]);

  // ─── Auto-scroll on new content ───────────────────────────
  useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages, partial]);

  // ─── Auto-resize composer ─────────────────────────────────
  useEffect(() => {
    const el = composerRef.current;
    if (!el) return;
    el.style.height = 'auto';
    el.style.height = Math.min(el.scrollHeight, 200) + 'px';
  }, [input]);

  // ─── Actions ──────────────────────────────────────────────
  async function send() {
    const text = input.trim();
    if (!text || !activeId || streaming) return;
    setInput('');
    setStreaming(true);
    setError('');
    setPartial('');
    try {
      const r = await fetch(`${PROXY}/sessions/${activeId}/messages`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ text }),
      });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error(j.error || `HTTP ${r.status}`);
      }
    } catch (e) {
      setStreaming(false);
      setError(e instanceof Error ? e.message : String(e));
    }
  }

  async function newSession() {
    try {
      const r = await fetch(`${PROXY}/sessions`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ meta: { kind: 'temp' } }),
      });
      const j = (await r.json()) as { session?: ChatSession };
      if (j?.session?.id) {
        await refreshSessions();
        setActiveId(j.session.id);
      }
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  }

  async function clearMessages() {
    if (!activeId) return;
    if (!confirm('Clear all messages in this session?')) return;
    await fetch(`${PROXY}/sessions/${activeId}/messages`, { method: 'DELETE' });
    setMessages([]);
    setPartial('');
  }

  async function deleteSession(id: string) {
    if (!confirm('Delete this session permanently?')) return;
    const r = await fetch(`${PROXY}/sessions/${id}`, { method: 'DELETE' });
    if (!r.ok) {
      const j = await r.json().catch(() => ({}));
      alert(j.error || 'Delete failed (main session cannot be deleted)');
      return;
    }
    if (id === activeId) setActiveId('');
    await refreshSessions();
  }

  // ─── Render ───────────────────────────────────────────────
  const activeSession = useMemo(
    () => sessions.find((s) => s.id === activeId),
    [sessions, activeId],
  );

  return (
    <div
      className="flex h-screen text-[var(--text-primary)] bg-[var(--bg-primary)]"
      style={{ fontFamily: SANS_FONT }}
    >
      {/* ─── Sidebar ─────────────────────────────────────── */}
      <aside className="w-64 shrink-0 border-r border-[var(--border)] flex flex-col bg-[var(--bg-secondary)]/40">
        <div className="px-4 py-3 border-b border-[var(--border)] flex items-center justify-between">
          <a
            href="/"
            className="text-sm font-semibold text-[var(--text-primary)] hover:text-[var(--accent)] transition-colors"
            title="Back to Dashboard"
          >
            ← Forge
          </a>
          <button
            onClick={newSession}
            className="text-xs px-2.5 py-1 bg-[var(--accent)] text-white rounded-md hover:opacity-90 transition-opacity"
          >
            + New
          </button>
        </div>

        <div className="flex-1 overflow-y-auto py-2 px-2">
          {sessions.length === 0 && (
            <div className="text-xs text-[var(--text-secondary)] italic px-2 py-3">No sessions yet.</div>
          )}
          {sessions.map((s) => {
            const isMain = s.meta?.kind === 'main';
            const isActive = s.id === activeId;
            return (
              <div
                key={s.id}
                className={`group flex items-center gap-2 px-3 py-2 rounded-md text-sm cursor-pointer mb-1 transition-colors ${
                  isActive
                    ? 'bg-[var(--accent)]/15 text-[var(--text-primary)]'
                    : 'text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)] hover:text-[var(--text-primary)]'
                }`}
                onClick={() => setActiveId(s.id)}
              >
                <span
                  className={`inline-block w-2 h-2 rounded-full shrink-0 ${
                    isMain ? 'bg-[var(--accent)]' : 'bg-[var(--text-secondary)]/40'
                  }`}
                />
                <div className="truncate flex-1">
                  {s.title || (isMain ? 'Main conversation' : `Temp · ${s.id.slice(0, 6)}`)}
                </div>
                {!isMain && (
                  <button
                    onClick={(e) => { e.stopPropagation(); deleteSession(s.id); }}
                    className="opacity-0 group-hover:opacity-100 text-[var(--text-secondary)] hover:text-red-400 text-base leading-none px-1"
                    title="Delete session"
                  >
                    ×
                  </button>
                )}
              </div>
            );
          })}
        </div>

        <div className="px-4 py-3 border-t border-[var(--border)] text-xs text-[var(--text-secondary)] space-y-1">
          <div className="flex items-center gap-2">
            <span>Memory</span>
            <span
              className={`px-1.5 py-[1px] rounded text-[10px] uppercase tracking-wide border ${
                memory?.backend === 'temper'
                  ? 'border-green-500/60 text-green-400'
                  : memory?.backend === 'local'
                    ? 'border-[var(--accent)] text-[var(--accent)]'
                    : 'border-[var(--border)]'
              }`}
            >
              {memory?.backend ?? '…'}
            </span>
          </div>
          {memory && (
            <div className="text-[11px]">
              {memory.pinnedCount ?? 0} pinned · {memory.blocksCount ?? 0} blocks
            </div>
          )}
          <div className="text-[10px] italic pt-1 leading-snug">
            Simplified web chat — full UX lives in the browser extension.
          </div>
        </div>
      </aside>

      {/* ─── Main pane ───────────────────────────────────── */}
      <main className="flex-1 flex flex-col min-w-0">
        <header className="border-b border-[var(--border)] px-6 py-3 flex items-center justify-between">
          <div className="min-w-0">
            <div className="text-sm font-medium truncate">
              {activeSession?.title ||
                (activeSession?.meta?.kind === 'main' ? 'Main conversation' : activeSession?.id) ||
                'No session'}
            </div>
            {activeSession && (
              <div className="text-[11px] text-[var(--text-secondary)]">
                {activeSession.provider || 'auto'} · {activeSession.model || 'default'}
              </div>
            )}
          </div>
          <button
            onClick={clearMessages}
            disabled={!activeId}
            className="text-xs px-3 py-1 border border-[var(--border)] text-[var(--text-secondary)] rounded-md hover:border-red-500/60 hover:text-red-400 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
          >
            Clear
          </button>
        </header>

        <div ref={scrollRef} className="flex-1 overflow-y-auto">
          <div className="max-w-3xl mx-auto px-6 py-6 space-y-6">
            {messages.length === 0 && !partial && !streaming && (
              <div className="text-center text-sm text-[var(--text-secondary)] mt-12">
                <div className="text-base mb-1">Start a conversation</div>
                <div className="text-xs">Type a message below. Markdown supported.</div>
              </div>
            )}
            {messages.map((m) => (
              <MessageView key={m.id} m={m} />
            ))}
            {partial && (
              <RoleBlock role="assistant">
                <MarkdownContent content={partial} />
                <span className="inline-block w-2 h-3 ml-0.5 align-text-bottom bg-[var(--accent)] animate-pulse" />
              </RoleBlock>
            )}
            {streaming && !partial && (
              <RoleBlock role="assistant">
                <div className="text-sm text-[var(--text-secondary)] italic">thinking…</div>
              </RoleBlock>
            )}
            {error && (
              <div className="text-sm text-red-400 border border-red-500/30 bg-red-500/5 rounded-md p-3">
                {error}
              </div>
            )}
          </div>
        </div>

        <form
          className="border-t border-[var(--border)] px-6 py-4"
          onSubmit={(e) => { e.preventDefault(); send(); }}
        >
          <div className="max-w-3xl mx-auto flex items-end gap-3">
            <textarea
              ref={composerRef}
              value={input}
              onChange={(e) => setInput(e.target.value)}
              onKeyDown={(e) => {
                // Skip Enter while an IME composition is active — otherwise
                // pinyin/kana commit-with-Enter sends the message by mistake.
                // isComposing covers modern browsers; keyCode===229 is the
                // legacy fallback some IMEs still emit.
                if (e.nativeEvent.isComposing || e.keyCode === 229) return;
                if (e.key === 'Enter' && !e.shiftKey) {
                  e.preventDefault();
                  send();
                }
              }}
              disabled={!activeId || streaming}
              placeholder={activeId ? 'Message…  (Enter to send · Shift+Enter for newline)' : 'Pick or create a session'}
              rows={1}
              style={{ fontFamily: SANS_FONT }}
              className="flex-1 resize-none bg-[var(--bg-tertiary)] border border-[var(--border)] rounded-lg px-4 py-3 text-sm text-[var(--text-primary)] leading-relaxed focus:outline-none focus:border-[var(--accent)] focus:ring-1 focus:ring-[var(--accent)]/40 disabled:opacity-50"
            />
            <button
              type="submit"
              disabled={!input.trim() || !activeId || streaming}
              className="px-4 py-2.5 text-sm font-medium bg-[var(--accent)] text-white rounded-lg hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed transition-opacity"
            >
              {streaming ? '…' : 'Send'}
            </button>
          </div>
        </form>
      </main>
    </div>
  );
}

// ─── Message renderers ────────────────────────────────────

function RoleBlock({ role, children }: { role: 'user' | 'assistant'; children: React.ReactNode }) {
  const isUser = role === 'user';
  return (
    <div className="flex gap-4">
      <div
        className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold ${
          isUser
            ? 'bg-[var(--accent)] text-white'
            : 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)] border border-[var(--border)]'
        }`}
        aria-hidden
      >
        {isUser ? 'U' : 'AI'}
      </div>
      <div className="flex-1 min-w-0">
        <div className="text-[11px] uppercase tracking-wide text-[var(--text-secondary)] mb-1">
          {role}
        </div>
        <div className="space-y-2">{children}</div>
      </div>
    </div>
  );
}

function MessageView({ m }: { m: Message }) {
  return (
    <RoleBlock role={m.role}>
      {m.blocks.map((b, i) => (
        <BlockView key={i} b={b} />
      ))}
      {m.error && (
        <div className="text-xs text-red-400 border border-red-500/30 bg-red-500/5 rounded-md p-2 mt-1">
          {m.error}
        </div>
      )}
    </RoleBlock>
  );
}

function BlockView({ b }: { b: ContentBlock }) {
  if (b.type === 'text') {
    return <MarkdownContent content={b.text} />;
  }
  if (b.type === 'tool_use') {
    return <ToolUseBlockView name={b.name} input={b.input} />;
  }
  if (b.type === 'tool_result') {
    const txt = typeof b.content === 'string' ? b.content : JSON.stringify(b.content);
    return <ToolResultBlockView content={txt} isError={!!b.is_error} />;
  }
  return null;
}

function ToolUseBlockView({ name, input }: { name: string; input: unknown }) {
  const [open, setOpen] = useState(false);
  const preview = JSON.stringify(input);
  return (
    <div className="rounded-md border border-[var(--border)] bg-[var(--bg-tertiary)]/50 text-xs">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="w-full px-3 py-1.5 flex items-center gap-2 text-left hover:bg-[var(--bg-tertiary)] transition-colors"
      >
        <span className="text-[10px]">{open ? '▾' : '▸'}</span>
        <span className="text-[var(--accent)] font-mono">→ {name}</span>
        {!open && (
          <span className="text-[var(--text-secondary)] font-mono truncate flex-1">{preview}</span>
        )}
      </button>
      {open && (
        <pre className="px-3 pb-2 pt-1 text-[11px] font-mono text-[var(--text-secondary)] whitespace-pre-wrap break-words border-t border-[var(--border)] bg-[var(--bg-tertiary)]/30">
          {tryPrettyJson(preview)}
        </pre>
      )}
    </div>
  );
}

function ToolResultBlockView({ content, isError }: { content: string; isError: boolean }) {
  const [open, setOpen] = useState(false);
  const truncated = content.length > 400 ? content.slice(0, 400) + '…' : content;
  return (
    <div
      className={`rounded-md border text-xs ${
        isError
          ? 'border-red-500/40 bg-red-500/5'
          : 'border-[var(--border)] bg-[var(--bg-tertiary)]/30'
      }`}
    >
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="w-full px-3 py-1.5 flex items-center gap-2 text-left hover:bg-[var(--bg-tertiary)] transition-colors"
      >
        <span className="text-[10px]">{open ? '▾' : '▸'}</span>
        <span className={isError ? 'text-red-400 font-mono' : 'text-[var(--text-secondary)] font-mono'}>
          {isError ? 'error result' : 'tool result'}
        </span>
        {!open && (
          <span className="text-[var(--text-secondary)] font-mono truncate flex-1">
            {truncated.replace(/\s+/g, ' ').slice(0, 100)}
          </span>
        )}
      </button>
      {open && (
        <pre
          className={`px-3 pb-2 pt-1 text-[11px] font-mono whitespace-pre-wrap break-words border-t ${
            isError
              ? 'border-red-500/30 text-red-400'
              : 'border-[var(--border)] text-[var(--text-secondary)]'
          }`}
        >
          {content.length > 4000 ? content.slice(0, 4000) + '\n…(truncated)' : content}
        </pre>
      )}
    </div>
  );
}

function tryPrettyJson(s: string): string {
  try {
    return JSON.stringify(JSON.parse(s), null, 2);
  } catch {
    return s;
  }
}
