import test from "node:test";
import assert from "node:assert/strict";
import { buildComposerPayload, initialComposerOptions, loadComposerOptions, normalizeComposerModel, saveComposerOptions, updateComposerOption } from "./composer-state.js";

test("composer payload includes message and selected options", () => {
  assert.deepEqual(buildComposerPayload({
    message: "Ship it",
    executionAccountId: "account-b",
    options: { ...initialComposerOptions, model: "gpt-5-codex", planMode: true },
    attachments: [{ id: "att-1", name: "photo.jpg" }],
    selectedSkills: [{ name: "imagegen" }]
  }), {
    text: "Ship it",
    executionAccountId: "account-b",
    options: {
      model: "gpt-5-codex",
      permissionProfile: ":workspace",
      approvalPolicy: "on-request",
      planMode: true
    },
    attachments: [{ id: "att-1" }],
    skills: [{ name: "imagegen" }]
  });
});

test("composer payload omits empty optional selections", () => {
  assert.deepEqual(buildComposerPayload({
    message: "Hello",
    options: initialComposerOptions,
    attachments: [],
    selectedSkills: []
  }), {
    text: "Hello",
    options: {
      permissionProfile: ":workspace",
      approvalPolicy: "on-request",
      planMode: false
    },
    attachments: [],
    skills: []
  });
});

test("composer permission profile persists and legacy choices migrate safely", () => {
  const values = new Map();
  const storage = {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, value)
  };
  const fullAccess = { ...initialComposerOptions, permissionProfile: ":danger-full-access" };

  saveComposerOptions(storage, fullAccess);
  assert.deepEqual(loadComposerOptions(storage), fullAccess);

  values.set("code-remote-manager.composer-permissions", JSON.stringify({ approvalPolicy: "on-request", sandbox: "read-only" }));
  assert.equal(loadComposerOptions(storage).permissionProfile, ":read-only");
});

test("composer permission storage falls back safely when unavailable or invalid", () => {
  const invalidStorage = { getItem: () => JSON.stringify({ permissionProfile: "unknown" }) };
  const blockedStorage = { getItem: () => { throw new Error("blocked"); }, setItem: () => { throw new Error("blocked"); } };

  assert.deepEqual(loadComposerOptions(invalidStorage), initialComposerOptions);
  assert.deepEqual(loadComposerOptions(blockedStorage), initialComposerOptions);
  assert.doesNotThrow(() => saveComposerOptions(blockedStorage, initialComposerOptions));
});

test("changing a permission persists it immediately for the next chat", () => {
  const values = new Map();
  const storage = {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, value)
  };

  const updated = updateComposerOption(initialComposerOptions, "permissionProfile", ":danger-full-access", storage);

  assert.equal(updated.permissionProfile, ":danger-full-access");
  assert.equal(loadComposerOptions(storage).permissionProfile, ":danger-full-access");
});

test("model and effort persist independently for each chat", () => {
  const values = new Map();
  const storage = {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, value)
  };

  let chatA = updateComposerOption(loadComposerOptions(storage, "chat-a"), "model", "gpt-5.4", storage, "chat-a");
  chatA = updateComposerOption(chatA, "effort", "high", storage, "chat-a");
  const chatB = updateComposerOption(loadComposerOptions(storage, "chat-b"), "model", "gpt-5.5", storage, "chat-b");

  assert.equal(loadComposerOptions(storage, "chat-a").model, "gpt-5.4");
  assert.equal(loadComposerOptions(storage, "chat-a").effort, "high");
  assert.equal(loadComposerOptions(storage, "chat-b").model, "gpt-5.5");
  assert.equal(loadComposerOptions(storage, "chat-b").effort, "");
  assert.equal(chatB.model, "gpt-5.5");
});

test("new chats default to the most recently selected model and effort without replacing chat-specific choices", () => {
  const values = new Map();
  const storage = {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, value)
  };

  updateComposerOption(loadComposerOptions(storage, "chat-a"), "model", "gpt-5.4", storage, "chat-a");
  let chatB = updateComposerOption(loadComposerOptions(storage, "chat-b"), "model", "gpt-5.5", storage, "chat-b");
  chatB = updateComposerOption(chatB, "effort", "medium", storage, "chat-b");

  assert.equal(loadComposerOptions(storage).model, "gpt-5.5");
  assert.equal(loadComposerOptions(storage).effort, "medium");
  assert.equal(loadComposerOptions(storage, "chat-a").model, "gpt-5.4");
  assert.equal(loadComposerOptions(storage, "chat-b").model, "gpt-5.5");
  assert.equal(loadComposerOptions(storage, "chat-b").effort, "medium");
});

test("chat restoration backfills effort for recent-model data saved before effort persistence", () => {
  const values = new Map([
    ["code-remote-manager.composer-recent-model", JSON.stringify({ model: "gpt-5.5" })],
    ["code-remote-manager.composer-chat.chat-a", JSON.stringify({ model: "gpt-5.5", effort: "medium" })]
  ]);
  const storage = {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, value)
  };

  loadComposerOptions(storage, "chat-a");

  assert.equal(loadComposerOptions(storage).effort, "medium");
});

test("chat model restoration keeps global permissions and rejects unavailable capabilities", () => {
  const values = new Map();
  const storage = {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, value)
  };
  const permissions = updateComposerOption(initialComposerOptions, "permissionProfile", ":read-only", storage);
  let options = updateComposerOption(permissions, "model", "retired-model", storage, "chat-a");
  options = updateComposerOption(options, "effort", "ultra", storage, "chat-a");

  assert.equal(loadComposerOptions(storage, "chat-a").permissionProfile, ":read-only");
  assert.deepEqual(normalizeComposerModel(options, [
    { model: "gpt-5.5", reasoningEfforts: [{ id: "low" }, { id: "high" }] }
  ]), { ...options, model: "", effort: "" });
  assert.deepEqual(normalizeComposerModel({ ...options, model: "gpt-5.5", effort: "ultra" }, [
    { model: "gpt-5.5", reasoningEfforts: [{ id: "low" }, { id: "high" }] }
  ]), { ...options, model: "gpt-5.5", effort: "" });
});
