import test from "node:test";
import assert from "node:assert/strict";
import * as chatsState from "./chats-state.js";

const { assertSessionWorkspace, initialChatsState, reduceChatsState } = chatsState;

test("new sessions must echo the explicitly selected workspace", () => {
  assert.doesNotThrow(() => assertSessionWorkspace({ session: { workspaceId: "crumbly-id" } }, "crumbly-id"));
  assert.throws(
    () => assertSessionWorkspace({ session: { workspaceId: "old-project" } }, "crumbly-id"),
    (error) => error.message === "workspace_mismatch"
  );
});

test("account change keeps unified rows", () => {
  const state = reduceChatsState({ ...initialChatsState, threads: [{ id: "old" }] }, { type: "account.changed", accountId: "b" });
  assert.deepEqual(state.threads, [{ id: "old" }]);
});

test("page failure preserves rows", () => {
  const threads = [{ id: "kept" }];
  const state = reduceChatsState({ ...initialChatsState, threads }, { type: "page.failed", error: "offline" });
  assert.equal(state.threads, threads);
  assert.equal(state.error, "offline");
});

test("thread actions update or remove only the matching owner row", () => {
  const initial = { ...initialChatsState, threads: [
    { ownerId: "a", id: "same", name: "A" },
    { ownerId: "b", id: "same", name: "B" }
  ] };
  const renamed = reduceChatsState(initial, { type: "thread.renamed", ownerId: "b", threadId: "same", name: "New B" });
  assert.deepEqual(renamed.threads.map((item) => item.name), ["A", "New B"]);
  const pinned = reduceChatsState(renamed, { type: "thread.pinned", ownerId: "b", threadId: "same", pinned: true });
  assert.deepEqual(pinned.threads.map((item) => item.pinned), [undefined, true]);
  const archived = reduceChatsState(pinned, { type: "thread.archived", ownerId: "b", threadId: "same" });
  assert.deepEqual(archived.threads, [{ ownerId: "a", id: "same", name: "A" }]);
});

test("archive start removes only the owned chat immediately", () => {
  const state = reduceChatsState({
    ...initialChatsState,
    threads: [
      { id: "same", ownerId: "a" },
      { id: "same", ownerId: "b" }
    ]
  }, { type: "archive.started", thread: { id: "same", ownerId: "a" } });

  assert.deepEqual(state.threads, [{ id: "same", ownerId: "b" }]);
});

test("archive failure restores the chat and exposes the error", () => {
  const thread = { id: "thread-1", ownerId: "a", updatedAt: "2026-01-01T00:00:00.000Z" };
  const state = reduceChatsState({ ...initialChatsState, threads: [] }, {
    type: "archive.failed",
    thread,
    error: "offline"
  });

  assert.deepEqual(state.threads, [thread]);
  assert.equal(state.error, "offline");
});

test("delete start removes archived chat and failure restores it", () => {
  const thread = { id: "thread-1", ownerId: "a", updatedAt: "2026-01-01T00:00:00.000Z" };
  const removed = reduceChatsState({ ...initialChatsState, threads: [thread] }, {
    type: "delete.started",
    thread
  });
  const restored = reduceChatsState(removed, {
    type: "delete.failed",
    thread,
    error: "chat_delete_failed"
  });

  assert.deepEqual(removed.threads, []);
  assert.deepEqual(restored.threads, [thread]);
  assert.equal(restored.error, "chat_delete_failed");
});

test("switching from recent to archived keeps the independent project history", () => {
  const recent = {
    ...initialChatsState,
    threads: [{ ownerId: "a", id: "recent" }],
    projectThreads: [{ ownerId: "a", id: "recent" }],
    queryScope: "recent"
  };

  const loading = reduceChatsState(recent, { type: "page.loading", queryScope: "archived" });

  assert.deepEqual(loading.threads, []);
  assert.deepEqual(loading.projectThreads, [{ ownerId: "a", id: "recent" }]);
  assert.equal(loading.queryScope, "archived");
  assert.equal(loading.loading, true);
});

test("the first unsearched recent page becomes the fast recent-project source", () => {
  const recentThreads = [
    { ownerId: "a", id: "new", workspaceId: "alpha" },
    { ownerId: "a", id: "old", workspaceId: "beta" }
  ];
  const recent = reduceChatsState(initialChatsState, {
    type: "page.loaded",
    threads: recentThreads,
    queryScope: "recent",
    searching: false
  });
  const searched = reduceChatsState(recent, {
    type: "page.loaded",
    threads: [{ ownerId: "a", id: "result", workspaceId: "other" }],
    queryScope: "recent",
    searching: true
  });

  assert.deepEqual(recent.recentProjectThreads, recentThreads);
  assert.deepEqual(searched.recentProjectThreads, recentThreads);
});

test("background refresh keeps the current rows out of a loading state", () => {
  const current = {
    ...initialChatsState,
    threads: [{ ownerId: "a", id: "visible" }],
    queryScope: "recent"
  };

  const refreshing = reduceChatsState(current, {
    type: "page.loading",
    queryScope: "recent",
    silent: true
  });

  assert.equal(refreshing, current);
});

test("chat operation errors expose only allowlisted fixed codes", () => {
  assert.equal(typeof chatsState.chatOperationError, "function");
  assert.equal(chatsState.chatOperationError(new Error("chat_rename_failed"), "chats_unavailable"), "chat_rename_failed");
  assert.equal(chatsState.chatOperationError(new Error("chat_delete_failed"), "chats_unavailable"), "chat_delete_failed");
  assert.equal(chatsState.chatOperationError(new Error("C:\\Users\\private\\auth.json"), "chats_unavailable"), "chats_unavailable");
});

test("partial chat list failures preserve rows and use a fixed error", () => {
  const threads = [{ id: "kept", ownerId: "a" }];
  const state = reduceChatsState(initialChatsState, {
    type: "page.loaded",
    threads,
    errors: [{ ownerLabel: "Private", error: "C:\\Users\\private\\auth.json" }]
  });

  assert.deepEqual(state.threads, threads);
  assert.equal(state.error, "chats_partial_unavailable");
});

test("project history loads separately from recent rows", () => {
  const recent = reduceChatsState(initialChatsState, {
    type: "page.loaded",
    threads: [{ id: "recent", ownerId: "a" }]
  });
  const state = reduceChatsState(recent, {
    type: "projects.loaded",
    threads: [{ id: "recent", ownerId: "a" }, { id: "older", ownerId: "a" }],
    errors: [],
    hiddenProjects: [{ projectKey: "workspace-a", hiddenAt: "2026-07-14T01:00:00.000Z" }]
  });

  assert.deepEqual(state.threads.map((thread) => thread.id), ["recent"]);
  assert.deepEqual(state.projectThreads.map((thread) => thread.id), ["recent", "older"]);
  assert.equal(state.hiddenProjects[0].projectKey, "workspace-a");
});

test("project history merges incremental pages without replacing earlier rows", () => {
  const first = reduceChatsState(initialChatsState, {
    type: "projects.loaded",
    threads: [{ id: "recent", ownerId: "a" }],
    errors: [],
    hiddenProjects: [],
    append: false
  });
  const second = reduceChatsState(first, {
    type: "projects.loaded",
    threads: [{ id: "older", ownerId: "a" }],
    errors: [],
    hiddenProjects: [],
    append: true
  });

  assert.deepEqual(second.projectThreads.map((thread) => thread.id), ["recent", "older"]);
});

test("hiding a project updates only project visibility preferences", () => {
  const threads = [{ id: "kept", ownerId: "a" }];
  const state = reduceChatsState({ ...initialChatsState, threads, projectThreads: threads }, {
    type: "projects.hidden",
    hiddenProject: { projectKey: "workspace-a", hiddenAt: "2026-07-14T01:00:00.000Z" }
  });

  assert.equal(state.hiddenProjects[0].projectKey, "workspace-a");
  assert.equal(state.threads, threads);
  assert.equal(state.projectThreads, threads);
});

test("recent page refresh does not replace full project history", () => {
  const state = reduceChatsState({
    ...initialChatsState,
    projectThreads: [{ id: "older", ownerId: "a" }],
    projectsLoaded: true
  }, {
    type: "page.loaded",
    threads: [{ id: "recent", ownerId: "a" }]
  });

  assert.deepEqual(state.projectThreads.map((thread) => thread.id), ["older"]);
});

test("partial project refresh preserves the last complete project history", () => {
  const previous = {
    ...initialChatsState,
    projectThreads: [{ id: "kept", ownerId: "a" }],
    projectsLoaded: true
  };
  const state = reduceChatsState(previous, {
    type: "projects.loaded",
    threads: [{ id: "partial", ownerId: "b" }],
    errors: [{ ownerId: "a", error: "source_unavailable" }]
  });

  assert.deepEqual(state.projectThreads, previous.projectThreads);
});
