import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";

test("app renders the destination chat shell while chat content opens in the background", async () => {
  const [app, conversationPanel, conversationCss, startup] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("../conversation/ConversationPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("../conversation/conversation.css", import.meta.url), "utf8"),
    readFile(new URL("../../app/StartupScreen.jsx", import.meta.url), "utf8")
  ]);

  assert.doesNotMatch(app, /ChatTransitionPanel/);
  assert.match(app, /const visibleSession = transitionSession \?\? displaySession/);
  assert.match(app, /projectConversation\(app\.chatTransition\.target\.optimisticEvents \?\? \[\]\)/);
  assert.match(app, /<ConversationAccountBar session=\{visibleSession\}/);
  assert.match(app, /<ConversationPanel session=\{visibleSession\} title=\{visibleConversationTitle\} conversation=\{visibleConversation\}/);
  assert.match(app, /transition=\{app\.chatTransition\}/);
  assert.match(app, /onTransitionRetry=\{\(\) => retryTransitionRef\.current\(\)\}/);
  assert.match(app, /if \(app\.chatTransition\) app\.cancelChatTransition\(\)/);
  assert.match(conversationPanel, /transition,/);
  assert.match(conversationPanel, /const transitionPending = transition\?\.status === "loading"/);
  assert.match(conversationPanel, /const existingChatTransitionPending = transitionPending && Boolean\(transition\?\.target\?\.codexThreadId\)/);
  assert.match(conversationPanel, /const newChatTransitionPending = transitionPending && !transition\?\.target\?\.codexThreadId/);
  assert.match(conversationPanel, /const emptyNewChatTransitionPending = newChatTransitionPending && conversation\.length === 0/);
  assert.match(conversationPanel, /transitionPending && <div className="conversation-opening-status"/);
  assert.match(conversationPanel, /emptyNewChatTransitionPending && <div className="conversation-new-chat-loading"[^>]*><div className="startup-mark" aria-hidden="true"><span \/><\/div><\/div>/);
  assert.match(conversationPanel, /existingChatTransitionPending && <div className="conversation-loading" role="status" aria-busy="true" aria-label=\{t\("transition\.opening"\)\}><span className="chat-transition-spinner" aria-hidden="true"><\/span><\/div>/);
  assert.match(conversationPanel, /newChatTransitionPending && conversation\.length > 0/);
  assert.match(conversationPanel, /transition\?\.status === "failed" && <div className="conversation-open-error" role="alert">/);
  assert.match(conversationPanel, /onTransitionRetry/);
  assert.doesNotMatch(conversationPanel, /newChatTransitionPending[\s\S]{0,300}chat-transition-spinner/);
  assert.match(conversationCss, /\.conversation-new-chat-loading\{[^}]*display:grid[^}]*place-items:center[^}]*height:100%/);
  assert.match(startup, /<div className="startup-mark" aria-hidden="true"><span \/><\/div>/);
});

test("light mode keeps the new chat loading mark visibly spinning", async () => {
  const appCss = await readFile(new URL("../../app/app.css", import.meta.url), "utf8");

  assert.match(appCss, /\.startup-mark>span\{[^}]*animation:chat-transition-spin \.9s linear infinite/);
  assert.match(appCss, /html\[data-theme="light"\] \.app-shell \.startup-mark>span\{[^}]*border-color:#d0d7de!important[^}]*border-top-color:#0969da!important/);
});

test("async navigation starts before requests and carries request identity", async () => {
  const [app, chats] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./useChats.js", import.meta.url), "utf8")
  ]);

  assert.match(chats, /onTransitionStarted/);
  assert.match(chats, /const requestId = onTransitionStarted/);
  assert.match(chats, /transitionTarget\(thread, accountId, workspaceId\)/);
  assert.match(chats, /const targetWorkspaceId = thread\.id \? \(thread\.workspaceId \|\| ""\) : \(workspaceId \|\| ""\)/);
  assert.match(chats, /projectLabel: thread\.projectLabel \|\| ""/);
  assert.match(app, /projectLabel: app\.chatTransition\.target\?\.projectLabel \|\| ""/);
  assert.match(chats, /onSessionOpened\(result, requestId\)/);
  assert.match(chats, /onTransitionFailed\(requestId, setupErrorMessage\(error, "chat_open_failed"\)\)/);
  const createWithMessageStart = chats.indexOf("async function createWithMessage");
  const createWithMessageBody = chats.slice(createWithMessageStart, chats.indexOf("async function archive"));
  assert.ok(createWithMessageStart >= 0);
  assert.ok(createWithMessageBody.indexOf("const requestId = onTransitionStarted") < createWithMessageBody.indexOf("createChat(accountId, selectedWorkspaceId, attempt.clientSessionId)"));
  assert.match(createWithMessageBody, /const optimisticEvent = createNewChatMessagePreview\(text, files\)/);
  assert.match(createWithMessageBody, /transitionTarget\(\{\}, accountId, selectedWorkspaceId, \[optimisticEvent\]\)/);
  assert.match(createWithMessageBody, /Promise\.allSettled/);
  assert.match(createWithMessageBody, /filter\(\(result\) => result\.status === "fulfilled"\)/);
  assert.match(createWithMessageBody, /uploadFailureCount = uploadResults\.filter\(\(result\) => result\.status === "rejected"\)\.length/);
  assert.match(createWithMessageBody, /attachmentUploadFailureCount: uploadFailureCount/);
  assert.match(createWithMessageBody, /uploadChatAttachment/);
  assert.match(createWithMessageBody, /attachments: uploadedAttachments/);
  assert.match(createWithMessageBody, /const acceptedOptimisticEvent = reconcileNewChatMessagePreview\(optimisticEvent, uploadedAttachments\.length\)/);
  assert.ok(createWithMessageBody.indexOf("const requestId = onTransitionStarted") < createWithMessageBody.indexOf("sendInitialChatMessage"));
  assert.match(createWithMessageBody, /createNewChatAttempt/);
  assert.match(createWithMessageBody, /attempt\.created/);
  assert.match(createWithMessageBody, /attempt\.uploadedAttachments/);
  assert.match(createWithMessageBody, /clientMessageId: attempt\.clientMessageId/);
  assert.doesNotMatch(chats, /onOwnerSelected/);
  assert.match(app, /app\.isCurrentChatTransition\(requestId\)/);
  assert.match(app, /onCreateWithMessage=\{chats\.createWithMessage\}/);
  assert.match(app, /useBackLayer\(screenLayerActive/);
  assert.match(app, /if \(app\.tab === "chat"\) chats\.reload\(\)/);
  assert.match(app, /onBack=\{closeScreen\}/);
});

test("new chat opens immediately and refreshes the slow thread list in the background", async () => {
  const chats = await readFile(new URL("./useChats.js", import.meta.url), "utf8");
  const createStart = chats.indexOf("async function create(");
  const createBody = chats.slice(createStart, chats.indexOf("async function createWithMessage"));
  const createWithMessageStart = chats.indexOf("async function createWithMessage");
  const createWithMessageBody = chats.slice(createWithMessageStart, chats.indexOf("async function archive"));

  assert.ok(createStart >= 0);
  assert.match(createBody, /onSessionOpened\(\{ \.\.\.result, events: \[\] \}, requestId, composerOptions\);/);
  assert.match(createBody, /void load\(undefined, activeSearch\);/);
  assert.doesNotMatch(createBody, /await load\(undefined, activeSearch\)/);
  assert.match(createWithMessageBody, /const openedSession = sent\.session \?\? created\.session/);
  assert.match(createWithMessageBody, /onSessionOpened\(\{[\s\S]*events: \[acceptedOptimisticEvent\][\s\S]*\}, requestId, payload\.options\);/);
  assert.match(createWithMessageBody, /void load\(undefined, activeSearch\);/);
  assert.doesNotMatch(createWithMessageBody, /await load\(undefined, activeSearch\)/);
});

test("chat creation verifies the returned session workspace before sending", async () => {
  const controller = await readFile(new URL("./useChats.js", import.meta.url), "utf8");
  assert.match(controller, /assertSessionWorkspace\(created, selectedWorkspaceId\)/);
});
