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

import {
  enqueueMessage,
  readQueuedMessages,
  removeQueuedMessage,
} from "../web/static/js/chat_queue.js";

class MemoryStorage {
  constructor() { this.values = new Map(); }
  getItem(key) { return this.values.get(key) ?? null; }
  setItem(key, value) { this.values.set(key, String(value)); }
  removeItem(key) { this.values.delete(key); }
}

test("queued messages are ordered and isolated by user and task", () => {
  const storage = new MemoryStorage();
  enqueueMessage(storage, "u1", "t1", { id: "1", content: "第一条" });
  enqueueMessage(storage, "u1", "t1", { id: "2", content: "第二条" });
  enqueueMessage(storage, "u1", "t2", { id: "3", content: "另一对话" });
  enqueueMessage(storage, "u2", "t1", { id: "4", content: "另一用户" });

  assert.deepEqual(readQueuedMessages(storage, "u1", "t1").map(x => x.id), ["1", "2"]);
  assert.equal(readQueuedMessages(storage, "u1", "t2")[0].content, "另一对话");
  assert.equal(readQueuedMessages(storage, "u2", "t1")[0].content, "另一用户");
});

test("queued messages can be removed without disturbing later entries", () => {
  const storage = new MemoryStorage();
  enqueueMessage(storage, "u1", "t1", { id: "1", content: "第一条" });
  enqueueMessage(storage, "u1", "t1", { id: "2", content: "第二条" });
  const remaining = removeQueuedMessage(storage, "u1", "t1", "1");
  assert.deepEqual(remaining.map(x => x.id), ["2"]);
  assert.deepEqual(readQueuedMessages(storage, "u1", "t1").map(x => x.id), ["2"]);
});

test("a task queue is bounded", () => {
  const storage = new MemoryStorage();
  for (let i = 0; i < 25; i++) {
    enqueueMessage(storage, "u1", "t1", { id: String(i), content: String(i) });
  }
  const items = readQueuedMessages(storage, "u1", "t1");
  assert.equal(items.length, 20);
  assert.equal(items[0].id, "5");
  assert.equal(items.at(-1).id, "24");
});

test("composer uses one Codex-style action button for stop and queued send", () => {
  const html = readFileSync(new URL("../web/static/dev.html", import.meta.url), "utf8");
  const chat = readFileSync(new URL("../web/static/js/chat.js", import.meta.url), "utf8");
  assert.match(html, /id="chat-action"/);
  assert.doesNotMatch(html, /id="chat-stop"/);
  assert.match(html, /\.msg\.queued/);
  assert.match(chat, /card\.className = "msg user queued"/);
  assert.match(chat, /function composerHasPayload\(\)/);
  assert.match(chat, /btn\.textContent = "停止"/);
  assert.match(chat, /if \(composerHasPayload\(\)\)[\s\S]*queueCurrentMessage\(\)/);
  assert.match(chat, /_actionMode === "loading" \|\| _actionMode === "submitting"[\s\S]*btn\.disabled = true/);
  assert.match(chat, /setActionMode\("submitting"\)/);
  assert.match(chat, /if \(state\.taskId === taskId\) \$\("chat-input"\)\.value = ""/);
  assert.match(chat, /if \(state\.taskId === taskId\) setActionMode\("streaming"\)/);
  assert.match(chat, /function queueCurrentMessage\(\)/);
  assert.match(chat, /async function dispatchNextQueuedMessage\(taskId\)/);
  assert.match(chat, /void dispatchNextQueuedMessage\(ctx\.taskId\)/);
  assert.match(chat, /removeQueuedMessage\([\s\S]*streamSse\(r\.events_url, run\)/);
});

test("automatic title polling starts before a long-running response finishes", () => {
  const chat = readFileSync(new URL("../web/static/js/chat.js", import.meta.url), "utf8");
  const sendMessage = chat.indexOf("async function sendMessage(");
  const poll = chat.indexOf("void pollAutoTitle(taskId)", sendMessage);
  const stream = chat.indexOf("streamSse(r.events_url, run)", sendMessage);
  assert.ok(poll > sendMessage, "sendMessage should start title polling");
  assert.ok(poll < stream, "title polling should start before waiting on the SSE run");
});
