// MFM Chat — App composition. Login → Chat home → conversational view.
// Wires all primitives together into a click-through prototype.

const FAKE_HISTORY = [
  { id: "c1", title: "Next.js routing patterns",       bucket: "today" },
  { id: "c2", title: "Help me draft a release note",   bucket: "today" },
  { id: "c3", title: "Dijkstra walk-through in TS",    bucket: "week" },
  { id: "c4", title: "Refactor my React reducer",      bucket: "week" },
  { id: "c5", title: "Compare pgvector and Pinecone",  bucket: "week" },
  { id: "c6", title: "Silicon Valley essay draft",     bucket: "older" },
  { id: "c7", title: "Weather in San Francisco",       bucket: "older" },
];

const FAKE_USER = {
  email: "demo@mfm.test",
  plan: "Guest",
};

// Canned assistant responses keyed by trigger words.
function fakeAssistantReply(userText) {
  const t = userText.toLowerCase();
  if (t.includes("next.js")) {
    return [
      "Next.js gives you a full-stack React framework — App Router for nested layouts, React Server Components for streaming server-rendered UI, and built-in routing, image, and font optimization.",
      "The main wins for teams:",
      "• Zero-config route handlers and middleware",
      "• Server Actions for type-safe mutations without an API layer",
      "• First-class deploy story on Vercel",
      "Read the [App Router docs](https://nextjs.org/docs/app) for the canonical reference. What's the project you're picking it for?",
    ].join("\n\n");
  }
  if (t.includes("dijkstra")) {
    return [
      "Here's a minimal Dijkstra in TypeScript that runs against an adjacency list and returns the shortest distance from `start` to every node.",
      "```ts\nfunction dijkstra(graph: Map<string, Array<[string, number]>>, start: string) {\n  const dist = new Map<string, number>();\n  for (const node of graph.keys()) dist.set(node, Infinity);\n  dist.set(start, 0);\n  const queue: Array<[string, number]> = [[start, 0]];\n  while (queue.length) {\n    queue.sort((a, b) => a[1] - b[1]);\n    const [u, d] = queue.shift()!;\n    if (d > dist.get(u)!) continue;\n    for (const [v, w] of graph.get(u) ?? []) {\n      if (d + w < dist.get(v)!) {\n        dist.set(v, d + w);\n        queue.push([v, d + w]);\n      }\n    }\n  }\n  return dist;\n}\n```",
      "I sorted the queue each pop for clarity — swap it for a binary heap if your graph is large. Want me to add that?",
    ].join("\n\n");
  }
  if (t.includes("silicon valley")) {
    return [
      "Want me to start that essay as a single arc, or split it into eras (Shockley → Fairchild → the PC → the web → mobile → AI)?",
      "Either way, I'd anchor the opening on one specific moment that's unfamiliar enough to set tone — something like Shockley Semiconductor's founding in Mountain View in 1956, *because* it's the unromantic origin most retellings skip.",
    ].join("\n\n");
  }
  if (t.includes("weather")) {
    return [
      "I don't have live weather access in this demo, but San Francisco at this time of year typically sits around 14–18 °C, with marine fog through the morning and clearer afternoons in the Mission and Castro.",
      "If you wire up a weather tool I can call it instead.",
    ].join("\n\n");
  }
  return [
    "Got it. I'll keep responses concise and helpful in this thread — what would you like to dig into first?",
  ].join("\n\n");
}

// Streamed text generator — yields one chunk at a time with realistic timing.
async function streamReply(text, onChunk, signal) {
  const tokens = text.split(/(\s+)/);
  let acc = "";
  for (const tok of tokens) {
    if (signal?.aborted) return;
    acc += tok;
    onChunk(acc);
    await new Promise((r) => setTimeout(r, 18 + Math.random() * 30));
  }
}

function bucketize(messageText) {
  // pick the right history item to "open" based on the prompt
  const t = messageText.toLowerCase();
  if (t.includes("next.js")) return "c1";
  if (t.includes("dijkstra")) return "c3";
  if (t.includes("silicon valley")) return "c6";
  if (t.includes("weather")) return "c7";
  return null;
}

const ChatApp = () => {
  // Read ?skip-auth=1 from the URL to bypass sign-in — useful for previews
  // and for jumping straight into the chat surface.
  const skipAuth = typeof window !== "undefined" &&
    new URLSearchParams(window.location.search).get("skip-auth") === "1";
  const [authed, setAuthed] = React.useState(skipAuth);
  const [sidebarOpen, setSidebarOpen] = React.useState(true);
  const [visibility, setVisibility] = React.useState("private");
  const [chats, setChats] = React.useState(FAKE_HISTORY);
  const [activeId, setActiveId] = React.useState(null);
  const [messages, setMessages] = React.useState([]);
  const [input, setInput] = React.useState("");
  const [status, setStatus] = React.useState("ready");
  const [selectedModelId, setSelectedModelId] = React.useState("chat-model");
  const [attachments, setAttachments] = React.useState([]);
  const [deleteOpen, setDeleteOpen] = React.useState(false);
  const abortRef = React.useRef(null);
  const scrollRef = React.useRef(null);

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

  const handleNew = () => {
    setMessages([]);
    setActiveId(null);
    setInput("");
    setAttachments([]);
    abortRef.current?.abort();
    setStatus("ready");
  };

  const handleSelect = (id) => {
    setActiveId(id);
    // For the demo, selecting old chat just shows a placeholder thread.
    const chat = chats.find((c) => c.id === id);
    if (chat) {
      setMessages([
        { role: "user", text: chat.title, id: id + "-u" },
        {
          role: "assistant",
          text: "(This is a saved conversation. Send a new message to continue.)",
          id: id + "-a",
        },
      ]);
    }
  };

  const submit = (override) => {
    const text = (override ?? input).trim();
    if (!text) return;
    const userMsg = {
      role: "user",
      text,
      id: "u-" + Date.now(),
      parts: attachments.map((a) => ({ type: "file", name: a.name })),
    };
    setMessages((m) => [...m, userMsg]);
    setInput("");
    setAttachments([]);
    setStatus("submitted");

    // bind to a history item if matching
    const bucketId = bucketize(text);
    if (bucketId) setActiveId(bucketId);

    // schedule streamed assistant reply
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    const reply = fakeAssistantReply(text);
    const aId = "a-" + Date.now();

    setTimeout(async () => {
      setStatus("streaming");
      setMessages((m) => [...m, { role: "assistant", text: "", id: aId }]);
      await streamReply(
        reply,
        (acc) => {
          setMessages((m) =>
            m.map((msg) => (msg.id === aId ? { ...msg, text: acc } : msg))
          );
        },
        ctrl.signal
      );
      setStatus("ready");
    }, 1100);
  };

  const stop = () => {
    abortRef.current?.abort();
    setStatus("ready");
  };

  const fakeUploadFile = async (file) => {
    await new Promise((r) => setTimeout(r, 700));
    return {
      url: URL.createObjectURL(file),
      name: file.name,
      contentType: file.type,
    };
  };

  if (!authed) {
    return (
      <div className="h-full w-full relative">
        <AuthScreen
          mode="login"
          onSubmit={(res) => {
            // Returning user — sign-in lands straight in the chat. (Onboarding
            // is a separate entry, reached via the email invite link.)
            // Magic-link shows "check your inbox" inside AuthScreen; Google
            // proceeds into the app.
            if (!res || res.method === "google") setAuthed(true);
          }}
        />
        {/* Dev shortcut — visible in the kit, lets you jump to the chat surface */}
        <button
          type="button"
          onClick={() => setAuthed(true)}
          className="fixed top-3 right-3 z-50 text-[11px] font-mono uppercase tracking-wide text-muted-foreground hover:text-foreground border border-border rounded-md px-2 py-1 bg-background"
          title="Skip auth — jump to the chat surface"
        >
          Skip auth →
        </button>
      </div>
    );
  }

  return (
    <div className="flex h-full w-full">
      <Sidebar
        open={sidebarOpen}
        onToggle={() => setSidebarOpen((o) => !o)}
        chats={chats}
        activeId={activeId}
        onSelect={handleSelect}
        onNew={handleNew}
        onDeleteAll={() => setDeleteOpen(true)}
        user={FAKE_USER}
      />

      <main className="flex h-full min-w-0 flex-1 flex-col bg-background">
        <ChatHeader
          sidebarOpen={sidebarOpen}
          onToggleSidebar={() => setSidebarOpen((o) => !o)}
          visibility={visibility}
          onChangeVisibility={setVisibility}
          onNew={handleNew}
        />

        <div ref={scrollRef} className="flex-1 overflow-y-auto px-4 md:px-8">
          {messages.length === 0 ? (
            <div className="flex h-full flex-col items-center justify-center pb-16">
              <Greeting />
              <div className="mt-8 w-full max-w-2xl">
                <PromptInput
                  input={input}
                  setInput={setInput}
                  onSubmit={() => submit()}
                  onStop={stop}
                  status={status}
                  selectedModelId={selectedModelId}
                  onModelChange={setSelectedModelId}
                  attachments={attachments}
                  setAttachments={setAttachments}
                  uploadFile={fakeUploadFile}
                />
                <div className="mt-3">
                  <SuggestedActions onPick={(s) => submit(s)} />
                </div>
              </div>
            </div>
          ) : (
            <div className="mx-auto flex w-full max-w-2xl flex-col py-4">
              {messages.map((m, i) => {
                const isLast = i === messages.length - 1;
                // Thinking: empty assistant message → spark pulsing where the
                // answer will begin.
                if (m.role === "assistant" && m.text === "") {
                  return <ThinkingIndicator key={m.id} />;
                }
                const streaming =
                  m.role === "assistant" && isLast && status === "streaming";
                // The IRIS spark + action toolbar live ONLY under the LAST
                // assistant reply — never repeated on earlier answers.
                const showFooter = m.role === "assistant" && isLast;
                return (
                  <React.Fragment key={m.id}>
                    <Message from={m.role} parts={m.parts}>
                      <MessageBody text={m.text} />
                    </Message>
                    {showFooter && (
                      <div className="flex flex-col items-start gap-2 pb-2">
                        {!streaming && (
                          <MessageActions
                            onCopy={() => navigator.clipboard?.writeText(m.text)}
                            onUp={() => {}}
                            onDown={() => {}}
                          />
                        )}
                        {/* Spark sits on its own line, left-aligned, BELOW the
                            response. While streaming it stays under the growing
                            text and descends as the text fills the width and
                            wraps to new lines. */}
                        <IrisSpark state={streaming ? "streaming" : "rest"} />
                      </div>
                    )}
                  </React.Fragment>
                );
              })}
              {/* Distinct thinking phase — shown after the user sends, before
                  the assistant's first token streams in. */}
              {status === "submitted" && <ThinkingIndicator />}
            </div>
          )}
        </div>

        <div className="sticky bottom-0 z-10 mx-auto flex w-full max-w-2xl gap-2 bg-background px-2 pb-3 md:px-4 md:pb-4">
          <div className="relative flex w-full flex-col gap-3">
            {messages.length > 0 && (
              <PromptInput
                input={input}
                setInput={setInput}
                onSubmit={() => submit()}
                onStop={stop}
                status={status}
                selectedModelId={selectedModelId}
                onModelChange={setSelectedModelId}
                attachments={attachments}
                setAttachments={setAttachments}
                uploadFile={fakeUploadFile}
              />
            )}
          </div>
        </div>
      </main>

      <Dialog
        variant="destructive"
        open={deleteOpen}
        title="Delete all chats?"
        description="This action cannot be undone. This will permanently delete all your chats and remove them from our servers."
        cancelLabel="Cancel"
        confirmLabel="Delete All"
        onCancel={() => setDeleteOpen(false)}
        onConfirm={() => {
          setChats([]);
          setDeleteOpen(false);
        }}
      />
    </div>
  );
};

// Lightweight markdown renderer for assistant body — handles code fences,
// bullets, and paragraphs. The source uses streamdown+shiki; we approximate.
const MessageBody = ({ text }) => {
  const parts = React.useMemo(() => {
    const out = [];
    const re = /```(\w+)?\n([\s\S]*?)```/g;
    let lastIdx = 0;
    let m;
    while ((m = re.exec(text)) !== null) {
      if (m.index > lastIdx) {
        out.push({ kind: "text", value: text.slice(lastIdx, m.index) });
      }
      out.push({ kind: "code", lang: m[1] || "", value: m[2] });
      lastIdx = re.lastIndex;
    }
    if (lastIdx < text.length) {
      out.push({ kind: "text", value: text.slice(lastIdx) });
    }
    return out;
  }, [text]);

  return (
    <div className="flex flex-col gap-3 leading-relaxed">
      {parts.map((p, i) =>
        p.kind === "code" ? (
          <CodeBlock key={i} lang={p.lang} code={p.value} />
        ) : (
          <ParagraphBlock key={i} text={p.value} />
        )
      )}
    </div>
  );
};

// Inline parser: convert [text](url) markdown links into <a> nodes,
// leave plain text as-is. Returns an array of strings + React nodes.
const renderInline = (text) => {
  const out = [];
  const re = /\[([^\]]+)\]\(([^)]+)\)/g;
  let lastIdx = 0;
  let m;
  while ((m = re.exec(text)) !== null) {
    if (m.index > lastIdx) out.push(text.slice(lastIdx, m.index));
    out.push(
      <a key={`a-${m.index}`} href={m[2]} target="_blank" rel="noreferrer noopener">
        {m[1]}
      </a>
    );
    lastIdx = re.lastIndex;
  }
  if (lastIdx < text.length) out.push(text.slice(lastIdx));
  return out;
};

const ParagraphBlock = ({ text }) => {
  // split on blank lines, render bullets
  const blocks = text.split(/\n{2,}/);
  return (
    <>
      {blocks.map((para, i) => {
        const lines = para.split("\n");
        if (lines.every((l) => l.startsWith("• ") || l.startsWith("- "))) {
          return (
            <ul key={i} className="ml-4 list-disc space-y-1">
              {lines.map((l, j) => (
                <li key={j}>
                  {renderInline(l.replace(/^[•\-]\s/, ""))}
                </li>
              ))}
            </ul>
          );
        }
        return (
          <p key={i} className="whitespace-pre-wrap">
            {renderInline(para)}
          </p>
        );
      })}
    </>
  );
};

const CodeBlock = ({ lang, code }) => {
  const [copied, setCopied] = React.useState(false);
  return (
    <div className="not-prose rounded-md border border-white/10 bg-zinc-950 text-zinc-100 overflow-hidden">
      <div className="flex items-center justify-between border-b border-white/10 px-3 py-1.5">
        <span className="text-[11px] uppercase tracking-wide text-zinc-400">
          {lang || "text"}
        </span>
        <button
          type="button"
          onClick={() => {
            navigator.clipboard?.writeText(code);
            setCopied(true);
            setTimeout(() => setCopied(false), 1200);
          }}
          className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-zinc-400 hover:bg-white/10 hover:text-zinc-100 transition-colors"
        >
          <CopyIcon size={12} />
          {copied ? "Copied" : "Copy"}
        </button>
      </div>
      <pre className="overflow-x-auto p-3 text-[12.5px] leading-relaxed"><code>{code}</code></pre>
    </div>
  );
};

Object.assign(window, { ChatApp, MessageBody, CodeBlock });
