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

test("project history failures use the visible chat retry state", async () => {
  const app = await readFile(new URL("../../App.jsx", import.meta.url), "utf8");
  assert.match(app, /error=\{chats\.error \|\| chats\.projectError\}/);
});

test("chat setup blockers stay on their related controls instead of filling the chat list", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./ChatsPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./chats.css", import.meta.url), "utf8")
  ]);

  assert.doesNotMatch(panel, /className="setup-warning"/);
  assert.doesNotMatch(css, /\.setup-warning/);
  assert.match(panel, /title=\{createBlockedReason \|\| setupPrompt \|\| t\("chats\.new"\)\}/);
});

test("project cards open a hide-only menu on long press or right click", async () => {
  const app = await readFile(new URL("../../App.jsx", import.meta.url), "utf8");
  const panel = await readFile(new URL("./ChatsPanel.jsx", import.meta.url), "utf8");

  assert.match(app, /onHideProject=\{chats\.hideProject\}/);
  assert.match(panel, /startLongPress\(event, project, "project"\)/);
  assert.match(panel, /const anchorRect = event\.currentTarget\.getBoundingClientRect\(\)/);
  assert.match(panel, /const anchorY = kind === "project" \? anchorRect\.bottom \+ 6 : clientY/);
  assert.match(panel, /const anchorRight = kind === "project" \? anchorRect\.right : undefined/);
  assert.match(panel, /openMenu\(target, anchorY, kind, anchorRight\)/);
  assert.match(panel, /left: menu\.x, right: "auto"/);
  assert.match(panel, /t\("chats\.hideProject"\)/);
});

test.skip("single-screen chat navigation exposes project drill-down and new chat picker", async () => {
  const app = await readFile(new URL("../../App.jsx", import.meta.url), "utf8");
  const panel = await readFile(new URL("./ChatsPanel.jsx", import.meta.url), "utf8");
  const panelCss = await readFile(new URL("./chats.css", import.meta.url), "utf8");
  const conversationPanel = await readFile(new URL("../conversation/ConversationPanel.jsx", import.meta.url), "utf8");
  const selectors = await readFile(new URL("../context/ContextSelectors.jsx", import.meta.url), "utf8");
  const picker = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.doesNotMatch(app, /<nav className="tabs">/);
  assert.match(app, /app\.tab === "threads"/);
  assert.match(app, /app\.tab === "new"/);
  assert.match(app, /<NewChatPanel/);
  assert.doesNotMatch(app, />Changes<\/button>/);
  assert.doesNotMatch(app, />Threads<\/button>/);
  assert.match(app, /<ChangeDiffSheet/);
  assert.match(conversationPanel, /changedFiles/);
  assert.match(panel, /<h2>Projects<\/h2>/);
  assert.match(panel, /project-button/);
  assert.match(panel, /className="project-chat-header"/);
  assert.match(panel, /<section className="panel chats-panel">[\s\S]*?\{\(activeProject \|\| archived\) && !searching && <button type="button" className="screen-back conversation-back"/);
  assert.match(panel, /<BackIcon \/>/);
  assert.doesNotMatch(panel, /project-chat-back/);
  assert.match(panelCss, /\.project-chat-header\{[^}]*position:relative[^}]*place-items:center/s);
  assert.match(panelCss, /\.project-chat-header h2\{[^}]*text-align:center/s);
  assert.match(panelCss, /\.chat-search-bar,\s*\.project-chat-composer\{[^}]*grid-template-columns:minmax\(0,1fr\) 56px/);
  assert.match(panelCss, /\.project-chat-composer\{[^}]*grid-template-columns:minmax\(0,1fr\) 56px/);
  assert.match(panelCss, /\.chat-search-bar,\s*\.project-chat-composer\{[^}]*gap:10px/);
  assert.match(panelCss, /\.chats-panel\{[^}]*--bottom-composer-offset:calc\(18px \+ env\(safe-area-inset-bottom\)\)/);
  assert.match(panelCss, /\.chat-search-bar,\s*\.project-chat-composer\{[^}]*bottom:var\(--bottom-composer-offset\)/);
  assert.match(panelCss, /\.chat-search-bar,\s*\.project-chat-composer\{[^}]*align-items:end/);
  assert.match(panelCss, /\.chat-search-bar,\s*\.project-chat-composer\{[^}]*height:56px/);
  assert.match(panelCss, /\.chat-search-bar,\s*\.project-chat-composer\{[^}]*margin:0/);
  assert.match(panelCss, /@media\(max-width:640px\)\{\.chats-panel\{[^}]*--bottom-composer-offset:calc\(14px \+ env\(safe-area-inset-bottom\)\)/);
  assert.match(panelCss, /\.chat-search-bar \.new-chat-fab\{[^}]*width:56px[^}]*height:56px/);
  assert.match(panelCss, /\.project-chat-composer \.new-chat-fab\{[^}]*width:56px[^}]*height:56px/);
  assert.match(panelCss, /\.project-chat-error\{[^}]*position:absolute[^}]*left:0[^}]*right:66px[^}]*bottom:calc\(100% \+ 6px\)/);
  assert.match(panel, /aria-label=\{archived \? "Back to Chats" : "Back to Projects"\}/);
  assert.doesNotMatch(panel, /setSelectedProject\(project\.key === selectedProject \? "" : project\.key\)/);
  assert.match(panel, /Recent chats/);
  assert.match(panel, /Archived chats/);
  assert.match(panel, /archived \? <header className="project-chat-header"><h2>Archived chats<\/h2><\/header> : <h2>Recent chats<\/h2>/);
  assert.match(panel, /const pinned = archived \? \[\] : threads\.filter\(\(thread\) => thread\.pinned\)/);
  assert.match(panel, /const recent = archived \? threads : recentThreads\(threads\)\.filter\(\(thread\) => !thread\.pinned\)/);
  assert.match(panel, /pinned\.length > 0 && <section className="thread-section pinned-threads"><h2>Pinned<\/h2>/);
  assert.ok(panel.indexOf('<h2>Pinned</h2>') < panel.indexOf('Recent chats'));
  assert.doesNotMatch(panel, /item\.pinned && <span className="thread-pin"/);
  assert.doesNotMatch(panelCss, /\.thread-pin\{/);
  assert.match(panel, /Loading chats/);
  assert.match(panel, /No chats yet\./);
  assert.doesNotMatch(selectors, /New Chat/);
  assert.match(panel, /className="new-chat-fab"/);
  assert.match(panel, /aria-label="New Chat"/);
  assert.doesNotMatch(panel, />New<\/button>/);
  assert.match(panel, /className="chat-search-input"/);
  assert.match(panel, /placeholder="Search chats"/);
  assert.match(panel, /searchQuery\.trim\(\)/);
  assert.match(panel, /Search results/);
  assert.match(panel, /className="chat-search-input"[\s\S]*?placeholder="Message this project"/);
  assert.match(panel, /onCreateProjectChat\(activeProject\.workspaceId, projectMessage\)/);
  assert.match(panel, /onCreateEmptyProjectChat\(activeProject\.workspaceId\)/);
  assert.match(panel, /onProjectChange\(project\.workspaceId\)/);
  assert.match(panel, /const rows = \(items, projectWorkspaceId = ""\) =>/);
  assert.match(panel, /onOpen\(\{ \.\.\.item, workspaceId: item\.workspaceId \|\| projectWorkspaceId \}\)/);
  assert.match(panel, /rows\(activeProject\.threads, activeProject\.workspaceId\)/);
  assert.doesNotMatch(panel, /!projectMessage\.trim\(\) \|\| projectMessagePending/);
  assert.match(panel, /projectMessage\.trim\(\)/);
  assert.match(panel, /project-chat-error/);
  assert.doesNotMatch(panel, /<span aria-hidden="true">↑<\/span>/);
  assert.match(panel, /aria-label="Send and create chat"[^>]*>[\s\S]*?className="new-chat-icon"/);
  assert.match(panel, /new-chat-icon/);
  assert.match(picker, /Choose a project folder/);
  assert.match(picker, /Create or select a folder on this PC for Codex to work in\./);
  assert.match(picker, /Projects root lists only direct child folders; workspaces are the folders already allowed for Codex runs\./);
  assert.match(picker, /No direct child project folders found\./);
  assert.doesNotMatch(picker, /project_list_failed/);
  assert.match(picker, /placeholder="Search projects"/);
  assert.match(picker, />New project</);
  assert.match(picker, /onSelectProject/);
  assert.match(picker, /create: true/);
  assert.doesNotMatch(picker, /project\.workspaceId === selectedWorkspaceId/);
  assert.doesNotMatch(app, /selectedWorkspaceId=\{app\.workspaceId\}/);
  assert.match(picker, /onBack/);
  assert.match(app, /registerProjectWorkspace/);
  assert.match(app, /app\.replaceWorkspaces/);
  assert.match(app, /canCreate=\{!newChatBlockedReason\}/);
  assert.match(app, /setupPrompt=\{projectSetupMessage\}/);
  assert.match(app, /function handleProjectChatCreate\(workspaceId, message\)/);
  assert.match(app, /app\.selectWorkspace\(workspaceId\);/);
  assert.match(app, /onCreateProjectChat=\{handleProjectChatCreate\}/);
  assert.match(app, /onCreateEmptyProjectChat=\{handleProjectChatCreate\}/);
  assert.match(app, /onProjectChange=\{app\.selectWorkspace\}/);
});

test("new chat project selection registers the workspace without creating an empty chat", async () => {
  const [app, controller] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./useChats.js", import.meta.url), "utf8")
  ]);

  assert.match(controller, /return true/);
  assert.match(controller, /return false/);
  assert.match(controller, /onTransitionFailed\(requestId, setupErrorMessage\(error, "chat_create_failed"\)\)/);
  assert.match(app, /async function handleSelectProject\(\{ name, create \}\)/);
  assert.match(app, /app\.replaceWorkspaces\(result\.workspaces, result\.workspace\.id\);/);
  assert.match(app, /return result\.workspace\.id;/);
  assert.doesNotMatch(app, /const created = await chats\.create\(result\.workspace\.id, requestId\)/);
  assert.match(app, /onCreateWithMessage=\{chats\.createWithMessage\}/);
});

test("chat list actions use fixed safe failure codes", async () => {
  const controller = await readFile(new URL("./useChats.js", import.meta.url), "utf8");

  assert.doesNotMatch(controller, /error\.message/);
  assert.match(controller, /"chats_unavailable"/);
  assert.match(controller, /"chat_archive_failed"/);
  assert.match(controller, /"chat_rename_failed"/);
  assert.match(controller, /"chat_pin_failed"/);
});

test("archived chats reuse their last first page while refreshing it", async () => {
  const controller = await readFile(new URL("./useChats.js", import.meta.url), "utf8");

  assert.match(controller, /const archivedCacheRef = useRef\(null\)/);
  assert.match(controller, /archivedCacheRef\.current = result/);
  assert.match(controller, /function changeArchived\(nextArchived\)/);
  assert.match(controller, /dispatch\(\{ type: "page\.loaded", \.\.\.archivedCacheRef\.current, queryScope: "archived" \}\)/);
  assert.match(controller, /setArchived: changeArchived/);
});

test("chat navigation uses localized labels and the dedicated new-chat overlay", async () => {
  const [app, panel, picker] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./ChatsPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8")
  ]);

  assert.match(app, /app\.newChatOpen && app\.tab !== "settings" && <div[\s\S]*<NewChatPanel/);
  assert.match(app, /app\.startNewChat/);
  assert.match(panel, /t\("chats\.projects"\)/);
  assert.match(panel, /t\("chats\.new"\)/);
  assert.match(panel, /t\("chats\.search"\)/);
  assert.match(picker, /t\("newChat\.search"\)/);
  assert.match(picker, /t\("newChat\.newProject"\)/);
});
