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

test("new chat receives recent used projects and labels the compact list", async () => {
  const [app, panel, css, chats] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8"),
    readFile(new URL("./useChats.js", import.meta.url), "utf8")
  ]);

  assert.match(app, /recentProjectFolders\(chats\.recentProjectThreads, app\.workspaces, chats\.hiddenProjects\)/);
  assert.match(app, /recentProjects=\{recentProjects\}/);
  assert.match(panel, /recentProjects = \[\]/);
  assert.match(panel, /className="project-picker-section-title">\{t\("newChat\.recentProjects"\)\}/);
  assert.doesNotMatch(panel, /listProjectFolders/);
  assert.match(css, /\.project-picker-section-title\{[^}]*font-size:12px/);
  assert.match(chats, /searching: Boolean\(search\)/);
});

test("project history reuses the recent page and publishes one completed refresh", async () => {
  const [chats, projectHistory] = await Promise.all([
    readFile(new URL("./useChats.js", import.meta.url), "utf8"),
    readFile(new URL("./project-history.js", import.meta.url), "utf8")
  ]);

  assert.match(chats, /void loadProjects\(result\)/);
  assert.match(chats, /collectProjectHistory\(seed, listProjectChats/);
  assert.match(chats, /dispatch\(\{ type: "projects\.loaded", \.\.\.result \}\)/);
  assert.match(projectHistory, /page = await loadPage\(cursor\)/);
  assert.match(projectHistory, /threads = mergeThreadPage\(threads, page\.threads \?\? \[\]\)/);
  assert.match(projectHistory, /setTimeout\(resolve, 0\)/);
});

test("large project histories use bounded, virtualizable main previews", async () => {
  const panel = await readFile(new URL("./ChatsPanel.jsx", import.meta.url), "utf8");

  assert.match(panel, /<VirtualThreadRows/);
  assert.match(panel, /scrollRootRef=\{threadListRef\}/);
  assert.match(panel, /visibleProjectThreads\(project\.threads, projectRowLimits\[project\.key\]\)/);
  assert.match(panel, /nextProjectThreadLimit\(current\[projectKey\], total\)/);
  assert.match(panel, /rows\(activeProject\.threads, activeProject\.workspaceId\)/);
  assert.match(panel, /setSelectedProject/);
});

test("new chat picker uses shared circular back control and offers no-project chat", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(panel, /className="screen-back conversation-back"/);
  assert.match(panel, /<BackIcon \/>/);
  assert.match(panel, /onCreateWithoutProject/);
  assert.match(panel, /newChat\.withoutProject/);
  assert.match(panel, /project-picker-icon/);
  assert.match(css, /\.project-picker-list button,\.project-picker-actions>button,\.project-picker-without-project\{/);
});

test("new chat opens on a centered prompt with the last selected project", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(panel, /selectedWorkspace/);
  assert.match(panel, /className="project-picker-trigger"/);
  assert.match(panel, /aria-expanded=\{pickerOpen\}/);
  assert.match(panel, /newChat\.promptBefore/);
  assert.match(panel, /className="new-chat-prompt-question"/);
  assert.doesNotMatch(panel, /newChat\.promptAfter/);
  assert.match(css, /\.project-picker-shell\{[^}]*place-items:center/);
  assert.match(css, /\.project-picker-trigger\{[^}]*border-bottom:/);
  assert.match(css, /\.project-picker\{[^}]*position:absolute/);
});

test("new chat keeps the prompt above a restored message composer and sends to the last conversation project", async () => {
  const [app, panel, css] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(app, /const lastConversationWorkspaceId = app\.workspaceId \|\| visibleSession\?\.workspaceId \|\| chats\.threads\.find\(\(thread\) => thread\.workspaceId\)\?\.workspaceId/);
  assert.match(app, /const lastConversationWorkspace = app\.workspaces\.find\(\(workspace\) => workspace\.id === lastConversationWorkspaceId\) \?\? app\.selectedWorkspace \?\? app\.workspaces\[0\]/);
  assert.match(app, /selectedWorkspace=\{lastConversationWorkspace\}/);
  assert.match(app, /onCreateWithMessage=\{chats\.createWithMessage\}/);
  assert.match(panel, /className="new-chat-composer composer-dock" onSubmit=\{submitMessage\}/);
  assert.match(panel, /<div className="composer">/);
  assert.match(panel, /className="composer-input"/);
  assert.match(panel, /className="composer-voice"/);
  assert.match(panel, /newChat\.message/);
  assert.match(panel, /await onCreateWithMessage\([\s\S]*selectedWorkspaceId,[\s\S]*message\.trim\(\),[\s\S]*buildComposerPayload/);
  assert.match(css, /\.project-picker-copy\{[^}]*align-self:end[^}]*margin-bottom:144px/);
  assert.doesNotMatch(css, /\.new-chat-composer\{[^}]*(?:position|left|right|bottom):/);
  assert.doesNotMatch(css, /\.new-chat-composer \.new-chat-fab/);
  assert.match(css, /\.new-chat-panel\{[^}]*grid-template-rows:minmax\(0,1fr\)/);
});

test("new chat uses an isolated composer state with the shared controls", async () => {
  const [app, panel, css] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(app, /capabilities=\{conversation\.capabilities\}/);
  assert.doesNotMatch(app, /<NewChatPanel[^>]*composerOptions=/);
  assert.match(panel, /useState\(\(\) => loadComposerOptions\(globalThis\.localStorage\)\)/);
  assert.match(panel, /onOption=\{setComposerOption\}/);
  assert.match(app, /conversation\.replace\(result\.session\.id, result\.events \?\? \[\], initialOptions\)/);
  assert.match(app, /selectedSkills=\{conversation\.selectedSkills\}/);
  assert.match(panel, /<ComposerModelMenu models=\{models\}/);
  assert.match(panel, /<ComposerPermissionMenu permissions=\{capabilities\?\.permissions \?\? \[\]\}/);
  assert.match(panel, /<ComposerToolsMenu busy=\{messagePending\}/);
  assert.match(panel, /buildComposerPayload\(\{ message: message\.trim\(\), options: composerOptions, selectedSkills \}\)/);
  assert.match(panel, /className=\{`composer-row \$\{listening \? "listening" : ""\}`\}/);
  assert.doesNotMatch(css, /\.new-chat-composer-row/);
});

test("new chat validates the recent model against the active account capabilities", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.match(panel, /normalizeComposerModel/);
  assert.match(panel, /normalizeComposerModel\(current, models\)/);
});

test("new chat accepts image attachments and shows removable previews before sending", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(panel, /const \[attachments, setAttachments\] = useState\(\[\]\)/);
  assert.match(panel, /<ComposerToolsMenu[^>]*uploadDisabled=\{messagePending\}/);
  assert.match(panel, /ref=\{fileRef\}[^>]*type="file"[^>]*accept="image\/\*"[^>]*multiple/);
  assert.match(panel, /event\.target\.value = ""/);
  assert.match(panel, /className="composer-attachments"/);
  assert.match(panel, /URL\.createObjectURL\(file\)/);
  assert.match(panel, /URL\.revokeObjectURL/);
  assert.match(panel, /onCreateWithMessage\([\s\S]*selectedWorkspaceId,[\s\S]*message\.trim\(\),[\s\S]*attachments\.map\(\(item\) => item\.file\)/);
  assert.match(panel, /message\.trim\(\)\.length > 0 \|\| attachments\.length > 0/);
  assert.doesNotMatch(css, /\.new-chat-attachments/);
});

test("new chat composer matches existing chat voice and textarea behavior", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.match(panel, /createVoiceRecognition/);
  assert.match(panel, /supportsVoiceInput/);
  assert.match(panel, /appendVoiceTranscript/);
  assert.match(panel, /buildVoiceWaveBars/);
  assert.match(panel, /resizeComposerInput/);
  assert.match(panel, /const listening = voiceStatus === "listening"/);
  assert.match(panel, /const showVoiceButton = voiceSupported && !hasComposerContent && !listening/);
  assert.match(panel, /aria-label=\{t\("conversation\.startVoice"\)\}/);
  assert.match(panel, /aria-label=\{t\("conversation\.stopVoice"\)\}/);
  assert.match(panel, /<VoiceWaveform bars=\{voiceBars\} \/>/);
  assert.match(panel, /finishVoice\("send"\)/);
  assert.match(panel, /voiceSubmitPendingRef\.current/);
  assert.match(panel, /<textarea className="composer-input"/);
  assert.match(panel, /shouldSubmitComposerKey\(event, \{ compact: usesCompactComposerKeyboard\(\) \}\)/);
  assert.doesNotMatch(panel, /event\.key === "Enter" && !event\.shiftKey/);
});

test("project choices stay hidden until the project name is pressed", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(panel, /const \[pickerOpen, setPickerOpen\] = useState\(false\)/);
  assert.match(panel, /\{pickerOpen && <div className="project-picker"/);
  assert.match(panel, /useBackLayer\(pickerOpen, \(\) => setPickerOpen\(false\), "project-picker"/);
  assert.match(panel, /function dismissPickerOutside\(event\)/);
  assert.match(panel, /event\.target\.closest\("\.project-picker, \.project-picker-trigger"\)/);
  assert.match(css, /\.new-chat-panel:has\(\.project-picker\)\{overflow:hidden\}/);
});

test("new chat composer matches the existing chat bar height", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(panel, /<\/div>\r?\n\s*<form className="new-chat-composer composer-dock"/);
  assert.match(panel, /<form className="new-chat-composer composer-dock"[^>]*>\r?\n\s*<div className="composer">/);
  assert.match(panel, /className="composer-input"/);
  assert.match(panel, /className="composer-voice"/);
  assert.match(panel, /<(?:MicrophoneIcon|SendIcon|StopIcon)/);
  assert.match(panel, /className="composer-send"/);
  assert.doesNotMatch(panel, /new-chat-fab/);
  assert.doesNotMatch(css, /\.new-chat-composer\{[^}]*(?:position|left|right|bottom):/);
});

test("new chat screen uses the same square panel edges as chats", async () => {
  const css = await readFile(new URL("./project-picker.css", import.meta.url), "utf8");

  assert.match(css, /\.new-chat-panel\{[^}]*border:0[^}]*border-radius:0[^}]*box-shadow:none/);
});

test("project picker uses a compact menu layout", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.doesNotMatch(panel, /className="project-picker-label"/);
  assert.match(css, /\.project-picker\{[^}]*padding:8px/);
  assert.match(css, /\.project-picker-list\{[^}]*max-height:114px[^}]*overflow:auto/);
  assert.match(css, /@media\(max-width:640px\)[\s\S]*\.project-picker-list\{max-height:144px\}/);
  assert.match(css, /\.project-picker-list button,\.project-picker-actions>button,\.project-picker-without-project\{[^}]*min-height:38px/);
});

test("recent projects are separated from top actions without loose folder gaps", async () => {
  const css = await readFile(new URL("./project-picker.css", import.meta.url), "utf8");

  assert.match(css, /\.project-picker-section-title\{[^}]*margin:12px 8px 3px/);
  assert.match(css, /\.project-picker-list\{[^}]*gap:0/);
});

test("project title and top actions precede recent projects while search stays at the bottom", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  const pickerTitle = panel.indexOf('className="project-picker-title"');
  const withoutProject = panel.indexOf('className="project-picker-without-project"');
  const newProject = panel.indexOf('className="project-picker-actions project-picker-new-project"');
  const recentTitle = panel.indexOf('className="project-picker-section-title"');
  const projectList = panel.indexOf('className="project-picker-list"');
  const projectSearch = panel.indexOf('className="project-search"');
  assert.ok(pickerTitle < withoutProject);
  assert.ok(withoutProject < recentTitle);
  assert.ok(withoutProject < newProject);
  assert.ok(newProject < recentTitle);
  assert.ok(recentTitle < projectList);
  assert.ok(projectList < projectSearch);
  assert.match(panel, /className="project-picker-title">\{t\("newChat\.chooseProject"\)\}/);
});

test("top project actions do not render divider lines", async () => {
  const css = await readFile(new URL("./project-picker.css", import.meta.url), "utf8");

  assert.doesNotMatch(css, /\.project-picker-without-project\{[^}]*border-bottom/);
  assert.match(css, /\.project-picker-actions\{[^}]*border-top:0/);
});

test("new project uses one button with create-or-existing choices", async () => {
  const [panel, css] = await Promise.all([
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(panel, /const \[projectMenuOpen, setProjectMenuOpen\] = useState\(false\)/);
  assert.match(panel, /className="project-picker-actions project-picker-new-project"/);
  assert.match(panel, /projectPopoverPlacement\(event\.currentTarget\.getBoundingClientRect\(\), window\.innerHeight\)/);
  assert.match(panel, /project-create-menu project-create-popover project-create-popover--\$\{projectMenuPlacement\}/);
  assert.match(panel, /new-project-form project-create-popover project-create-popover--\$\{projectMenuPlacement\}/);
  assert.match(panel, /t\("newChat\.createFolder"\)/);
  assert.match(panel, /t\("newChat\.browseFolders"\)/);
  assert.match(panel, /useBackLayer\(projectMenuOpen, \(\) => setProjectMenuOpen\(false\), "new-project-menu"/);
  assert.doesNotMatch(panel, /className="project-picker-browse"/);
  assert.match(css, /\.project-create-popover\{[^}]*position:absolute/);
  assert.match(css, /\.project-create-popover--above\{[^}]*bottom:calc\(100% \+ 4px\)/);
  assert.match(css, /\.project-create-popover--below\{[^}]*top:calc\(100% \+ 4px\)/);
});

test("created and browsed projects remain authoritative after the parent workspace list changes", async () => {
  const [app, panel] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8")
  ]);

  assert.match(panel, /selectionTouchedRef\.current = true/);
  assert.match(panel, /if \(selectionTouchedRef\.current \|\| !selectedWorkspace\?\.id\) return/);
  assert.match(panel, /setProjects\(\(current\) => upsertProjectFolder\(current, \{ name: trimmed, workspaceId \}\)\)/);
  assert.match(panel, /setSearch\(""\)/);
  assert.match(panel, /aria-current=\{project\.workspaceId === selectedWorkspaceId \? "true" : undefined\}/);
  assert.doesNotMatch(app, /throw new Error\(projectOperationError\(error\)\)/);
});

test("folder browsing ignores stale responses and can create a child folder", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.match(panel, /browseRequestRef/);
  assert.match(panel, /requestId !== browseRequestRef\.current/);
  assert.match(panel, /createProjectFolder/);
  assert.match(panel, /onSelectFolder\(result\.currentId\)/);
  assert.match(panel, /newChat\.createFolderHere/);
  assert.match(panel, /newChat\.confirmCreateFolder/);
});

test("closing the picker resets nested project creation screens", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.match(panel, /if \(pickerOpen\) return;/);
  assert.match(panel, /setProjectMenuOpen\(false\)/);
  assert.match(panel, /setNewProject\(false\)/);
  assert.match(panel, /setFolderBrowser\(null\)/);
});

test("new chat picker is a dismissible popup with durable form state", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.match(panel, /className="new-chat-overlay"/);
  assert.match(panel, /role="dialog"/);
  assert.match(panel, /loadNewChatDraft/);
  assert.match(panel, /saveNewChatDraft/);
  assert.match(panel, /clearNewChatDraft/);
});

test("new chat picker renders vector icons instead of text glyph controls", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");

  assert.doesNotMatch(panel, />＋</);
  assert.doesNotMatch(panel, />×</);
  assert.doesNotMatch(panel, />›</);
  assert.doesNotMatch(panel, /\? "✓"/);
  assert.match(panel, /className="project-picker-action-svg"/);
  assert.match(panel, /project-picker-chevron-svg/);
});

test("new chat picker can browse and register a PC folder", async () => {
  const panel = await readFile(new URL("./NewChatPanel.jsx", import.meta.url), "utf8");
  assert.match(panel, /browseProjectFolders/);
  assert.match(panel, /onSelectFolder/);
  assert.match(panel, /newChat\.newProject/);
  assert.match(panel, /folder-browser-list/);
  assert.match(panel, /folder\.id/);
});

test("opening header popups dismisses the new chat popup", async () => {
  const [app, contextBar, notifications] = await Promise.all([
    readFile(new URL("../../App.jsx", import.meta.url), "utf8"),
    readFile(new URL("../context/ContextBar.jsx", import.meta.url), "utf8"),
    readFile(new URL("../notifications/NotificationCenter.jsx", import.meta.url), "utf8")
  ]);

  assert.match(app, /function closePopupBefore\(afterClose\)/);
  assert.match(app, /onPopupOpen=\{closePopupBefore\}/);
  assert.match(contextBar, /function toggleAccountMenu\(\)/);
  assert.match(contextBar, /if \(onPopupOpen\) onPopupOpen\(openAccountMenu\)/);
  assert.match(contextBar, /<NotificationCenter[\s\S]*onPopupOpen=\{onPopupOpen\}/);
  assert.match(notifications, /function toggleNotificationCenter\(\)/);
  assert.match(notifications, /if \(onPopupOpen\) onPopupOpen\(openNotificationCenter\)/);
});

test("account and notification popups stack above the new chat screen", async () => {
  const [contextCss, pickerCss] = await Promise.all([
    readFile(new URL("../context/context.css", import.meta.url), "utf8"),
    readFile(new URL("./project-picker.css", import.meta.url), "utf8")
  ]);

  assert.match(contextCss, /\.context-bar\{z-index:40\}/);
  assert.match(pickerCss, /\.new-chat-overlay\{[^}]*z-index:10/);
});
