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

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

installResolveHooks([
  {
    specifiers: [
      "../../../extensions.js",
      "../../../../extensions.js",
      "../../../../../extensions.js",
    ],
    url: extensionsShimUrl,
  },
  {
    specifiers: [
      "../../../../script.js",
      "../../../../../script.js",
    ],
    url: scriptShimUrl,
  },
]);

const originalSillyTavern = globalThis.SillyTavern;
const originalEjsTemplate = globalThis.EjsTemplate;
const originalMvu = globalThis.Mvu;
const originalGetCharWorldbookNames = globalThis.getCharWorldbookNames;
const originalGetWorldbook = globalThis.getWorldbook;
const originalGetLorebookEntries = globalThis.getLorebookEntries;

function createWorldbookEntry({
  uid,
  name,
  comment = name,
  content,
  enabled = true,
  positionType = "before_character_definition",
  role = "system",
  depth = 0,
  order = 10,
  strategyType = "constant",
  keys = [],
  keysSecondary = [],
}) {
  return {
    uid,
    name,
    comment,
    content,
    enabled,
    position: {
      type: positionType,
      role,
      depth,
      order,
    },
    strategy: {
      type: strategyType,
      keys,
      keys_secondary: { logic: "and_any", keys: keysSecondary },
    },
    probability: 100,
    extra: {},
  };
}

function createConstantWorldbookEntry(uid, name, content, comment = name) {
  return createWorldbookEntry({
    uid,
    name,
    comment,
    content,
  });
}

const constantEntry = createWorldbookEntry({
  uid: 1,
  name: "常驻设定",
  comment: "常驻设定",
  content: "这里是常驻世界设定。",
  order: 10,
});

const dynEntry = createWorldbookEntry({
  uid: 2,
  name: "EW/Dyn/线索",
  comment: "线索条目",
  content: "隐藏线索：<%= charName %> 正在调查。",
  enabled: false,
  strategyType: "selective",
  keys: ["调查"],
  order: 15,
});

const inlineSummaryEntry = createWorldbookEntry({
  uid: 3,
  name: "普通 EJS 汇总",
  comment: "EJS 汇总",
  content: '控制摘要：<%= await getwi("EW/Dyn/线索") %>',
  order: 20,
});

const inlineDataSummaryEntry = createWorldbookEntry({
  uid: 12,
  name: "数据 EJS 汇总",
  comment: "数据 EJS 汇总",
  content:
    '数据摘要：<%= await getwi("数据模板", { clue: "蓝钥匙", mood: "紧张" }) %>',
  order: 21,
});

const inlineDataTemplateEntry = createWorldbookEntry({
  uid: 13,
  name: "数据模板",
  comment: "数据模板",
  content:
    "线索=<%= clue %>；情绪=<%= mood %>；角色=<%= char %>；用户=<%= user %>；上下文=<%= recentMessages %>",
  enabled: false,
  order: 22,
});

const commentKeywordProbeEntry = createWorldbookEntry({
  uid: 14,
  name: "备注命中测试",
  comment: "常驻备注",
  content: "这条只用于验证 comment 不参与自定义过滤。",
  strategyType: "selective",
  keys: ["绝不会匹配到这里"],
  order: 23,
});

const extensionLiteralEntry = createWorldbookEntry({
  uid: 4,
  name: "扩展语义正文",
  comment: "扩展语义正文",
  content: "@@generate\n[GENERATE:Test]\n扩展语义只是普通文本。",
  order: 25,
});

const externalInlineEntry = createWorldbookEntry({
  uid: 5,
  name: "外部书汇总",
  comment: "外部书汇总",
  content: '外部补充：<%= await getwi("bonus-book", "Bonus 条目") %>',
  order: 26,
});

const forceControlEntry = createWorldbookEntry({
  uid: 6,
  name: "普通 EJS 控制",
  comment: "EJS 控制",
  content: '<% await activewi("强制 after") %>',
  order: 30,
});

const forcedAfterEntry = createWorldbookEntry({
  uid: 7,
  name: "强制 after",
  comment: "强制后置",
  content: "这是被 EJS 强制激活的后置条目。",
  enabled: false,
  positionType: "after_character_definition",
  strategyType: "selective",
  keys: ["永远不会命中"],
  order: 40,
});

const atDepthEntry = createWorldbookEntry({
  uid: 8,
  name: "深度注入",
  comment: "深度注入",
  content: "这是一条 atDepth 消息。",
  positionType: "at_depth_as_system",
  depth: 2,
  order: 5,
});

const mvuTaggedEntry = createWorldbookEntry({
  uid: 9,
  name: "[mvu_update] 状态同步",
  comment: "MVU tagged",
  content: "这一条不应该进入结果。",
  order: 28,
});

const mvuHeuristicEntry = createWorldbookEntry({
  uid: 10,
  name: "MVU 启发式条目",
  comment: "MVU heuristic",
  content: "<status_current_variable>secret=true</status_current_variable>",
  order: 29,
});

const mvuLazyProbeEntry = createWorldbookEntry({
  uid: 11,
  name: "MVU 懒加载探测",
  comment: "MVU 懒加载探测",
  content: 'MVU lazy: <%= await getwi("bonus-book", "Bonus MVU") %>',
  order: 27,
});

const statDataControllerEntry = createWorldbookEntry({
  uid: 15,
  name: "StatData Controller",
  comment: "StatData Controller",
  content:
    '<% if (typeof stat_data !== "undefined" && stat_data?.user?.["\u610f\u8bc6\u72b6\u6001"] === "\u6c89\u7720") { %>stat_data controller payload<% } %>',
  order: 24,
});

const statDataTargetEntry = createWorldbookEntry({
  uid: 16,
  name: "StatData Target",
  comment: "StatData Target",
  content: "stat_data controller payload",
  enabled: false,
  order: 24.1,
});

const messageVarMacroEntry = createWorldbookEntry({
  uid: 17,
  name: "MessageVar Macro",
  comment: "MessageVar Macro",
  content: "latest state={{get_message_variable::stat_data.user.\u610f\u8bc6\u72b6\u6001}}",
  order: 24.2,
});

const customContextProbeEntry = createWorldbookEntry({
  uid: 18,
  name: "Custom Context Probe",
  comment: "Custom Context Probe",
  content: "上下文探针：user=<%= user_input %>;char=<%= charName %>",
  strategyType: "selective",
  keys: ["probe custom mode"],
  order: 24.3,
});
const bonusEntry = createWorldbookEntry({
  uid: 101,
  name: "Bonus 条目",
  comment: "Bonus 条目",
  content: "来自 bonus-book 的补充内容。",
  order: 10,
});

  const bonusMvuEntry = createWorldbookEntry({
  uid: 102,
  name: "Bonus MVU",
  comment: "Bonus MVU",
  content: "变量更新规则:\ntype: sync\n当前时间: 12:00",
  order: 20,
});

  const worldbooksByName = {
    "main-book": [
    constantEntry,
    dynEntry,
    inlineSummaryEntry,
    inlineDataSummaryEntry,
    inlineDataTemplateEntry,
    commentKeywordProbeEntry,
    extensionLiteralEntry,
    externalInlineEntry,
    mvuLazyProbeEntry,
    statDataControllerEntry,
    statDataTargetEntry,
    messageVarMacroEntry,
    customContextProbeEntry,
    forceControlEntry,
    forcedAfterEntry,
    atDepthEntry,
    mvuTaggedEntry,
    mvuHeuristicEntry,
    ],
    "bonus-book": [bonusEntry, bonusMvuEntry],
  };

try {
  globalThis.SillyTavern = {
    getContext() {
      return {
        name1: "User",
        name2: "Alice",
        chat: [{ is_user: true, mes: "我们继续调查那条线索" }],
        chatMetadata: {},
        extensionSettings: {},
      };
    },
  };
  globalThis.getCharWorldbookNames = () => ({
    primary: "main-book",
    additional: [],
  });
  globalThis.getWorldbook = async (worldbookName) =>
    worldbooksByName[worldbookName] || [];
  globalThis.getLorebookEntries = async (worldbookName) =>
    (worldbooksByName[worldbookName] || []).map((entry) => ({
      uid: entry.uid,
      comment: entry.comment,
    }));

  const { resolveTaskWorldInfo } = await import("../prompting/task-worldinfo.js");
  const { buildTaskPrompt, buildTaskLlmPayload } = await import(
    "../prompting/prompt-builder.js"
  );

  const emptyTriggerWorldInfo = await resolveTaskWorldInfo({
    chatMessages: [],
    userMessage: "",
    templateContext: {},
  });
  assert.equal(
    emptyTriggerWorldInfo.beforeEntries.some((entry) => entry.name === "常驻设定"),
    true,
    "constant world info should still resolve without trigger text",
  );
  assert.equal(
    emptyTriggerWorldInfo.beforeEntries.some((entry) => entry.name === "数据 EJS 汇总"),
    true,
    "constant EJS entry should still render with empty template context defaults",
  );
  assert.match(emptyTriggerWorldInfo.beforeText, /数据摘要：线索=蓝钥匙；情绪=紧张；角色=Alice；用户=User；上下文=/);
  assert.equal(
    emptyTriggerWorldInfo.debug.warnings.some((warning) => warning.includes("渲染失败")),
    false,
  );

  const worldInfo = await resolveTaskWorldInfo({
    templateContext: {
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
    userMessage: "继续调查",
  });
  assert.equal(worldInfo.beforeEntries.length, 6);
  assert.equal(worldInfo.afterEntries.length, 1);
  assert.equal(worldInfo.additionalMessages.length, 1);
  assert.match(worldInfo.additionalMessages[0].content, /atDepth/);
  assert.match(worldInfo.beforeText, /Alice/);
  assert.match(worldInfo.beforeText, /bonus-book/);
  assert.match(worldInfo.beforeText, /MVU lazy:/);
  assert.match(worldInfo.beforeText, /@@generate/);
  assert.match(worldInfo.beforeText, /\[GENERATE:Test\]/);
  assert.doesNotMatch(worldInfo.beforeText, /getwi|<%=?/);
  assert.doesNotMatch(worldInfo.beforeText, /status_current_variable|updatevariable/i);
  assert.equal(worldInfo.debug.ejsInlinePullCount, 3);
  assert.equal(worldInfo.debug.ejsForcedActivationCount, 1);
  assert.equal(worldInfo.debug.resolvePassCount >= 2, true);
  assert.equal(worldInfo.debug.forcedActivatedEntries.length, 1);
  assert.equal(worldInfo.debug.inlinePulledEntries.length, 3);
  assert.deepEqual(worldInfo.debug.lazyLoadedWorldbooks, ["bonus-book"]);
  assert.equal(worldInfo.debug.mvu.filteredEntryCount, 3);
  assert.equal(worldInfo.debug.mvu.lazyFilteredEntryCount, 1);
  assert.equal(worldInfo.debug.mvu.blockedContentsCount, 4);
  const defaultFilteredSourceNames = worldInfo.debug.mvu.filteredEntries
    .map((entry) => entry.sourceName)
    .sort();
  assert.equal(defaultFilteredSourceNames.includes("Bonus MVU"), true);
  assert.equal(defaultFilteredSourceNames.some((name) => String(name || "").includes("MVU")), true);
  assert.equal(defaultFilteredSourceNames.some((name) => String(name || "").startsWith("[mvu_update]")), true);
  assert.equal(
    worldInfo.debug.warnings.some((warning) => warning.includes("EW/")),
    true,
  );
  assert.equal(
    worldInfo.debug.recursionWarnings.some((warning) =>
      warning.includes("mvu filtered world info blocked"),
    ),
    true,
  );
  assert.equal(worldInfo.debug.customFilter.mode, "default");
  assert.equal(worldInfo.debug.customFilter.filteredEntryCount, 0);

  globalThis.Mvu = {
    getMvuData({ type, message_id: messageId } = {}) {
      if (type === "message" && messageId === "latest") {
        return {
          stat_data: {
            user: {
              "意识状态": "沉眠",
            },
            "恼恼": {
              "发情值": 71,
            },
          },
        };
      }
      return {};
    },
  };

  const customWorldInfo = await resolveTaskWorldInfo({
    settings: {
      worldInfoFilterMode: "custom",
      worldInfoFilterCustomKeywords: "",
    },
    templateContext: {
      recentMessages: "custom-mode regression probe",
      charName: "Alice",
    },
    userMessage: "probe custom mode",
  });

  assert.equal(
    customWorldInfo.beforeEntries.some((entry) =>
      String(entry.sourceName || "").startsWith("[mvu_update]"),
    ),
    true,
  );
  assert.equal(
    customWorldInfo.beforeEntries.some((entry) =>
      String(entry.sourceName || "").includes("MVU"),
    ),
    true,
  );
  assert.match(
    customWorldInfo.beforeText,
    /<status_current_variable>secret=true<\/status_current_variable>/,
  );
  assert.match(
    customWorldInfo.beforeText,
    /控制摘要：隐藏线索：Alice 正在调查。/,
  );
  assert.match(
    customWorldInfo.beforeText,
    /上下文探针：user=probe custom mode;char=Alice/,
  );
  assert.equal(
    customWorldInfo.allEntries.some((entry) => String(entry.name || "").startsWith("EW/Dyn/")),
    true,
  );
  assert.equal(
    customWorldInfo.afterEntries.some((entry) => entry.sourceName === "强制 after"),
    true,
  );
  assert.equal(customWorldInfo.debug.mvu.filteredEntryCount, 0);
  assert.equal(customWorldInfo.debug.customFilter.mode, "custom");
  assert.equal(customWorldInfo.debug.customFilter.filteredEntryCount, 0);
  assert.equal(
    customWorldInfo.debug.customRender.bridgedStatDataFromLatestMessage,
    true,
  );
  assert.equal(customWorldInfo.debug.customRender.taskEjsStatDataRoots.cache, true);
  assert.equal(
    customWorldInfo.debug.customRender.taskEjsStatDataRoots.message,
    true,
  );
  assert.equal(customWorldInfo.debug.customRender.fallbackEntryCount > 0, true);
  assert.match(customWorldInfo.beforeText, /stat_data controller payload/);
  assert.match(customWorldInfo.beforeText, /latest state=.+/);

  globalThis.EjsTemplate = {
    async prepareContext() {
      return {
        user_input: "OLD_FROM_NATIVE",
        charName: "OLD_CHAR",
      };
    },
    async evalTemplate(text, env) {
      return String(text)
        .replace(/<%=\s*user_input\s*%>/g, String(env.user_input ?? ""))
        .replace(/<%=\s*charName\s*%>/g, String(env.charName ?? ""));
    },
  };

  const customWorldInfoWithNativeRuntime = await resolveTaskWorldInfo({
    settings: {
      worldInfoFilterMode: "custom",
      worldInfoFilterCustomKeywords: "",
    },
    templateContext: {
      recentMessages: "custom-mode regression probe",
      charName: "Alice",
    },
    userMessage: "probe custom mode",
  });

  assert.match(
    customWorldInfoWithNativeRuntime.beforeText,
    /上下文探针：user=probe custom mode;char=Alice/,
  );
  assert.doesNotMatch(
    customWorldInfoWithNativeRuntime.beforeText,
    /OLD_FROM_NATIVE|OLD_CHAR/,
  );
  delete globalThis.EjsTemplate;

  const keywordWorldInfo = await resolveTaskWorldInfo({
    settings: {
      worldInfoFilterMode: "custom",
      worldInfoFilterCustomKeywords: "常驻",
    },
    templateContext: {
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
    userMessage: "继续调查",
  });

  assert.equal(
    keywordWorldInfo.beforeEntries.some(
      (entry) => entry.sourceName === "常驻设定",
    ),
    false,
  );
  assert.equal(
    keywordWorldInfo.allEntries.some((entry) => entry.name === "备注命中测试"),
    true,
  );
  assert.equal(keywordWorldInfo.debug.customFilter.filteredEntryCount, 1);
  assert.equal(
    keywordWorldInfo.debug.customFilter.filteredEntries[0].name,
    "常驻设定",
  );
  assert.equal(
    keywordWorldInfo.debug.customFilter.filteredEntries[0].matchedKeyword,
    "常驻",
  );

  const keywordCachePrime = await resolveTaskWorldInfo({
    settings: {
      worldInfoFilterMode: "custom",
      worldInfoFilterCustomKeywords: "常驻,缓存探针",
    },
    templateContext: {
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
    userMessage: "继续调查",
  });
  assert.equal(keywordCachePrime.debug.cache.hit, false);
  assert.equal(keywordCachePrime.debug.customFilter.filteredEntryCount, 1);

  const keywordCacheHit = await resolveTaskWorldInfo({
    settings: {
      worldInfoFilterMode: "custom",
      worldInfoFilterCustomKeywords: "常驻,缓存探针",
    },
    templateContext: {
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
    userMessage: "继续调查",
  });
  assert.equal(keywordCacheHit.debug.cache.hit, true);
  assert.equal(keywordCacheHit.debug.customFilter.filteredEntryCount, 1);
  assert.equal(
    keywordCacheHit.debug.customFilter.filteredEntries[0].name,
    "常驻设定",
  );

  delete globalThis.Mvu;

  const defaultModeWithKeywords = await resolveTaskWorldInfo({
    settings: {
      worldInfoFilterMode: "default",
      worldInfoFilterCustomKeywords: "常驻",
    },
    templateContext: {
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
    userMessage: "继续调查",
  });
  assert.equal(
    defaultModeWithKeywords.beforeEntries.some(
      (entry) => entry.sourceName === "常驻设定",
    ),
    true,
  );
  assert.equal(defaultModeWithKeywords.debug.mvu.filteredEntryCount > 0, true);
  assert.equal(defaultModeWithKeywords.debug.customFilter.filteredEntryCount, 0);

  const settings = {
    taskProfiles: {
      recall: {
        activeProfileId: "custom",
        profiles: [
          {
            id: "custom",
            name: "测试预设",
            taskType: "recall",
            builtin: false,
            blocks: [
              {
                id: "b1",
                type: "builtin",
                sourceKey: "worldInfoBefore",
                role: "system",
                enabled: true,
                order: 0,
                injectionMode: "append",
              },
              {
                id: "b2",
                type: "builtin",
                sourceKey: "worldInfoAfter",
                role: "system",
                enabled: true,
                order: 1,
                injectionMode: "append",
              },
              {
                id: "b3",
                type: "custom",
                content: "角色: {{charName}}",
                role: "user",
                enabled: true,
                order: 2,
                injectionMode: "append",
              },
            ],
          },
        ],
      },
    },
  };

  const promptBuild = await buildTaskPrompt(settings, "recall", {
    taskName: "recall",
    userMessage: "继续调查",
    recentMessages: "我们继续调查那条线索",
    charName: "Alice",
  });

  assert.match(promptBuild.systemPrompt, /这里是常驻世界设定/);
  assert.match(promptBuild.systemPrompt, /控制摘要：隐藏线索：Alice 正在调查/);
  assert.match(
    promptBuild.systemPrompt,
    /数据摘要：线索=蓝钥匙；情绪=紧张；角色=Alice；用户=User；上下文=我们继续调查那条线索/,
  );
  assert.match(promptBuild.systemPrompt, /扩展语义只是普通文本/);
  assert.match(promptBuild.systemPrompt, /来自 bonus-book 的补充内容/);
  assert.match(promptBuild.systemPrompt, /MVU lazy:/);
  assert.doesNotMatch(promptBuild.systemPrompt, /getwi|<%=?/);
  assert.doesNotMatch(promptBuild.systemPrompt, /status_current_variable|变量更新规则|updatevariable/i);
  assert.equal(
    promptBuild.privateTaskMessages.length,
    2,
    "custom user block + atDepth world info should both enter private task messages",
  );
  assert.deepEqual(
    promptBuild.privateTaskMessages.map((message) => message.role),
    ["system", "user"],
  );
  assert.equal(
    promptBuild.privateTaskMessages[0].content,
    "这是一条 atDepth 消息。",
  );
  assert.deepEqual(
    promptBuild.hostInjections.before.map((entry) => entry.name),
    [
      "常驻设定",
      "EJS 汇总",
      "数据 EJS 汇总",
      "扩展语义正文",
      "外部书汇总",
      "MVU 懒加载探测",
    ],
  );
  assert.deepEqual(
    promptBuild.hostInjections.after.map((entry) => entry.name),
    ["强制后置"],
  );
  assert.equal(promptBuild.hostInjections.atDepth.length, 1);
  assert.equal(promptBuild.hostInjections.atDepth[0].depth, 2);
  assert.equal(promptBuild.hostInjectionPlan.before.length, 1);
  assert.equal(promptBuild.hostInjectionPlan.before[0].blockId, "b1");
  assert.equal(promptBuild.hostInjectionPlan.before[0].sourceKey, "worldInfoBefore");
  assert.deepEqual(promptBuild.hostInjectionPlan.before[0].entryNames, [
    "常驻设定",
    "EJS 汇总",
    "数据 EJS 汇总",
    "扩展语义正文",
    "外部书汇总",
    "MVU 懒加载探测",
  ]);
  assert.equal(promptBuild.hostInjectionPlan.after.length, 1);
  assert.equal(promptBuild.hostInjectionPlan.after[0].blockId, "b2");
  assert.equal(promptBuild.hostInjectionPlan.after[0].sourceKey, "worldInfoAfter");
  assert.deepEqual(promptBuild.hostInjectionPlan.after[0].entryNames, ["强制后置"]);
  assert.equal(promptBuild.hostInjectionPlan.atDepth.length, 1);
  assert.equal(promptBuild.hostInjectionPlan.atDepth[0].entryName, "深度注入");
  assert.equal(typeof promptBuild.debug.worldInfoCacheHit, "boolean");
  assert.equal(promptBuild.executionMessages.length, 4);
  assert.deepEqual(
    promptBuild.executionMessages.map((message) => message.role),
    ["system", "system", "system", "user"],
  );
  assert.equal(
    promptBuild.executionMessages[0].content,
    "这是一条 atDepth 消息。",
  );
  assert.deepEqual(
    promptBuild.renderedBlocks.map((block) => block.delivery),
    ["private.system", "private.system", "private.message"],
  );
  assert.equal(promptBuild.additionalMessages.length, 1);
  assert.equal(promptBuild.additionalMessages[0].content, "这是一条 atDepth 消息。");
  assert.equal(promptBuild.debug.mvu.sanitizedFieldCount >= 0, true);

  const customPromptBuild = await buildTaskPrompt(
    {
      ...settings,
      worldInfoFilterMode: "custom",
      worldInfoFilterCustomKeywords: "",
    },
    "recall",
    {
      taskName: "recall",
      userMessage: "继续调查",
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
  );
  assert.match(
    customPromptBuild.systemPrompt,
    /<status_current_variable>secret=true<\/status_current_variable>/,
  );
  assert.match(customPromptBuild.systemPrompt, /这一条不应该进入结果/);
  assert.match(customPromptBuild.systemPrompt, /控制摘要：隐藏线索：Alice 正在调查/);
  const customPayload = buildTaskLlmPayload(customPromptBuild, "unused fallback");
  assert.equal(
    customPayload.promptMessages.some((message) =>
      /<status_current_variable>secret=true<\/status_current_variable>/.test(
        message.content,
      ),
    ),
    true,
  );

  const interpolatedSettings = {
    taskProfiles: {
      recall: {
        activeProfileId: "interpolated",
        profiles: [
          {
            id: "interpolated",
            name: "插值预设",
            taskType: "recall",
            builtin: false,
            blocks: [
              {
                id: "interp-system",
                type: "custom",
                content: "世界书插值:\\n{{worldInfoBefore}}",
                role: "system",
                enabled: true,
                order: 0,
                injectionMode: "append",
              },
            ],
          },
        ],
      },
    },
    worldInfoFilterMode: "custom",
    worldInfoFilterCustomKeywords: "",
  };
  const customInterpolatedPromptBuild = await buildTaskPrompt(
    interpolatedSettings,
    "recall",
    {
      taskName: "recall",
      userMessage: "继续调查",
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
  );
  assert.match(
    customInterpolatedPromptBuild.systemPrompt,
    /<status_current_variable>secret=true<\/status_current_variable>/,
  );
  const customInterpolatedPayload = buildTaskLlmPayload(
    customInterpolatedPromptBuild,
    "unused fallback",
  );
  assert.equal(
    customInterpolatedPayload.promptMessages.some((message) =>
      /<status_current_variable>secret=true<\/status_current_variable>/.test(
        message.content,
      ),
    ),
    true,
  );

  const noWorldInfoBlockSettings = {
    taskProfiles: {
      recall: {
        activeProfileId: "custom",
        profiles: [
          {
            id: "custom",
            name: "无世界书显式块",
            taskType: "recall",
            builtin: false,
            blocks: [
              {
                id: "u1",
                type: "custom",
                content: "角色: {{charName}}",
                role: "user",
                enabled: true,
                order: 0,
                injectionMode: "append",
              },
            ],
          },
        ],
      },
    },
  };

  const atDepthOnlyPromptBuild = await buildTaskPrompt(
    noWorldInfoBlockSettings,
    "recall",
    {
      taskName: "recall",
      userMessage: "继续调查",
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
  );

  assert.equal(atDepthOnlyPromptBuild.debug.worldInfoRequested, true);
  assert.equal(atDepthOnlyPromptBuild.debug.worldInfoAtDepthCount, 1);
  assert.equal(atDepthOnlyPromptBuild.additionalMessages.length, 1);
  assert.equal(
    atDepthOnlyPromptBuild.additionalMessages[0].content,
    "这是一条 atDepth 消息。",
  );
  assert.deepEqual(
    atDepthOnlyPromptBuild.executionMessages.map((message) => message.role),
    ["system", "user"],
  );
  assert.equal(
    atDepthOnlyPromptBuild.executionMessages[0].content,
    "这是一条 atDepth 消息。",
  );

  const depthD4Entry = createWorldbookEntry({
    uid: 201,
    name: "深度注入 D4",
    comment: "深度注入 D4",
    content: "这是 d4 atDepth 消息。",
    positionType: "at_depth_as_system",
    depth: 4,
    order: 8,
  });
  const depthD1Entry = createWorldbookEntry({
    uid: 202,
    name: "深度注入 D1",
    comment: "深度注入 D1",
    content: "这是 d1 atDepth 消息。",
    positionType: "at_depth_as_system",
    depth: 1,
    order: 3,
  });
  worldbooksByName["main-book"].push(depthD4Entry, depthD1Entry);
  const previousGetContext = globalThis.SillyTavern.getContext;
  globalThis.SillyTavern.getContext = () => ({
    ...previousGetContext(),
    chatId: "depth-aware-chat",
  });

  const depthAwareSettings = {
    taskProfiles: {
      recall: {
        activeProfileId: "depth-aware",
        profiles: [
          {
            id: "depth-aware",
            name: "深度顺序预设",
            taskType: "recall",
            builtin: false,
            blocks: [
              {
                id: "depth-recent",
                type: "builtin",
                sourceKey: "recentMessages",
                role: "system",
                enabled: true,
                order: 0,
                injectionMode: "append",
              },
              {
                id: "depth-user",
                type: "custom",
                content: "用户问题：{{userMessage}}",
                role: "user",
                enabled: true,
                order: 1,
                injectionMode: "append",
              },
            ],
          },
        ],
      },
    },
  };

  const depthAwarePromptBuild = await buildTaskPrompt(depthAwareSettings, "recall", {
    taskName: "recall",
    userMessage: "继续调查 depth 排序",
    recentMessages: "这里会被 chatMessages 替换",
    chatMessages: [
      { seq: 11, role: "user", content: "第一句" },
      { seq: 12, role: "assistant", content: "第二句" },
    ],
    charName: "Alice",
  });

  assert.deepEqual(
    depthAwarePromptBuild.executionMessages.map((message) => message.content),
    [
      "#1 [assistant|深度注入 D4]: 这是 d4 atDepth 消息。\n\n#2 [assistant|深度注入]: 这是一条 atDepth 消息。\n\n#11 [user]: 第一句\n\n#4 [assistant|深度注入 D1]: 这是 d1 atDepth 消息。\n\n#12 [assistant]: 第二句",
      "用户问题：继续调查 depth 排序",
    ],
  );
  assert.deepEqual(
    depthAwarePromptBuild.hostInjections.atDepth.map((entry) => entry.name),
    ["深度注入 D4", "深度注入", "深度注入 D1"],
  );
  assert.deepEqual(
    depthAwarePromptBuild.hostInjectionPlan.atDepth.map((entry) => entry.entryName),
    ["深度注入 D4", "深度注入", "深度注入 D1"],
  );
  assert.equal(
    depthAwarePromptBuild.executionMessages.at(-1)?.content.includes("atDepth"),
    false,
  );
  worldbooksByName["main-book"].splice(-2, 2);
  globalThis.SillyTavern.getContext = previousGetContext;

  const { initializeHostAdapter } = await import("../host/adapter/index.js");
  const partialBridgeCalls = [];
  const partialBridgeEntriesByWorldbook = {
    "main-book": [createConstantWorldbookEntry(11, "主书原名", "主书内容。", "主书注释")],
    "side-book": [createConstantWorldbookEntry(12, "支线原名", "支线内容。", "支线注释")],
    "persona-book": [createConstantWorldbookEntry(13, "人格原名", "人格内容。", "人格注释")],
    "chat-book": [createConstantWorldbookEntry(14, "聊天原名", "聊天内容。", "聊天注释")],
  };

  globalThis.SillyTavern = {
    getContext() {
      return {
        name1: "User",
        name2: "Alice",
        chat: [{ is_user: true, mes: "我们继续调查那条线索" }],
        chatMetadata: {
          world: "chat-book",
        },
        extensionSettings: {
          persona_description_lorebook: "persona-book",
        },
      };
    },
  };
  globalThis.getCharWorldbookNames = () => ({
    primary: "main-book",
    additional: ["side-book"],
  });
  globalThis.getWorldbook = async () => {
    throw new Error(
      "legacy getWorldbook should not be used when bridge getWorldbook is available",
    );
  };
  globalThis.getLorebookEntries = async (worldbookName) =>
    (partialBridgeEntriesByWorldbook[worldbookName] || []).map((entry) => ({
      uid: entry.uid,
      comment: entry.comment,
    }));

  initializeHostAdapter({
    worldbookProvider: {
      async getWorldbook(worldbookName) {
        partialBridgeCalls.push(worldbookName);
        return partialBridgeEntriesByWorldbook[worldbookName] || [];
      },
    },
  });

  const partialBridgeWorldInfo = await resolveTaskWorldInfo({
    templateContext: {
      recentMessages: "我们继续调查那条线索",
      charName: "Alice",
    },
    userMessage: "继续调查",
  });

  assert.deepEqual(partialBridgeCalls, [
    "main-book",
    "side-book",
    "persona-book",
    "chat-book",
  ]);
  assert.deepEqual(
    partialBridgeWorldInfo.beforeEntries.map((entry) => entry.name).sort(),
    ["主书注释", "支线注释", "人格注释", "聊天注释"].sort(),
  );

  console.log("task-worldinfo tests passed");
} finally {
  if (originalSillyTavern === undefined) {
    delete globalThis.SillyTavern;
  } else {
    globalThis.SillyTavern = originalSillyTavern;
  }

  if (originalEjsTemplate === undefined) {
    delete globalThis.EjsTemplate;
  } else {
    globalThis.EjsTemplate = originalEjsTemplate;
  }

  if (originalMvu === undefined) {
    delete globalThis.Mvu;
  } else {
    globalThis.Mvu = originalMvu;
  }

  if (originalGetCharWorldbookNames === undefined) {
    delete globalThis.getCharWorldbookNames;
  } else {
    globalThis.getCharWorldbookNames = originalGetCharWorldbookNames;
  }

  if (originalGetWorldbook === undefined) {
    delete globalThis.getWorldbook;
  } else {
    globalThis.getWorldbook = originalGetWorldbook;
  }

  if (originalGetLorebookEntries === undefined) {
    delete globalThis.getLorebookEntries;
  } else {
    globalThis.getLorebookEntries = originalGetLorebookEntries;
  }

  try {
    const { initializeHostAdapter } = await import("../host/adapter/index.js");
    initializeHostAdapter({});
  } catch {
    // ignore reset failures in test cleanup
  }
}
