import assert from "node:assert/strict";
import {
  installResolveHooks,
  toDataModuleUrl,
} from "./helpers/register-hooks-compat.mjs";

const extensionsShimSource = [
  "export function getContext(...args) {",
  "  return globalThis.SillyTavern?.getContext?.(...args) || null;",
  "}",
].join("\n");
const extensionsShimUrl = `data:text/javascript,${encodeURIComponent(
  extensionsShimSource,
)}`;

installResolveHooks([
  {
    specifiers: [
      "../../../extensions.js",
      "../../../../extensions.js",
    ],
    url: extensionsShimUrl || toDataModuleUrl(extensionsShimSource),
  },
]);

const originalSillyTavern = globalThis.SillyTavern;
const originalGetCurrentChatId = globalThis.getCurrentChatId;
const originalEjs = globalThis.ejs;

try {
  globalThis.getCurrentChatId = () => "chat-from-global";
  globalThis.SillyTavern = {
    getContext() {
      return {
        name1: "User",
        name2: "Alice",
        name1_description: "旧 persona 字段",
        powerUserSettings: {
          persona_description: "桥接 persona",
          persona_description_lorebook: "persona-book",
        },
        extensionSettings: {
          persona_description: "扩展 persona",
          variables: {
            global: {
              score: 7,
            },
          },
        },
        characterId: 0,
        characters: [
          {
            avatar: "alice.png",
            data: {
              description: "角色描述",
              extensions: {
                world: "char-book",
              },
            },
          },
        ],
        chatMetadata: {
          world: "chat-book",
          variables: {
            location: "library",
          },
        },
        chat: [
          { is_user: true, mes: "第一句" },
          {
            is_user: false,
            is_system: true,
            mes: "被 BME 隐藏的助手楼层",
            extra: {
              __st_bme_hide_managed: true,
            },
          },
          {
            is_user: false,
            mes: "回应",
            variables: {
              0: {
                mood: "calm",
              },
            },
          },
          { is_user: true, mes: "最后一句" },
        ],
        onlineStatus: "gpt-test",
        selectedGroupId: 42,
      };
    },
  };

  const { getSTContextForPrompt, getSTContextSnapshot } =
    await import("../host/st-context.js");
  const {
    substituteTaskEjsParams,
    createTaskEjsRenderContext,
    evalTaskEjsTemplate,
    checkTaskEjsSyntax,
    inspectTaskEjsRuntimeBackend,
  } = await import("../prompting/task-ejs.js");

  const promptContext = getSTContextForPrompt();
  assert.deepEqual(promptContext, {
    userPersona: "桥接 persona",
    charDescription: "角色描述",
    charName: "Alice",
    userName: "User",
    currentTime: promptContext.currentTime,
  });

  const hostSnapshot = getSTContextSnapshot();
  assert.equal(hostSnapshot.snapshot.persona.text, "桥接 persona");
  assert.equal(hostSnapshot.snapshot.character.description, "角色描述");
  assert.equal(hostSnapshot.snapshot.character.worldbook, "char-book");
  assert.equal(hostSnapshot.snapshot.worldbook.persona, "persona-book");
  assert.equal(hostSnapshot.snapshot.worldbook.chat, "chat-book");
  assert.equal(hostSnapshot.snapshot.variables.global.score, 7);
  assert.equal(hostSnapshot.snapshot.variables.local.location, "library");
  assert.equal(hostSnapshot.snapshot.chat.lastUserMessage, "最后一句");
  assert.equal(hostSnapshot.snapshot.chat.id, "chat-from-global");
  assert.equal(
    hostSnapshot.snapshot.chat.messages[1]?.is_system,
    false,
  );
  assert.equal(
    hostSnapshot.snapshot.chat.messages[1]?.mes,
    "被 BME 隐藏的助手楼层",
  );
  assert.equal(hostSnapshot.prompt.charName, "Alice");
  assert.equal(hostSnapshot.prompt.userPersona, "桥接 persona");

  const substitution = substituteTaskEjsParams(
    "{{charName}}|{{userPersona}}|{{hostSnapshot.worldbook.chat}}|{{stSnapshot.chat.lastUserMessage}}",
    {},
    { hostSnapshot },
  );
  assert.equal(substitution, "Alice|桥接 persona|chat-book|最后一句");

  const compileCalls = [];
  globalThis.ejs = {
    compile(template) {
      compileCalls.push(template);
      if (template === "<% broken") {
        throw new Error("Unexpected end of input");
      }
      if (template === "<% await execute() %>") {
        return async function compiled(locals) {
          await locals.execute();
          return "";
        };
      }
      return async function compiled(locals) {
        return [
          locals.charName,
          locals.userName,
          locals.userLoreBook,
          locals.chatLoreBook,
          locals.variables.score,
          locals.variables.location,
          locals.lastUserMessage,
          locals.recentMessages,
          locals.persona,
          locals.hostSnapshot.character.worldbook,
          locals.stSnapshot.chat.lastUserMessage,
          typeof locals.execute,
        ].join("|");
      };
    },
  };

  const renderCtx = createTaskEjsRenderContext([], {
    hostSnapshot,
    templateContext: {
      user: "AliasUser",
      char: "AliasAlice",
      userName: "AliasUser",
      charName: "AliasAlice",
      recentMessages: "最近上下文",
      persona: "AliasPersona",
    },
  });
  const primaryBackend = await inspectTaskEjsRuntimeBackend({
    ensureRuntime: false,
  });
  assert.equal(primaryBackend.status, "primary");
  assert.equal(primaryBackend.isAvailable, true);
  assert.equal(primaryBackend.isFallback, false);

  const syntaxOk = await checkTaskEjsSyntax("<%= 1 %>");
  assert.equal(syntaxOk, null);

  const rendered = await evalTaskEjsTemplate("<%= 1 %>", renderCtx);
  assert.equal(
    rendered,
    "AliasAlice|AliasUser|persona-book|chat-book|7|library|最后一句|最近上下文|AliasPersona|char-book|最后一句|function",
  );
  assert.deepEqual(compileCalls, ["<%= 1 %>", "<%= 1 %>"]);

  await assert.rejects(
    () => evalTaskEjsTemplate("<% await execute() %>", renderCtx),
    (error) =>
      error?.code === "st_bme_task_ejs_unsupported_helper" &&
      error?.helperName === "execute",
  );

  const syntaxError = await checkTaskEjsSyntax("<% broken");
  assert.equal(syntaxError, "Unexpected end of input");

  delete globalThis.ejs;
  const failedBackend = await inspectTaskEjsRuntimeBackend({
    ensureRuntime: false,
  });
  assert.equal(failedBackend.status, "failed");
  assert.equal(failedBackend.isAvailable, false);
  assert.equal(failedBackend.isFallback, false);

  const passthrough = await evalTaskEjsTemplate("{{charName}}", renderCtx);
  assert.equal(passthrough, "AliasAlice");
} finally {
  globalThis.SillyTavern = originalSillyTavern;
  globalThis.getCurrentChatId = originalGetCurrentChatId;
  globalThis.ejs = originalEjs;
}
