// Shared hooks + production-backed chat state.
const NOW_ITEMS = [
  "researching prediction-market alpha at Gnosis",
  "studying abroad in Prague",
  "probably in the gym",
  "thinking about why SPY vol clusters around lunchtime",
  "messing around with open source LLMs",
  "reading The Myth of Sisyphus",
  "getting ready to work at Valkyrie Trading, SCALP Trade, and PEAK6",
  "running on cold brew and late nights",
  "learning about recent advancements in AI",
];

const FACTS = [
  { k: "Current",   v: "Prague, Czechia" },
  { k: "School",    v: "UMich \u201927" },
  { k: "Studying",  v: "Financial Math + Data Science, CS minor" },
  { k: "Focus",     v: "Trading, market microstructure, options" },
  { k: "Next",      v: "Valkyrie \u00b7 SCALP \u00b7 PEAK6" },
  { k: "Off hours", v: "Lifting, cold brew, Greek food" },
];

const SUGGESTED_PROMPTS = [
  "What's Baz actually good at?",
  "How does he think about markets?",
  "Why trader first?",
  "What did the engineering background add?",
  "What\u2019s he building right now?",
  "What is he like outside work?",
];

const SITE_THEME_STORAGE_KEY = "baz_site_theme";
const CHAT_REQUEST_TIMEOUT_MS = 120_000;

function normalizeSiteTheme(theme) {
  return theme === "light" ? "light" : "dark";
}

function getStoredSiteTheme() {
  try {
    return normalizeSiteTheme(localStorage.getItem(SITE_THEME_STORAGE_KEY));
  } catch {
    return "dark";
  }
}

function getCurrentSiteTheme() {
  if (typeof document === "undefined") return getStoredSiteTheme();
  return normalizeSiteTheme(document.documentElement.dataset.theme || getStoredSiteTheme());
}

function setSiteTheme(theme) {
  const nextTheme = normalizeSiteTheme(theme);

  if (typeof document !== "undefined") {
    document.documentElement.dataset.theme = nextTheme;
  }

  try {
    localStorage.setItem(SITE_THEME_STORAGE_KEY, nextTheme);
  } catch {}

  if (typeof window !== "undefined") {
    window.dispatchEvent(new CustomEvent("site-theme-change", { detail: { theme: nextTheme } }));
  }

  return nextTheme;
}

function toggleSiteTheme() {
  return setSiteTheme(getCurrentSiteTheme() === "dark" ? "light" : "dark");
}

function openSiteLink(href, options = {}) {
  const { sameTab = false } = options;
  if (!href) return;

  if (sameTab || href.startsWith("mailto:")) {
    window.location.href = href;
    return;
  }

  window.open(href, "_blank", "noopener,noreferrer");
}

async function copySiteEmailAddress() {
  const emailAddress = "bcosm@umich.edu";

  try {
    await navigator.clipboard.writeText(emailAddress);
    return "Email address copied.";
  } catch {
    const helper = document.createElement("textarea");
    helper.value = emailAddress;
    helper.setAttribute("readonly", "true");
    helper.style.position = "absolute";
    helper.style.left = "-9999px";
    document.body.appendChild(helper);
    helper.select();
    document.execCommand("copy");
    document.body.removeChild(helper);
    return "Email address copied.";
  }
}

if (typeof document !== "undefined") {
  document.documentElement.dataset.theme = getCurrentSiteTheme();
}

const sharedChatStore = {
  messages: [],
  history: [],
  busy: false,
  listeners: new Set(),
};

function emitSharedChatUpdate() {
  sharedChatStore.listeners.forEach((listener) => {
    try {
      listener();
    } catch (error) {
      console.error("[Chat] subscriber update failed", error);
    }
  });
}

function subscribeSharedChat(listener) {
  sharedChatStore.listeners.add(listener);
  return () => {
    sharedChatStore.listeners.delete(listener);
  };
}

function getSharedChatSnapshot() {
  return {
    messages: sharedChatStore.messages,
    busy: sharedChatStore.busy,
  };
}

function setSharedChatState(updater) {
  updater(sharedChatStore);
  emitSharedChatUpdate();
}

function useSharedChatState() {
  const [snapshot, setSnapshot] = React.useState(() => getSharedChatSnapshot());

  React.useEffect(() => subscribeSharedChat(() => {
    setSnapshot(getSharedChatSnapshot());
  }), []);

  return snapshot;
}

function createModelContent(role, text) {
  return { role, parts: [{ text }] };
}

function getContentText(content) {
  return String(content?.parts?.[0]?.text || "").trim();
}

function buildRequestHistory(contents) {
  return contents
    .filter((content) => content?.role === "user" || content?.role === "model")
    .map((content) => {
      const text = getContentText(content);
      return text ? createModelContent(content.role, text) : null;
    })
    .filter(Boolean);
}

function buildChatPayload(contents) {
  return {
    contents,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    localDate: new Date().toLocaleDateString("en-US", {
      weekday: "long",
      year: "numeric",
      month: "long",
      day: "numeric",
    }),
    localTime: new Date().toLocaleTimeString("en-US", {
      hour: "2-digit",
      minute: "2-digit",
    }),
  };
}

function safeLogChatMessage(role, content, responseTime) {
  if (typeof window.logChatMessage !== "function") return;

  try {
    window.logChatMessage(role, content, responseTime);
  } catch (error) {
    console.error("[Chat] logChatMessage failed:", error);
  }
}

async function requestAssistantReply(requestContents, requestId, signal, onDelta) {
  const response = await fetch(`/api/chat?rid=${encodeURIComponent(requestId)}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "text/event-stream, application/json",
      "X-Chat-Request-Id": requestId,
    },
    cache: "no-store",
    credentials: "same-origin",
    signal,
    body: JSON.stringify(buildChatPayload(requestContents)),
  });

  const contentType = String(response.headers.get("content-type") || "").toLowerCase();

  if (response.ok && contentType.includes("text/event-stream")) {
    return readStreamedAssistantReply(response, signal, onDelta);
  }

  let payload;
  try {
    payload = await response.json();
  } catch {
    throw new Error(`The server returned an invalid response (${response.status}). Please try again.`);
  }

  if (!response.ok) {
    throw new Error(
      payload?.candidates?.[0]?.content?.parts?.[0]?.text
      || payload?.error
      || `API error (${response.status})`
    );
  }

  const aiResponse = payload?.candidates?.[0]?.content?.parts?.[0]?.text;
  if (!aiResponse || !String(aiResponse).trim()) {
    throw new Error("The server returned an empty response. Please try again.");
  }

  return String(aiResponse).trim();
}

function extractOpenAIStreamText(payload) {
  const content = payload?.choices?.[0]?.delta?.content;

  if (typeof content === "string") {
    return content;
  }

  if (Array.isArray(content)) {
    return content
      .map((part) => part?.text || part?.content || "")
      .join("");
  }

  return "";
}

async function readStreamedAssistantReply(response, signal, onDelta) {
  if (!response.body) {
    throw new Error("The server returned an empty stream. Please try again.");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let accumulated = "";

  const flushBuffer = () => {
    const events = [];
    buffer = buffer.replace(/\r\n/g, "\n");

    let boundaryIndex = buffer.indexOf("\n\n");
    while (boundaryIndex !== -1) {
      events.push(buffer.slice(0, boundaryIndex));
      buffer = buffer.slice(boundaryIndex + 2);
      boundaryIndex = buffer.indexOf("\n\n");
    }

    return events;
  };

  const consumeEvent = (eventBlock) => {
    const data = eventBlock
      .split("\n")
      .filter((line) => line.startsWith("data:"))
      .map((line) => line.slice(5).trimStart())
      .join("\n");

    if (!data) return false;
    if (data === "[DONE]") return true;

    let payload;
    try {
      payload = JSON.parse(data);
    } catch {
      return false;
    }

    if (payload?.error) {
      throw new Error(payload.error?.message || payload.error || "Streaming failed.");
    }

    const chunk = extractOpenAIStreamText(payload);
    if (chunk) {
      accumulated += chunk;
      signal?.throwIfAborted?.();
      onDelta?.(accumulated);
    }

    return false;
  };

  while (true) {
    signal?.throwIfAborted?.();
    const { value, done } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const events = flushBuffer();

    for (const eventBlock of events) {
      const finished = consumeEvent(eventBlock);
      if (finished) {
        return accumulated.trim();
      }
    }
  }

  buffer += decoder.decode();
  const trailingEvents = flushBuffer();
  for (const eventBlock of trailingEvents) {
    const finished = consumeEvent(eventBlock);
    if (finished) {
      return accumulated.trim();
    }
  }

  if (!accumulated.trim()) {
    throw new Error("The server returned an empty response. Please try again.");
  }

  return accumulated.trim();
}

async function sendSharedChatMessage(text) {
  const userMessage = String(text || "").trim();
  if (!userMessage || sharedChatStore.busy) return;

  const requestId = `chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  const responseStartTime = Date.now();
  const abortController = new AbortController();
  let abortMessage = "";
  let requestTimeoutHandle = 0;

  const requestContents = [
    ...buildRequestHistory(sharedChatStore.history),
    createModelContent("user", userMessage),
  ];

  setSharedChatState((store) => {
    store.busy = true;
    store.messages = [
      ...store.messages,
      { who: "u", text: userMessage },
      { who: "b", text: "thinking…", pending: true, requestId },
    ];
  });
  safeLogChatMessage("user", userMessage);

  const cancelRequest = (message) => {
    if (message) abortMessage = message;
    if (!abortController.signal.aborted) {
      abortController.abort();
    }
  };

  const updatePendingMessage = (replyText) => {
    setSharedChatState((store) => {
      store.messages = store.messages.map((message) => (
        message.pending && message.requestId === requestId
          ? { ...message, text: replyText }
          : message
      ));
    });
  };

  const replacePendingMessage = (replyText, isError = false) => {
    setSharedChatState((store) => {
      store.messages = store.messages.map((message) => (
        message.pending && message.requestId === requestId
          ? { who: "b", text: replyText, error: isError }
          : message
      ));
      store.busy = false;
      if (!isError) {
        store.history = [
          ...store.history,
          createModelContent("user", userMessage),
          createModelContent("model", replyText),
        ];
      }
    });
  };

  try {
    requestTimeoutHandle = window.setTimeout(() => {
      cancelRequest("The chat request timed out. Please try again.");
    }, CHAT_REQUEST_TIMEOUT_MS);

    const aiResponse = await requestAssistantReply(requestContents, requestId, abortController.signal, (partialReply) => {
      updatePendingMessage(partialReply);
    });
    replacePendingMessage(aiResponse);
    safeLogChatMessage("model", aiResponse, Date.now() - responseStartTime);
  } catch (error) {
    const requestWasAborted = abortController.signal.aborted || error?.name === "AbortError";
    const errorMsg = requestWasAborted
      ? (abortMessage || "The request timed out. Please try again.")
      : (typeof error?.message === "string" && error.message
        ? error.message
        : "Something went wrong. Please try again.");

    replacePendingMessage(errorMsg, true);
    safeLogChatMessage("model", errorMsg, Date.now() - responseStartTime);
  } finally {
    if (requestTimeoutHandle) {
      clearTimeout(requestTimeoutHandle);
    }
    if (sharedChatStore.busy) {
      setSharedChatState((store) => {
        store.busy = false;
      });
    }
  }
}

// Typed-out status line that cycles through NOW_ITEMS.
function useRotatingStatus(interval = 4200) {
  const [i, setI] = React.useState(0);
  const [shown, setShown] = React.useState("");
  const full = NOW_ITEMS[i % NOW_ITEMS.length];

  React.useEffect(() => {
    let t;
    let c = 0;
    setShown("");
    const tick = () => {
      c++;
      setShown(full.slice(0, c));
      if (c < full.length) t = setTimeout(tick, 18 + Math.random() * 22);
      else t = setTimeout(() => setI((x) => x + 1), interval);
    };
    t = setTimeout(tick, 120);
    return () => clearTimeout(t);
  }, [i]);

  return { shown, idx: i };
}

// Blinking caret
function Caret({ color = "currentColor" }) {
  return (
    <span
      style={{
        display: "inline-block",
        width: "0.55em",
        height: "1em",
        background: color,
        marginLeft: "0.12em",
        verticalAlign: "-0.12em",
        animation: "bazBlink 1s steps(2, end) infinite",
      }}
    />
  );
}

function getMarkedParser() {
  if (typeof window !== "undefined") {
    if (typeof window.marked?.parse === "function") {
      return window.marked.parse.bind(window.marked);
    }

    if (typeof window.marked === "function") {
      return window.marked;
    }
  }

  if (typeof marked !== "undefined" && typeof marked?.parse === "function") {
    return marked.parse.bind(marked);
  }

  return null;
}

function renderChatMarkdown(text) {
  const parser = getMarkedParser();
  if (!parser) return "";

  try {
    return parser(String(text || ""));
  } catch (error) {
    console.error("[Chat] markdown render failed", error);
    return "";
  }
}

// --- Chat co-star. Shared state keeps the hero chat and docked chat in sync.
function ChatPanel({ accent = "#6EE7F4", tone = "dark", compact = false, seed = [] }) {
  const { messages, busy } = useSharedChatState();
  const [val, setVal] = React.useState("");
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    if (!seed.length || sharedChatStore.messages.length > 0) return;
    setSharedChatState((store) => {
      store.messages = [...seed];
    });
  }, [seed]);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, busy]);

  const send = async (text) => {
    const t = (text ?? val).trim();
    if (!t || busy) return;
    setVal("");
    await sendSharedChatMessage(t);
  };

  const isDark = tone === "dark";
  const bubbleU = {
    alignSelf: "flex-end",
    maxWidth: "85%",
    padding: "8px 12px",
    borderRadius: 14,
    background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)",
    color: isDark ? "#EDE9E0" : "#1a1a1a",
    fontSize: compact ? 13 : 14,
    lineHeight: 1.45,
    overflowWrap: "anywhere",
  };
  const bubbleB = {
    alignSelf: "flex-start",
    maxWidth: "92%",
    padding: "8px 12px",
    borderRadius: 14,
    borderTopLeftRadius: 2,
    background: "transparent",
    color: isDark ? "#EDE9E0" : "#1a1a1a",
    fontSize: compact ? 13 : 14,
    lineHeight: 1.5,
    display: "flex",
    alignItems: "flex-start",
    gap: 8,
    overflowWrap: "anywhere",
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", maxHeight: "100%", minHeight: 0, overflow: "hidden" }}>
      <div
        ref={scrollRef}
        style={{
          flex: 1,
          minHeight: 0,
          overflowY: "auto",
          overflowX: "hidden",
          display: "flex",
          flexDirection: "column",
          gap: 6,
          padding: compact ? "8px 2px" : "14px 4px",
          scrollbarWidth: "thin",
        }}
      >
        {messages.length === 0 && (
          <div
            style={{
              color: isDark ? "rgba(237,233,224,0.45)" : "rgba(0,0,0,0.45)",
              fontFamily: "'JetBrains Mono', ui-monospace, monospace",
              fontSize: 12,
              letterSpacing: "0.02em",
              lineHeight: 1.7,
              padding: "2px 2px 8px",
            }}
          >
            Ask me anything about Baz. I know him better than he knows himself.<br />
          </div>
        )}
        {messages.map((m, i) => (
          <div key={i} style={m.who === "u" ? bubbleU : bubbleB}>
            {m.who === "b" && (
              <span
                style={{
                  display: "inline-block",
                  width: 6, height: 6, borderRadius: 999,
                  background: accent,
                  marginTop: 8,
                  flexShrink: 0,
                  boxShadow: `0 0 12px ${accent}`,
                  animation: m.pending ? "bazPulse 1s ease-in-out infinite" : "none",
                }}
              />
            )}
            {(() => {
              const markdownHtml = m.who === "b" && !m.pending ? renderChatMarkdown(m.text) : "";

              return markdownHtml
              ? <div
                  className="chat-md"
                  style={{ flex: 1, minWidth: 0, opacity: 1 }}
                  dangerouslySetInnerHTML={{ __html: markdownHtml }}
                />
              : <span style={{ whiteSpace: "pre-wrap", opacity: m.pending ? 0.78 : 1 }}>{m.text}</span>;
            })()}
          </div>
        ))}
      </div>

      {messages.length === 0 && (
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, padding: "0 2px 10px" }}>
          {SUGGESTED_PROMPTS.slice(0, 3).map((p) => (
            <button
              key={p}
              onClick={() => send(p)}
              style={{
                fontFamily: "'JetBrains Mono', ui-monospace, monospace",
                fontSize: 11,
                padding: "5px 10px",
                borderRadius: 999,
                background: "transparent",
                border: `1px solid ${isDark ? "rgba(237,233,224,0.18)" : "rgba(0,0,0,0.15)"}`,
                color: isDark ? "rgba(237,233,224,0.75)" : "rgba(0,0,0,0.7)",
                cursor: "pointer",
                letterSpacing: "0.01em",
              }}
            >
              {p}
            </button>
          ))}
        </div>
      )}

      <form
        data-chat-form="true"
        onSubmit={(e) => { e.preventDefault(); send(); }}
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          flexShrink: 0,
          padding: "10px 12px",
          borderRadius: 14,
          background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.04)",
          border: `1px solid ${isDark ? "rgba(237,233,224,0.10)" : "rgba(0,0,0,0.08)"}`,
        }}
      >
        <span style={{ color: accent, fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: 13 }}>{">"}</span>
        <input
          value={val}
          onChange={(e) => setVal(e.target.value)}
          placeholder="ask anything…"
          style={{
            flex: 1,
            background: "transparent",
            border: "none",
            outline: "none",
            color: isDark ? "#EDE9E0" : "#1a1a1a",
            fontSize: 14,
            fontFamily: "'Inter', system-ui, sans-serif",
          }}
        />
        <button
          type="submit"
          disabled={busy || !val.trim()}
          style={{
            width: 30, height: 30, borderRadius: 999,
            background: val.trim() ? accent : "transparent",
            color: "#0a0a0a",
            border: val.trim() ? "none" : `1px solid ${isDark ? "rgba(237,233,224,0.18)" : "rgba(0,0,0,0.15)"}`,
            cursor: val.trim() ? "pointer" : "default",
            display: "grid", placeItems: "center",
            transition: "all 140ms ease",
          }}
          aria-label="Send"
        >
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={val.trim() ? "#0a0a0a" : (isDark ? "rgba(237,233,224,0.5)" : "rgba(0,0,0,0.5)")} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
            <path d="M5 12h14M13 6l6 6-6 6" />
          </svg>
        </button>
      </form>
    </div>
  );
}

Object.assign(window, {
  ChatPanel,
  Caret,
  useRotatingStatus,
  NOW_ITEMS,
  FACTS,
  SUGGESTED_PROMPTS,
  copySiteEmailAddress,
  getCurrentSiteTheme,
  openSiteLink,
  sendSharedChatMessage,
  setSiteTheme,
  toggleSiteTheme,
  useSharedChatState,
});
