// ST-BME: Prompt Builder
// 统一负责任务预设块排序、变量渲染，以及世界书/EJS 上下文接入。

import { debugLog, debugWarn } from "../runtime/debug-logging.js";
import { getActiveTaskProfile, getLegacyPromptForTask } from "./prompt-profiles.js";
import {
  createEmptyInjectionSanitizerDebug,
  PROMPT_CONTENT_ORIGIN,
  sanitizeInjectionText,
  sanitizeInjectionMessages,
  sanitizeInjectionStructuredValue,
} from "./injection-sanitizer.js";
import { resolveTaskWorldInfo } from "./task-worldinfo.js";
import { applyTaskRegex } from "./task-regex.js";

const WORLD_INFO_VARIABLE_KEYS = [
  "worldInfoBefore",
  "worldInfoAfter",
  "worldInfoBeforeEntries",
  "worldInfoAfterEntries",
  "worldInfoAtDepthEntries",
  "activatedWorldInfoNames",
  "taskAdditionalMessages",
];

const INPUT_CONTEXT_MVU_FIELDS = [
  "userMessage",
  "recentMessages",
  "chatMessages",
  "dialogueText",
  "candidateText",
  "candidateNodes",
  "nodeContent",
  "eventSummary",
  "characterSummary",
  "threadSummary",
  "contradictionSummary",
  "charDescription",
  "userPersona",
];

const INPUT_REGEX_STAGE_BY_FIELD = {
  userMessage: "input.userMessage",
  recentMessages: "input.recentMessages",
  chatMessages: "input.recentMessages",
  dialogueText: "input.recentMessages",
  candidateText: "input.candidateText",
  candidateNodes: "input.candidateText",
  nodeContent: "input.candidateText",
  eventSummary: "input.candidateText",
  characterSummary: "input.candidateText",
  threadSummary: "input.candidateText",
  contradictionSummary: "input.candidateText",
};

const INPUT_REGEX_ROLE_BY_FIELD = {
  userMessage: "user",
  recentMessages: "mixed",
  chatMessages: "mixed",
  dialogueText: "mixed",
};

const INPUT_HOST_REGEX_SOURCE_BY_FIELD = {
  userMessage: "user_input",
  recentMessages: "ai_output",
  chatMessages: "ai_output",
  dialogueText: "ai_output",
  candidateText: "ai_output",
  candidateNodes: "ai_output",
  nodeContent: "ai_output",
  eventSummary: "ai_output",
  characterSummary: "ai_output",
  threadSummary: "ai_output",
  contradictionSummary: "ai_output",
  charDescription: "ai_output",
  userPersona: "user_input",
};

function cloneRuntimeDebugValue(value, fallback = null) {
  if (value == null) {
    return fallback;
  }

  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return fallback ?? value;
  }
}

function getRuntimeDebugState() {
  const stateKey = "__stBmeRuntimeDebugState";
  if (
    !globalThis[stateKey] ||
    typeof globalThis[stateKey] !== "object"
  ) {
    globalThis[stateKey] = {
      hostCapabilities: null,
      taskPromptBuilds: {},
      taskLlmRequests: {},
      injections: {},
      updatedAt: "",
    };
  }
  return globalThis[stateKey];
}

function recordTaskPromptBuild(taskType, snapshot = {}) {
  const normalizedTaskType = String(taskType || "").trim() || "unknown";
  const state = getRuntimeDebugState();
  state.taskPromptBuilds[normalizedTaskType] = {
    updatedAt: new Date().toISOString(),
    ...sanitizePromptBuildDebugSnapshot(snapshot),
  };
  state.updatedAt = new Date().toISOString();
}

function isVerboseRuntimeDebugEnabled() {
  return globalThis.__stBmeVerboseDebug === true;
}

function isLightweightHostModeEnabled() {
  return globalThis.__stBmeLightweightHostMode === true;
}

function buildPreviewText(value, maxChars = 240) {
  const effectiveMaxChars = isLightweightHostModeEnabled()
    ? Math.min(maxChars, 160)
    : maxChars;
  const text = String(value ?? "").replace(/\s+/g, " ").trim();
  if (!text) return "";
  return text.length > effectiveMaxChars
    ? `${text.slice(0, effectiveMaxChars)}...`
    : text;
}

function summarizeExecutionMessages(messages = []) {
  const list = Array.isArray(messages) ? messages : [];
  const roles = {};
  let totalChars = 0;
  const preview = [];
  for (const message of list) {
    const role = String(message?.role || "system");
    const content = String(message?.content || "");
    roles[role] = Number(roles[role] || 0) + 1;
    totalChars += content.length;
    if (preview.length < 3) {
      const compact = buildPreviewText(content, 96);
      if (compact) {
        preview.push(`${role}: ${compact}`);
      }
    }
  }
  return {
    count: list.length,
    roles,
    totalChars,
    preview,
  };
}

function compactExecutionMessages(messages = []) {
  const list = Array.isArray(messages) ? messages : [];
  return list.slice(0, 6).map((message) => ({
    role: String(message?.role || ""),
    content: buildPreviewText(message?.content || "", 160),
    source: String(message?.source || ""),
    blockId: String(message?.blockId || ""),
    blockType: String(message?.blockType || ""),
    sourceKey: String(message?.sourceKey || ""),
    regexSourceType: String(message?.regexSourceType || ""),
  }));
}

function summarizeRenderedBlocks(blocks = []) {
  const list = Array.isArray(blocks) ? blocks : [];
  return {
    count: list.length,
    preview: list.slice(0, 6).map((block) => ({
      id: String(block?.id || ""),
      name: String(block?.name || ""),
      type: String(block?.type || ""),
      role: String(block?.role || ""),
      chars: String(block?.content || "").length,
    })),
  };
}

function summarizeWorldInfoResolution(worldInfoResolution = null) {
  const source =
    worldInfoResolution &&
    typeof worldInfoResolution === "object" &&
    !Array.isArray(worldInfoResolution)
      ? worldInfoResolution
      : {};
  return {
    beforeCount: Array.isArray(source.beforeEntries) ? source.beforeEntries.length : 0,
    afterCount: Array.isArray(source.afterEntries) ? source.afterEntries.length : 0,
    atDepthCount: Array.isArray(source.atDepthEntries) ? source.atDepthEntries.length : 0,
    additionalMessageCount: Array.isArray(source.additionalMessages)
      ? source.additionalMessages.length
      : 0,
    activatedEntryNames: Array.isArray(source.activatedEntryNames)
      ? source.activatedEntryNames.slice(0, 12)
      : [],
    debug:
      source.debug && typeof source.debug === "object" && !Array.isArray(source.debug)
        ? {
            ejsRuntimeStatus: String(source.debug.ejsRuntimeStatus || ""),
            cacheHit: Boolean(source.debug.cache?.hit),
            loadMs: Number(source.debug.loadMs || 0),
          }
        : null,
  };
}

function sanitizePromptBuildDebugSnapshot(snapshot = {}) {
  const cloned = cloneRuntimeDebugValue(snapshot, {});
  if (isVerboseRuntimeDebugEnabled()) {
    return {
      ...cloned,
      debugMode: "verbose",
    };
  }

  return {
    updatedAt: new Date().toISOString(),
    debugMode: "summary",
    taskType: String(cloned?.taskType || ""),
    profileId: String(cloned?.profileId || ""),
    profileName: String(cloned?.profileName || ""),
    systemPromptPreview: buildPreviewText(cloned?.systemPrompt || ""),
    executionMessages: compactExecutionMessages(cloned?.executionMessages),
    privateTaskMessages: compactExecutionMessages(cloned?.privateTaskMessages),
    renderedBlocks: [],
    hostInjections: cloned?.hostInjections
      ? {
          before: Array.isArray(cloned.hostInjections.before)
            ? cloned.hostInjections.before.length
            : 0,
          after: Array.isArray(cloned.hostInjections.after)
            ? cloned.hostInjections.after.length
            : 0,
          atDepth: Array.isArray(cloned.hostInjections.atDepth)
            ? cloned.hostInjections.atDepth.length
            : 0,
        }
      : null,
    executionMessagesSummary: summarizeExecutionMessages(cloned?.executionMessages),
    privateTaskMessagesSummary: summarizeExecutionMessages(cloned?.privateTaskMessages),
    renderedBlocksSummary: summarizeRenderedBlocks(cloned?.renderedBlocks),
    worldInfoResolutionSummary: summarizeWorldInfoResolution(cloned?.worldInfoResolution),
    mvu: cloneRuntimeDebugValue(cloned?.mvu, null),
    inputContext: null,
    inputContextSummary:
      cloned?.inputContext && typeof cloned.inputContext === "object"
        ? {
            keys: Object.keys(cloned.inputContext).slice(0, 16),
          }
        : null,
    regexInput:
      cloned?.regexInput && typeof cloned.regexInput === "object"
        ? {
            entryCount: Array.isArray(cloned.regexInput.entries)
              ? cloned.regexInput.entries.length
              : 0,
          }
        : null,
    debug:
      cloned?.debug && typeof cloned.debug === "object"
        ? {
            renderedBlockCount: Number(cloned.debug.renderedBlockCount || 0),
            executionMessageCount: Number(cloned.debug.executionMessageCount || 0),
            hostInjectionCount: Number(cloned.debug.hostInjectionCount || 0),
            worldInfoRequested: cloned.debug.worldInfoRequested !== false,
            worldInfoCacheHit: Boolean(cloned.debug.worldInfoCacheHit),
            ejsRuntimeStatus: String(cloned.debug.ejsRuntimeStatus || ""),
          }
        : null,
  };
}

function mergeRegexCollectors(...collectors) {
  const mergedEntries = [];
  for (const collector of collectors) {
    if (!Array.isArray(collector?.entries)) {
      continue;
    }
    mergedEntries.push(...collector.entries);
  }
  return {
    entries: mergedEntries,
  };
}

export function buildTaskExecutionDebugContext(
  promptBuild = null,
  options = {},
) {
  const promptDebug = promptBuild?.debug || {};
  const worldInfoDebug =
    promptBuild?.worldInfo?.debug || promptBuild?.worldInfoResolution?.debug || {};
  const worldInfoHit =
    Number(promptDebug.worldInfoBeforeCount || 0) +
      Number(promptDebug.worldInfoAfterCount || 0) +
      Number(promptDebug.worldInfoAtDepthCount || 0) >
    0;

  return {
    promptAssembly: {
      mode: "ordered-private-messages",
      hostInjectionPlanMode:
        promptDebug.hostInjectionPlanMode || "diagnostic-plan-only",
      privateTaskMessageCount: Number(
        promptDebug.executionMessageCount ??
          promptBuild?.executionMessages?.length ??
          promptDebug.privateTaskMessageCount ??
          promptBuild?.privateTaskMessages?.length ??
          0,
      ),
    },
    promptBuild: {
      taskType: String(promptDebug.taskType || ""),
      profileId: String(promptDebug.profileId || ""),
      profileName: String(promptDebug.profileName || ""),
      renderedBlockCount: Number(promptDebug.renderedBlockCount || 0),
      privateTaskMessageCount: Number(promptDebug.privateTaskMessageCount || 0),
    },
    effectiveDelivery:
      promptDebug.effectiveDelivery && typeof promptDebug.effectiveDelivery === "object"
        ? cloneRuntimeDebugValue(promptDebug.effectiveDelivery, {})
        : null,
    ejsRuntimeStatus: String(
      promptDebug.ejsRuntimeStatus || worldInfoDebug.ejsRuntimeStatus || "",
    ),
    worldInfo: {
      requested: promptDebug.worldInfoRequested !== false,
      hit: worldInfoHit,
      cacheHit: Boolean(promptDebug.worldInfoCacheHit),
      beforeCount: Number(promptDebug.worldInfoBeforeCount || 0),
      afterCount: Number(promptDebug.worldInfoAfterCount || 0),
      atDepthCount: Number(promptDebug.worldInfoAtDepthCount || 0),
      loadMs: Number(worldInfoDebug.loadMs || 0),
    },
    mvu:
      promptDebug.mvu && typeof promptDebug.mvu === "object"
        ? cloneRuntimeDebugValue(promptDebug.mvu, {})
        : null,
    inputContext:
      promptDebug.inputContext && typeof promptDebug.inputContext === "object"
        ? cloneRuntimeDebugValue(promptDebug.inputContext, {})
        : null,
    regexInput:
      (() => {
        const merged = mergeRegexCollectors(
          promptBuild?.regexInput,
          options.regexInput,
        );
        return Array.isArray(merged.entries) && merged.entries.length > 0
          ? cloneRuntimeDebugValue(merged, {})
          : null;
      })(),
  };
}

function getByPath(target, path) {
  return String(path || "")
    .split(".")
    .filter(Boolean)
    .reduce((acc, key) => (acc == null ? undefined : acc[key]), target);
}

function normalizeRole(role) {
  const value = String(role || "system").toLowerCase();
  if (["system", "user", "assistant"].includes(value)) {
    return value;
  }
  return "system";
}

function normalizeInjectionMode(mode) {
  const value = String(mode || "append").toLowerCase();
  if (["prepend", "append", "relative"].includes(value)) {
    return value;
  }
  return "append";
}

function createExecutionMessage(
  role,
  content,
  extra = {},
) {
  const trimmedContent = String(content || "").trim();
  if (!trimmedContent) {
    return null;
  }
  return {
    role: normalizeRole(role),
    content: trimmedContent,
    ...extra,
  };
}

function isCustomWorldInfoFilterEnabled(settings = {}) {
  return String(settings?.worldInfoFilterMode || "default").trim() === "custom";
}

function usesWorldInfoSourceKey(sourceKey = "") {
  return [
    "worldInfoBefore",
    "worldInfoAfter",
    "worldInfoBeforeEntries",
    "worldInfoAfterEntries",
    "worldInfoAtDepthEntries",
    "activatedWorldInfoNames",
    "taskAdditionalMessages",
  ].includes(String(sourceKey || ""));
}

function blockUsesWorldInfoContent(block = {}) {
  if (usesWorldInfoSourceKey(block?.sourceKey)) {
    return true;
  }
  const content = String(block?.content || "");
  return /\{\{\s*(worldInfoBefore|worldInfoAfter|worldInfoBeforeEntries|worldInfoAfterEntries|worldInfoAtDepthEntries|activatedWorldInfoNames|taskAdditionalMessages)\s*\}\}/.test(
    content,
  );
}

function messageUsesWorldInfoContent(message = {}) {
  if (message?.derivedFromWorldInfo === true) {
    return true;
  }
  if (usesWorldInfoSourceKey(message?.sourceKey)) {
    return true;
  }
  return String(message?.source || "") === "worldInfo-atDepth";
}

function getOptionalFiniteNumber(value) {
  if (value === null || value === undefined || value === "") {
    return null;
  }
  const numericValue = Number(value);
  return Number.isFinite(numericValue) ? numericValue : null;
}

function getPromptMessageLikeDescriptor(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return null;
  }

  if (typeof value.content === "string") {
    const role = String(value.role || "assistant").trim().toLowerCase();
    const speaker = String(
      value.speaker || value.name || value.displayName || "",
    ).trim();
    return {
      content: String(value.content || ""),
      role: role === "user" ? "user" : "assistant",
      seq: getOptionalFiniteNumber(value.seq),
      speaker,
      hideSpeakerLabel: value?.hideSpeakerLabel === true,
      isContextOnly:
        typeof value.isContextOnly === "boolean" ? value.isContextOnly : null,
    };
  }

  if (typeof value.mes === "string") {
    const speaker = String(
      value.speaker || value.name || value.displayName || "",
    ).trim();
    return {
      content: String(value.mes || ""),
      role: value.is_user === true ? "user" : "assistant",
      seq: getOptionalFiniteNumber(value.seq),
      speaker,
      hideSpeakerLabel: value?.hideSpeakerLabel === true,
      isContextOnly:
        typeof value.isContextOnly === "boolean" ? value.isContextOnly : null,
    };
  }

  return null;
}

function isPromptMessageArray(value) {
  return (
    Array.isArray(value) &&
    value.length > 0 &&
    value.every((entry) => getPromptMessageLikeDescriptor(entry))
  );
}

export const EXTRACTION_CONTEXT_REVIEW_HEADER =
  "--- 以下是上下文回顾（已提取过），仅供理解剧情 ---";
export const EXTRACTION_TARGET_CONTENT_HEADER =
  "--- 以下是本次需要提取记忆的新对话内容 ---";
export const RECALL_TARGET_CONTENT_HEADER =
  "--- 以下是本次需要召回记忆的新对话内容 ---";

function getPromptMessageContextGroup(value) {
  const descriptor = getPromptMessageLikeDescriptor(value);
  if (!descriptor || typeof descriptor.isContextOnly !== "boolean") {
    return null;
  }
  return descriptor.isContextOnly ? "context" : "target";
}

function getPromptMessageContextHeader(group = "") {
  if (group === "context") {
    return EXTRACTION_CONTEXT_REVIEW_HEADER;
  }
  if (group === "target") {
    return EXTRACTION_TARGET_CONTENT_HEADER;
  }
  return "";
}

function formatPromptMessageTranscript(value) {
  const entries = Array.isArray(value) ? value : [value];
  const hasContextMessages = entries.some(
    (entry) => getPromptMessageContextGroup(entry) === "context",
  );
  const hasTargetMessages = entries.some(
    (entry) => getPromptMessageContextGroup(entry) === "target",
  );
  const lines = [];
  let activeGroup = null;

  for (let index = 0; index < entries.length; index += 1) {
    const entry = entries[index];
    const descriptor = getPromptMessageLikeDescriptor(entry);
    if (!descriptor) {
      continue;
    }
    const group = getPromptMessageContextGroup(entry);
    if (hasContextMessages && hasTargetMessages && group && group !== activeGroup) {
      lines.push(getPromptMessageContextHeader(group));
      activeGroup = group;
    }
    const seqLabel =
      descriptor.seq != null ? `#${descriptor.seq}` : `#${index + 1}`;
    const speakerLabel = !descriptor.hideSpeakerLabel && descriptor.speaker
      ? `|${descriptor.speaker}`
      : "";
    lines.push(`${seqLabel} [${descriptor.role}${speakerLabel}]: ${descriptor.content}`);
  }

  return lines.filter(Boolean).join("\n\n");
}

function stringifyInterpolatedValue(value) {
  if (value == null) return "";
  if (typeof value === "string") return value;
  if (typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }
  if (getPromptMessageLikeDescriptor(value)) {
    return formatPromptMessageTranscript(value);
  }
  if (isPromptMessageArray(value)) {
    return formatPromptMessageTranscript(value);
  }

  try {
    return JSON.stringify(value, null, 2);
  } catch {
    return String(value);
  }
}

function buildEmptyWorldInfoContext() {
  return {
    worldInfoBefore: "",
    worldInfoAfter: "",
    worldInfoBeforeEntries: [],
    worldInfoAfterEntries: [],
    worldInfoAtDepthEntries: [],
    activatedWorldInfoNames: [],
    taskAdditionalMessages: [],
    worldInfoDebug: null,
  };
}

function createEmptyMvuPromptDebug() {
  return createEmptyInjectionSanitizerDebug();
}

function pushMvuPromptDebugEntry(debugState, entry = {}) {
  if (!debugState || !entry || (!entry.changed && !entry.dropped)) {
    return;
  }

  debugState.sanitizedFields.push({
    name: String(entry.name || ""),
    stage: String(entry.stage || ""),
    changed: Boolean(entry.changed),
    dropped: Boolean(entry.dropped),
    reasons: Array.isArray(entry.reasons) ? [...entry.reasons] : [],
    blockedHitCount: Number(entry.blockedHitCount || 0),
  });
  debugState.sanitizedFieldCount = debugState.sanitizedFields.length;
}

function sanitizeTaskPromptText(
  settings = {},
  taskType,
  text,
  {
    mode = "injection-safe",
    blockedContents = [],
    regexStage = "",
    role = "system",
    regexCollector = null,
    applyMvu = true,
    contentOrigin = PROMPT_CONTENT_ORIGIN.HOST_INJECTED,
    sanitizationEligible = true,
    regexSourceType = "",
  } = {},
) {
  const sanitized = sanitizeInjectionText(settings, taskType, text, {
    mode:
      String(mode || "").trim() === "final-safe"
        ? "final-injection-safe"
        : "injection-safe",
    blockedContents,
    contentOrigin,
    sanitizationEligible,
    regexSourceType,
    role,
    regexCollector,
    applySanitizer: applyMvu,
    applyHostRegex: Boolean(regexSourceType),
  });
  const finalText = regexStage
    ? applyTaskRegex(
        settings,
        taskType,
        regexStage,
        sanitized.text,
        regexCollector,
        role,
      )
    : sanitized.text;

  return {
    ...sanitized,
    text: finalText,
    changed: finalText !== String(text || ""),
  };
}

function joinStructuredPath(basePath = "", segment = "") {
  const normalizedSegment = String(segment || "");
  if (!normalizedSegment) {
    return basePath;
  }
  if (!basePath) {
    return normalizedSegment.startsWith("[")
      ? normalizedSegment.slice(1, -1)
      : normalizedSegment;
  }
  return normalizedSegment.startsWith("[")
    ? `${basePath}${normalizedSegment}`
    : `${basePath}.${normalizedSegment}`;
}

function looksLikeMvuStateContainer(value, seen = new WeakSet()) {
  if (!value || typeof value !== "object") {
    return false;
  }
  if (seen.has(value)) {
    return false;
  }
  seen.add(value);

  if (Array.isArray(value)) {
    return value.some((item) => looksLikeMvuStateContainer(item, seen));
  }

  const keys = Object.keys(value).map((key) =>
    String(key || "").trim().toLowerCase(),
  );
  if (
    keys.some((key) =>
      ["stat_data", "display_data", "delta_data", "$internal"].includes(key),
    )
  ) {
    return true;
  }

  return Object.values(value).some((item) =>
    looksLikeMvuStateContainer(item, seen),
  );
}

function getMvuObjectKeyStripReason(key, value) {
  const normalizedKey = String(key || "").trim().toLowerCase();
  if (
    ["stat_data", "display_data", "delta_data", "$internal"].includes(
      normalizedKey,
    )
  ) {
    return "mvu_state_key_removed";
  }
  if (
    ["variables", "message_variables", "chat_variables"].includes(normalizedKey) &&
    looksLikeMvuStateContainer(value)
  ) {
    return "mvu_variables_container_removed";
  }
  return "";
}

function sanitizeStructuredPromptValue(
  settings = {},
  taskType,
  value,
  {
    fieldName = "",
    path = fieldName,
    mode = "aggressive",
    blockedContents = [],
    regexStage = "",
    role = "system",
    debugState = null,
    regexCollector = null,
    applyMvu = true,
    stripMvuContainers = true,
    seen = new WeakSet(),
  } = {},
) {
  if (typeof value === "string") {
    const sanitized = sanitizeTaskPromptText(settings, taskType, value, {
      mode,
      blockedContents,
      regexStage,
      role,
      regexCollector,
      applyMvu,
    });
    pushMvuPromptDebugEntry(debugState, {
      name: path || fieldName,
      stage: regexStage,
      ...sanitized,
    });
    return {
      value: sanitized.text,
      changed: Boolean(sanitized.changed || sanitized.dropped),
      omit:
        !String(sanitized.text || "").trim() &&
        String(value || "").trim().length > 0,
    };
  }

  if (Array.isArray(value)) {
    const sanitizedArray = [];
    let changed = false;
    for (let index = 0; index < value.length; index += 1) {
      const childResult = sanitizeStructuredPromptValue(
        settings,
        taskType,
        value[index],
        {
          fieldName,
          path: joinStructuredPath(path, `[${index}]`),
          mode,
          blockedContents,
          regexStage,
          role,
          debugState,
          regexCollector,
          applyMvu,
          stripMvuContainers,
          seen,
        },
      );
      if (childResult.omit) {
        changed = true;
        continue;
      }
      sanitizedArray.push(childResult.value);
      if (childResult.changed) {
        changed = true;
      }
    }
    return {
      value: sanitizedArray,
      changed: changed || sanitizedArray.length !== value.length,
      omit: value.length > 0 && sanitizedArray.length === 0,
    };
  }

  if (value && typeof value === "object") {
    if (seen.has(value)) {
      return {
        value,
        changed: false,
        omit: false,
      };
    }
    seen.add(value);

    const originalLooksMvuContainer = looksLikeMvuStateContainer(value);
    const sanitizedObject = {};
    let changed = false;
    let keptEntries = 0;

    for (const [key, entryValue] of Object.entries(value)) {
      const stripReason = stripMvuContainers
        ? getMvuObjectKeyStripReason(key, entryValue)
        : "";
      if (stripReason) {
        changed = true;
        pushMvuPromptDebugEntry(debugState, {
          name: joinStructuredPath(path, key),
          stage: regexStage,
          changed: true,
          dropped: true,
          reasons: [stripReason],
          blockedHitCount: 0,
        });
        continue;
      }

      const childResult = sanitizeStructuredPromptValue(
        settings,
        taskType,
        entryValue,
        {
          fieldName,
          path: joinStructuredPath(path, key),
          mode,
          blockedContents,
          regexStage,
          role,
          debugState,
          regexCollector,
          applyMvu,
          stripMvuContainers,
          seen,
        },
      );
      if (childResult.omit) {
        changed = true;
        continue;
      }
      sanitizedObject[key] = childResult.value;
      keptEntries += 1;
      if (childResult.changed) {
        changed = true;
      }
    }

    return {
      value: sanitizedObject,
      changed,
      omit: originalLooksMvuContainer && keptEntries === 0,
    };
  }

  return {
    value,
    changed: false,
    omit: false,
  };
}

function sanitizePromptMessages(
  settings = {},
  taskType,
  messages = [],
  {
    blockedContents = [],
    debugState = null,
    regexCollector = null,
    applySanitizer = null,
  } = {},
) {
  const preparedMessages = (Array.isArray(messages) ? messages : []).map(
    (message, index) => {
      if (!message || typeof message !== "object") {
        return message;
      }
      const shouldSanitize =
        typeof applySanitizer === "function"
          ? applySanitizer(message, index)
          : applySanitizer;
      if (shouldSanitize === false) {
        return {
          ...message,
          sanitizationEligible: false,
        };
      }
      return message;
    },
  );

  return sanitizeInjectionMessages(settings, taskType, preparedMessages, {
    blockedContents,
    debugState,
    regexCollector,
  })
    .map((message) =>
      createExecutionMessage(message.role, message.content, {
        source: String(message?.source || ""),
        blockId: String(message?.blockId || ""),
        blockName: String(message?.blockName || ""),
        blockType: String(message?.blockType || ""),
        sourceKey: String(message?.sourceKey || ""),
        injectionMode: String(message?.injectionMode || ""),
        derivedFromWorldInfo: message?.derivedFromWorldInfo === true,
        contentOrigin: String(message?.contentOrigin || ""),
        sanitizationEligible: message?.sanitizationEligible === true,
        regexSourceType: String(message?.regexSourceType || ""),
      }),
    )
    .filter(Boolean);
}

function resolveStructuredMessageSanitizerInput(fieldName = "", context = {}, value) {
  const normalizedFieldName = String(fieldName || "").trim();
  if (!["recentMessages", "dialogueText"].includes(normalizedFieldName)) {
    return {
      value,
      renderAsTranscript: false,
    };
  }

  if (
    typeof value === "string" &&
    Array.isArray(context?.chatMessages) &&
    isPromptMessageArray(context.chatMessages)
  ) {
    return {
      value: context.chatMessages,
      renderAsTranscript: true,
    };
  }

  return {
    value,
    renderAsTranscript: false,
  };
}

function sanitizePromptContextInputs(
  settings = {},
  taskType,
  context = {},
  debugState = null,
  regexCollector = null,
  options = {},
) {
  const sanitizedContext = {
    ...context,
  };
  const {
    applyMvu = true,
    stripMvuContainers = applyMvu,
  } = options || {};

  const applyLocalRegexToStructuredValue = (
    value,
    regexStage,
    regexRole,
    seen = new WeakSet(),
  ) => {
    if (!regexStage) {
      return value;
    }
    if (typeof value === "string") {
      return applyTaskRegex(
        settings,
        taskType,
        regexStage,
        value,
        regexCollector,
        regexRole,
      );
    }
    if (Array.isArray(value)) {
      return value.map((item) =>
        applyLocalRegexToStructuredValue(item, regexStage, regexRole, seen),
      );
    }
    if (value && typeof value === "object") {
      if (seen.has(value)) {
        return value;
      }
      seen.add(value);
      const messageDescriptor = getPromptMessageLikeDescriptor(value);
      if (messageDescriptor) {
        const contentKey = typeof value.content === "string"
          ? "content"
          : typeof value.mes === "string"
            ? "mes"
            : "";
        const messageRole = messageDescriptor.role === "user"
          ? "user"
          : messageDescriptor.role === "assistant"
            ? "assistant"
            : regexRole;
        return Object.fromEntries(
          Object.entries(value).map(([key, entryValue]) => [
            key,
            key === contentKey
              ? applyLocalRegexToStructuredValue(
                  entryValue,
                  regexStage,
                  messageRole,
                  seen,
                )
              : entryValue,
          ]),
        );
      }
      return Object.fromEntries(
        Object.entries(value).map(([key, entryValue]) => [
          key,
          applyLocalRegexToStructuredValue(
            entryValue,
            regexStage,
            regexRole,
            seen,
          ),
        ]),
      );
    }
    return value;
  };

  for (const fieldName of INPUT_CONTEXT_MVU_FIELDS) {
    if (!(fieldName in sanitizedContext)) {
      continue;
    }
    const value = sanitizedContext[fieldName];
    const structuredSanitizerInput = resolveStructuredMessageSanitizerInput(
      fieldName,
      context,
      value,
    );
    const valueForSanitizer = structuredSanitizerInput.value;
    const regexStage = INPUT_REGEX_STAGE_BY_FIELD[fieldName] || "";
    const regexRole = INPUT_REGEX_ROLE_BY_FIELD[fieldName] || "system";
    const regexSourceType = INPUT_HOST_REGEX_SOURCE_BY_FIELD[fieldName] || "";
    const sanitized = sanitizeInjectionStructuredValue(
      settings,
      taskType,
      valueForSanitizer,
      {
        fieldName,
        path: fieldName,
        mode: "injection-safe",
        contentOrigin: PROMPT_CONTENT_ORIGIN.HOST_INJECTED,
        sanitizationEligible: true,
        regexSourceType,
        role: regexRole,
        debugState,
        regexCollector,
        applySanitizer: applyMvu,
        applyHostRegex: Boolean(regexSourceType),
        stripMvuContainers,
      },
    );
    let sanitizedValue = sanitized.omit
      ? Array.isArray(valueForSanitizer)
        ? []
        : typeof valueForSanitizer === "string"
          ? ""
          : null
      : sanitized.value;
    sanitizedValue = applyLocalRegexToStructuredValue(
      sanitizedValue,
      regexStage,
      regexRole,
    );
    if (structuredSanitizerInput.renderAsTranscript) {
      sanitizedValue = stringifyInterpolatedValue(sanitizedValue);
    }
    sanitizedContext[fieldName] = sanitizedValue;
  }

  return sanitizedContext;
}

function sanitizeWorldInfoEntries(
  settings = {},
  taskType,
  entries = [],
  blockedContents = [],
  debugState = null,
  regexCollector = null,
  { applyMvu = true } = {},
) {
  return (Array.isArray(entries) ? entries : [])
    .map((entry, index) => {
      const sanitized = sanitizeInjectionText(
        settings,
        taskType,
        String(entry?.content || ""),
        {
          mode: "injection-safe",
          blockedContents,
          contentOrigin: PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
          sanitizationEligible: true,
          regexSourceType: "world_info",
          role: entry?.role || "system",
          regexCollector,
          applySanitizer: applyMvu,
          applyHostRegex: true,
          path: `worldInfo[${index}]`,
          stage: "world-info-rendered",
        },
      );
      debugState.worldInfoBlockedContentHits += sanitized.blockedHitCount;
      if (sanitized.changed || sanitized.dropped) {
        debugState.finalMessageStripCount += 1;
      }
      if (!sanitized.text.trim()) {
        return null;
      }
      return {
        ...entry,
        content: sanitized.text,
        index:
          Number.isFinite(Number(entry?.index))
            ? Number(entry.index)
            : index,
      };
    })
    .filter(Boolean);
}

function sanitizeWorldInfoContext(
  settings = {},
  taskType,
  worldInfo = null,
  debugState = null,
  regexCollector = null,
) {
  const isCustomFilter = isCustomWorldInfoFilterEnabled(settings);
  const rawDebug =
    worldInfo?.debug && typeof worldInfo.debug === "object"
      ? worldInfo.debug
      : null;
  const blockedContentsCount = Number(rawDebug?.mvu?.blockedContentsCount || 0);
  const blockedContents = [];
  if (blockedContentsCount > 0 && Array.isArray(rawDebug?.mvu?.filteredEntries)) {
    // Use only the structural count for debug; blocked content strings stay internal
    // on the world info object via the non-enumerable runtime property below.
  }

  const runtimeBlockedContents = Array.isArray(worldInfo?.__mvuBlockedContents)
    ? worldInfo.__mvuBlockedContents
    : [];

  const beforeEntries = sanitizeWorldInfoEntries(
    settings,
    taskType,
    worldInfo?.beforeEntries,
    runtimeBlockedContents,
    debugState,
    regexCollector,
    { applyMvu: !isCustomFilter },
  );
  const afterEntries = sanitizeWorldInfoEntries(
    settings,
    taskType,
    worldInfo?.afterEntries,
    runtimeBlockedContents,
    debugState,
    regexCollector,
    { applyMvu: !isCustomFilter },
  );
  const atDepthEntries = sanitizeWorldInfoEntries(
    settings,
    taskType,
    worldInfo?.atDepthEntries,
    runtimeBlockedContents,
    debugState,
    regexCollector,
    { applyMvu: !isCustomFilter },
  );
  const additionalMessages = (Array.isArray(worldInfo?.additionalMessages)
    ? worldInfo.additionalMessages
    : []
  )
    .map((message) => {
      const sanitized = sanitizeInjectionText(
        settings,
        taskType,
        String(message?.content || ""),
        {
          mode: "injection-safe",
          blockedContents: runtimeBlockedContents,
          contentOrigin: PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
          sanitizationEligible: true,
          regexSourceType: "world_info",
          role: message?.role || "system",
          regexCollector,
          applySanitizer: !isCustomFilter,
          applyHostRegex: true,
          path: "taskAdditionalMessages",
          stage: "world-info-rendered",
        },
      );
      debugState.worldInfoBlockedContentHits += sanitized.blockedHitCount;
      if (sanitized.changed || sanitized.dropped) {
        debugState.finalMessageStripCount += 1;
      }
      if (!sanitized.text.trim()) {
        return null;
      }
      return {
        ...message,
        content: sanitized.text,
        source: String(message?.source || "worldInfo-atDepth"),
        sourceKey: String(message?.sourceKey || "taskAdditionalMessages"),
        contentOrigin: PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
        sanitizationEligible: true,
        regexSourceType: "world_info",
      };
    })
    .filter(Boolean);

  const beforeText = beforeEntries.map((entry) => entry.content).join("\n\n");
  const afterText = afterEntries.map((entry) => entry.content).join("\n\n");
  const activatedEntryNames = [
    ...beforeEntries.map((entry) => entry.name),
    ...afterEntries.map((entry) => entry.name),
    ...atDepthEntries.map((entry) => entry.name),
  ].filter(Boolean);

  const sanitizedWorldInfo = {
    beforeEntries,
    afterEntries,
    atDepthEntries,
    beforeText,
    afterText,
    additionalMessages,
    activatedEntryNames: [...new Set(activatedEntryNames)],
    debug: rawDebug,
  };

  Object.defineProperty(sanitizedWorldInfo, "__mvuBlockedContents", {
    value: [...runtimeBlockedContents],
    configurable: true,
    enumerable: false,
    writable: false,
  });

  return sanitizedWorldInfo;
}

function createHostInjectionEntry(
  entry = {},
  position = "after",
  source = "worldInfo",
) {
  return {
    source,
    position,
    role: normalizeRole(entry.role),
    content: String(entry.content || "").trim(),
    name: String(entry.name || ""),
    sourceName: String(entry.sourceName || entry.name || ""),
    worldbook: String(entry.worldbook || ""),
    depth:
      position === "atDepth" && Number.isFinite(Number(entry.depth))
        ? Number(entry.depth)
        : null,
    order: Number.isFinite(Number(entry.order)) ? Number(entry.order) : 0,
  };
}

function buildWorldInfoResolution(worldInfoContext = {}) {
  const beforeEntries = Array.isArray(worldInfoContext.worldInfoBeforeEntries)
    ? worldInfoContext.worldInfoBeforeEntries
    : [];
  const afterEntries = Array.isArray(worldInfoContext.worldInfoAfterEntries)
    ? worldInfoContext.worldInfoAfterEntries
    : [];
  const atDepthEntries = Array.isArray(worldInfoContext.worldInfoAtDepthEntries)
    ? worldInfoContext.worldInfoAtDepthEntries
    : [];
  const additionalMessages = Array.isArray(worldInfoContext.taskAdditionalMessages)
    ? worldInfoContext.taskAdditionalMessages
    : [];

  return {
    beforeText: String(worldInfoContext.worldInfoBefore || ""),
    afterText: String(worldInfoContext.worldInfoAfter || ""),
    beforeEntries,
    afterEntries,
    atDepthEntries,
    activatedEntryNames: Array.isArray(worldInfoContext.activatedWorldInfoNames)
      ? worldInfoContext.activatedWorldInfoNames
      : [],
    additionalMessages,
    debug:
      worldInfoContext.worldInfoDebug &&
      typeof worldInfoContext.worldInfoDebug === "object"
        ? worldInfoContext.worldInfoDebug
        : null,
    injections: {
      before: beforeEntries
        .map((entry) => createHostInjectionEntry(entry, "before"))
        .filter((entry) => entry.content),
      after: afterEntries
        .map((entry) => createHostInjectionEntry(entry, "after"))
        .filter((entry) => entry.content),
      atDepth: atDepthEntries
        .map((entry) => createHostInjectionEntry(entry, "atDepth"))
        .filter((entry) => entry.content),
    },
  };
}

function sortInjectionEntries(entries = []) {
  return [...entries].sort((left, right) => {
    const orderLeft = Number.isFinite(Number(left?.order))
      ? Number(left.order)
      : 0;
    const orderRight = Number.isFinite(Number(right?.order))
      ? Number(right.order)
      : 0;
    return orderLeft - orderRight;
  });
}

function sortAtDepthInjectionEntries(entries = []) {
  return [...entries].sort((left, right) => {
    const depthLeft = Number.isFinite(Number(left?.depth))
      ? Number(left.depth)
      : 0;
    const depthRight = Number.isFinite(Number(right?.depth))
      ? Number(right.depth)
      : 0;
    const orderLeft = Number.isFinite(Number(left?.order))
      ? Number(left.order)
      : 0;
    const orderRight = Number.isFinite(Number(right?.order))
      ? Number(right.order)
      : 0;
    const uidLeft = Number.isFinite(Number(left?.uid))
      ? Number(left.uid)
      : Number.NEGATIVE_INFINITY;
    const uidRight = Number.isFinite(Number(right?.uid))
      ? Number(right.uid)
      : Number.NEGATIVE_INFINITY;
    const indexLeft = Number.isFinite(Number(left?.index))
      ? Number(left.index)
      : 0;
    const indexRight = Number.isFinite(Number(right?.index))
      ? Number(right.index)
      : 0;
    return (
      depthRight - depthLeft ||
      orderLeft - orderRight ||
      uidRight - uidLeft ||
      indexLeft - indexRight
    );
  });
}

function createHostInjectionPlanEntry(block = {}, position, extra = {}) {
  return {
    source: "block",
    origin: "profile-block",
    position,
    role: normalizeRole(block.role),
    content: String(block.content || "").trim(),
    blockId: String(block.id || ""),
    blockName: String(block.name || ""),
    sourceKey: String(block.sourceKey || ""),
    injectionMode: normalizeInjectionMode(block.injectionMode),
    order: Number.isFinite(Number(block.order)) ? Number(block.order) : 0,
    ...extra,
  };
}

function buildHostInjectionPlan(renderedBlocks = [], worldInfoResolution = {}) {
  const beforeEntryNames = (
    Array.isArray(worldInfoResolution.beforeEntries)
      ? worldInfoResolution.beforeEntries
      : []
  )
    .map((entry) => String(entry?.name || entry?.sourceName || "").trim())
    .filter(Boolean);
  const afterEntryNames = (
    Array.isArray(worldInfoResolution.afterEntries)
      ? worldInfoResolution.afterEntries
      : []
  )
    .map((entry) => String(entry?.name || entry?.sourceName || "").trim())
    .filter(Boolean);
  const atDepthEntries = Array.isArray(worldInfoResolution.injections?.atDepth)
    ? worldInfoResolution.injections.atDepth
    : [];

  const plan = {
    before: [],
    after: [],
    atDepth: [],
  };

  for (const block of renderedBlocks) {
    if (!block?.content) continue;

    if (
      block.type === "builtin" &&
      String(block.sourceKey || "") === "worldInfoBefore"
    ) {
      plan.before.push(
        createHostInjectionPlanEntry(block, "before", {
          entryNames: beforeEntryNames,
          entryCount: beforeEntryNames.length,
        }),
      );
      continue;
    }

    if (
      block.type === "builtin" &&
      String(block.sourceKey || "") === "worldInfoAfter"
    ) {
      plan.after.push(
        createHostInjectionPlanEntry(block, "after", {
          entryNames: afterEntryNames,
          entryCount: afterEntryNames.length,
        }),
      );
    }
  }

  for (const entry of atDepthEntries) {
    if (!entry?.content) continue;
    plan.atDepth.push({
      ...entry,
      origin: "worldInfo-entry",
      entryName: String(entry.name || entry.sourceName || "").trim(),
    });
  }

  return {
    before: sortInjectionEntries(plan.before),
    after: sortInjectionEntries(plan.after),
    atDepth: sortAtDepthInjectionEntries(plan.atDepth),
  };
}

function createInjectedAtDepthChatMessage(message = {}) {
  const descriptor = getPromptMessageLikeDescriptor(message);
  if (!descriptor) {
    return null;
  }
  return {
    ...(message && typeof message === "object" ? message : {}),
    role: descriptor.role,
    content: descriptor.content,
    seq: descriptor.seq,
    uid: Number.isFinite(Number(message?.uid))
      ? Number(message.uid)
      : null,
    index: Number.isFinite(Number(message?.index))
      ? Number(message.index)
      : null,
    name: String(message?.name || ""),
    sourceName: String(message?.sourceName || ""),
    worldbook: String(message?.worldbook || ""),
    source: String(message?.source || "worldInfo-atDepth"),
    sourceKey: String(message?.sourceKey || "taskAdditionalMessages"),
    derivedFromWorldInfo: true,
    contentOrigin:
      String(message?.contentOrigin || "") ||
      PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
    sanitizationEligible: message?.sanitizationEligible === true,
    regexSourceType: String(message?.regexSourceType || "world_info"),
    depth: Number.isFinite(Number(message?.depth))
      ? Number(message.depth)
      : 0,
    order: Number.isFinite(Number(message?.order))
      ? Number(message.order)
      : 0,
  };
}

function injectAtDepthMessagesIntoChatMessages(
  chatMessages = [],
  atDepthMessages = [],
) {
  const normalizedChatMessages = (Array.isArray(chatMessages) ? chatMessages : [])
    .map((message) => {
      const descriptor = getPromptMessageLikeDescriptor(message);
      if (!descriptor) return null;
      return {
        ...(message && typeof message === "object" ? message : {}),
        role: descriptor.role,
        content: descriptor.content,
        seq: descriptor.seq,
      };
    })
    .filter(Boolean);
  if (normalizedChatMessages.length === 0) {
    return null;
  }

  const groupedByDepth = new Map();
  for (const message of sortAtDepthInjectionEntries(atDepthMessages)) {
    const injectedMessage = createInjectedAtDepthChatMessage(message);
    if (!injectedMessage) continue;
    const depth = Math.max(0, Number(injectedMessage.depth || 0));
    if (!groupedByDepth.has(depth)) {
      groupedByDepth.set(depth, []);
    }
    groupedByDepth.get(depth).push(injectedMessage);
  }
  if (groupedByDepth.size === 0) {
    return normalizedChatMessages;
  }

  const reversedMessages = [...normalizedChatMessages].reverse();
  const sortedDepths = [...groupedByDepth.keys()].sort((left, right) => left - right);
  let totalInsertedMessages = 0;

  for (const depth of sortedDepths) {
    const depthMessages = groupedByDepth.get(depth) || [];
    if (depthMessages.length === 0) continue;
    const injectIndex = Math.min(
      Math.max(0, depth + totalInsertedMessages),
      reversedMessages.length,
    );
    reversedMessages.splice(injectIndex, 0, ...depthMessages);
    totalInsertedMessages += depthMessages.length;
  }

  return reversedMessages.reverse();
}

function getPromptFieldContentOrigin(sourceKey = "") {
  const normalizedSourceKey = String(sourceKey || "").trim();
  if (!normalizedSourceKey) {
    return PROMPT_CONTENT_ORIGIN.TEMPLATE_OWNED;
  }
  if (WORLD_INFO_VARIABLE_KEYS.includes(normalizedSourceKey)) {
    return PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED;
  }
  if (INPUT_CONTEXT_MVU_FIELDS.includes(normalizedSourceKey)) {
    return PROMPT_CONTENT_ORIGIN.HOST_INJECTED;
  }
  return PROMPT_CONTENT_ORIGIN.TEMPLATE_OWNED;
}

function getPromptFieldRegexSourceType(sourceKey = "") {
  const normalizedSourceKey = String(sourceKey || "").trim();
  if (!normalizedSourceKey) {
    return "";
  }
  if (WORLD_INFO_VARIABLE_KEYS.includes(normalizedSourceKey)) {
    return "world_info";
  }
  return INPUT_HOST_REGEX_SOURCE_BY_FIELD[normalizedSourceKey] || "";
}

function blockIsPureInjectedContent(block = {}) {
  return (
    block?.type === "builtin" &&
    !String(block?.content || "").trim() &&
    String(block?.sourceKey || "").trim().length > 0
  );
}

function describeBlockContentOwnership(block = {}) {
  const contentOrigin = blockIsPureInjectedContent(block)
    ? getPromptFieldContentOrigin(block.sourceKey)
    : PROMPT_CONTENT_ORIGIN.TEMPLATE_OWNED;
  return {
    contentOrigin,
    sanitizationEligible:
      contentOrigin !== PROMPT_CONTENT_ORIGIN.TEMPLATE_OWNED,
    regexSourceType:
      contentOrigin === PROMPT_CONTENT_ORIGIN.TEMPLATE_OWNED
        ? ""
        : getPromptFieldRegexSourceType(block.sourceKey),
  };
}

function resolveBlockDelivery(block = {}) {
  return normalizeRole(block.role) === "system"
    ? "private.system"
    : "private.message";
}

function getBlockDiagnosticInjectionPosition(block = {}) {
  if (
    block.type === "builtin" &&
    String(block.sourceKey || "") === "worldInfoBefore"
  ) {
    return "before";
  }
  if (
    block.type === "builtin" &&
    String(block.sourceKey || "") === "worldInfoAfter"
  ) {
    return "after";
  }
  return "";
}

function profileRequiresWorldInfo(profile) {
  if (
    profile?.worldInfo === false ||
    profile?.metadata?.disableWorldInfo === true
  ) {
    return false;
  }

  const blocks = Array.isArray(profile?.blocks) ? profile.blocks : [];
  for (const block of blocks) {
    if (!block || block.enabled === false) continue;
    if (
      block.type === "builtin" &&
      ["worldInfoBefore", "worldInfoAfter"].includes(String(block.sourceKey || ""))
    ) {
      return true;
    }

    const rawContent = String(block.content || "");
    if (!rawContent.includes("{{")) continue;
    if (
      WORLD_INFO_VARIABLE_KEYS.some((key) =>
        rawContent.includes(`{{${key}}}`) ||
        rawContent.includes(`{{ ${key} }}`),
      )
    ) {
      return true;
    }
  }

  // atDepth world info is implicit in the final message chain, so profiles
  // without explicit before/after placeholders should still resolve lore.
  return blocks.some((block) => block && block.enabled !== false);
}

function extractWorldInfoChatMessages(context = {}) {
  if (Array.isArray(context.chatMessages)) {
    return context.chatMessages;
  }
  return [];
}

export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
  const isCustomFilter = isCustomWorldInfoFilterEnabled(settings);
  const profile = getActiveTaskProfile(settings, taskType);
  const legacyPrompt = getLegacyPromptForTask(settings, taskType);
  const promptRegexInput = { entries: [] };
  const mvuPromptDebug = createEmptyMvuPromptDebug();
  const taskInputDebug =
    context?.taskInputDebug && typeof context.taskInputDebug === "object"
      ? cloneRuntimeDebugValue(context.taskInputDebug, {})
      : null;
  const worldInfoInputContext = {
    ...context,
  };
  const sanitizedInputContext = sanitizePromptContextInputs(
    settings,
    taskType,
    context,
    mvuPromptDebug,
    promptRegexInput,
  );
  const rawBlocks = Array.isArray(profile?.blocks) ? profile.blocks : [];
  const blocks = rawBlocks
    .map((block, index) => ({ ...block, _orderIndex: index }))
    .sort((a, b) => {
      const orderA = Number.isFinite(Number(a.order))
        ? Number(a.order)
        : a._orderIndex;
      const orderB = Number.isFinite(Number(b.order))
        ? Number(b.order)
        : b._orderIndex;
      return orderA - orderB;
    });

  const worldInfoRequested = context?.__skipWorldInfo === true
    ? false
    : profileRequiresWorldInfo(profile);
  const emptyWorldInfo = buildEmptyWorldInfoContext();
  let resolvedWorldInfo = emptyWorldInfo;
  let worldInfoRuntimeBlockedContents = [];
  let deliveredAtDepthViaChatMessages = false;

  if (worldInfoRequested) {
    const worldInfo = await resolveTaskWorldInfo({
      settings,
      chatMessages: extractWorldInfoChatMessages(worldInfoInputContext),
      userMessage: String(worldInfoInputContext.userMessage || ""),
      templateContext: worldInfoInputContext,
    });
    const sanitizedWorldInfo = sanitizeWorldInfoContext(
      settings,
      taskType,
      worldInfo,
      mvuPromptDebug,
      promptRegexInput,
    );
    worldInfoRuntimeBlockedContents = Array.isArray(
      sanitizedWorldInfo.__mvuBlockedContents,
    )
      ? sanitizedWorldInfo.__mvuBlockedContents
      : [];
    resolvedWorldInfo = {
      worldInfoBefore: sanitizedWorldInfo.beforeText || "",
      worldInfoAfter: sanitizedWorldInfo.afterText || "",
      worldInfoBeforeEntries: sanitizedWorldInfo.beforeEntries || [],
      worldInfoAfterEntries: sanitizedWorldInfo.afterEntries || [],
      worldInfoAtDepthEntries: sanitizedWorldInfo.atDepthEntries || [],
      activatedWorldInfoNames: sanitizedWorldInfo.activatedEntryNames || [],
      taskAdditionalMessages: sanitizedWorldInfo.additionalMessages || [],
      worldInfoDebug: sanitizedWorldInfo.debug || null,
    };

    if (
      Array.isArray(sanitizedInputContext.chatMessages) &&
      isPromptMessageArray(sanitizedInputContext.chatMessages)
    ) {
      const injectedChatMessages = injectAtDepthMessagesIntoChatMessages(
        sanitizedInputContext.chatMessages,
        sanitizedWorldInfo.additionalMessages,
      );
      if (Array.isArray(injectedChatMessages) && injectedChatMessages.length > 0) {
        sanitizedInputContext.chatMessages = injectedChatMessages;
        if (typeof context.recentMessages === "string") {
          sanitizedInputContext.recentMessages =
            stringifyInterpolatedValue(injectedChatMessages);
        }
        if (typeof context.dialogueText === "string") {
          sanitizedInputContext.dialogueText =
            stringifyInterpolatedValue(injectedChatMessages);
        }
        deliveredAtDepthViaChatMessages = true;
      }
    }
  }

  const resolvedContext = {
    ...sanitizedInputContext,
    ...emptyWorldInfo,
    ...resolvedWorldInfo,
  };
  const worldInfoResolution = buildWorldInfoResolution(resolvedContext);

  let systemPrompt = "";
  const customMessages = [];
  const executionMessages = [];
  const renderedBlocks = [];
  let userRoleBlockCount = 0;
  let assistantRoleBlockCount = 0;
  let systemRoleBlockCount = 0;

  debugLog(
    `[ST-BME][prompt-diag] buildTaskPrompt: taskType=${taskType}, ` +
      `total blocks=${blocks.length}, ` +
      `block roles=[${blocks.map((b) => `${b.name}(${b.role},${b.enabled !== false ? "on" : "off"})`).join(", ")}]`,
  );

  for (const block of blocks) {
    if (!block || block.enabled === false) continue;

    const role = normalizeRole(block.role);
    const blockDerivedFromWorldInfo = blockUsesWorldInfoContent(block);
    const blockOwnership = describeBlockContentOwnership(block);
    let content = "";

    if (block.type === "legacyPrompt") {
      content = legacyPrompt || block.content || "";
    } else if (block.type === "builtin") {
      if (block.content) {
        content = interpolateVariables(block.content, resolvedContext);
      } else if (block.sourceKey) {
        content = stringifyInterpolatedValue(
          getByPath(resolvedContext, block.sourceKey),
        );
      }
    } else if (block.type === "custom") {
      content = interpolateVariables(block.content || "", resolvedContext);
    }

    if (role === "user") {
      debugLog(
        `[ST-BME][prompt-diag] user block "${block.name || block.id}": ` +
          `type=${block.type}, contentLen=${String(content || "").length}, ` +
          `rawContentLen=${String(block.content || "").length}, ` +
          `blockedContentsCount=${worldInfoRuntimeBlockedContents.length}`,
      );
    }

    if (!String(content || "").trim()) {
      continue;
    }

    const mode = normalizeInjectionMode(block.injectionMode);
    renderedBlocks.push({
      id: String(block.id || ""),
      name: String(block.name || ""),
      type: String(block.type || "custom"),
      role,
      sourceKey: String(block.sourceKey || ""),
      sourceField: String(block.sourceField || ""),
      content,
      order: Number.isFinite(Number(block.order))
        ? Number(block.order)
        : block._orderIndex,
      derivedFromWorldInfo: blockDerivedFromWorldInfo,
      injectionMode: mode,
      delivery: resolveBlockDelivery(block),
      effectiveDelivery: resolveBlockDelivery(block),
      diagnosticInjectionPosition: getBlockDiagnosticInjectionPosition(block),
      contentOrigin: blockOwnership.contentOrigin,
      sanitizationEligible: blockOwnership.sanitizationEligible,
      regexSourceType: blockOwnership.regexSourceType,
    });

    const executionMessage = createExecutionMessage(role, content, {
      source: "profile-block",
      blockId: String(block.id || ""),
      blockName: String(block.name || ""),
      blockType: String(block.type || "custom"),
      sourceKey: String(block.sourceKey || ""),
      injectionMode: mode,
      derivedFromWorldInfo: blockDerivedFromWorldInfo,
      contentOrigin: blockOwnership.contentOrigin,
      sanitizationEligible: blockOwnership.sanitizationEligible,
      regexSourceType: blockOwnership.regexSourceType,
    });
    if (executionMessage) {
      executionMessages.push(executionMessage);
    }

    if (role === "system") {
      systemRoleBlockCount += 1;
      if (!systemPrompt) {
        systemPrompt = content;
      } else if (mode === "prepend") {
        systemPrompt = `${content}\n\n${systemPrompt}`;
      } else {
        systemPrompt = `${systemPrompt}\n\n${content}`;
      }
      continue;
    }

    if (role === "user") {
      userRoleBlockCount += 1;
    } else if (role === "assistant") {
      assistantRoleBlockCount += 1;
    }
    if (mode === "prepend") {
      customMessages.unshift({
        role,
        content,
        source: "profile-block",
        blockId: String(block.id || ""),
        blockName: String(block.name || ""),
        blockType: String(block.type || "custom"),
        sourceKey: String(block.sourceKey || ""),
        injectionMode: mode,
        derivedFromWorldInfo: blockDerivedFromWorldInfo,
        contentOrigin: blockOwnership.contentOrigin,
        sanitizationEligible: blockOwnership.sanitizationEligible,
        regexSourceType: blockOwnership.regexSourceType,
      });
    } else {
      customMessages.push({
        role,
        content,
        source: "profile-block",
        blockId: String(block.id || ""),
        blockName: String(block.name || ""),
        blockType: String(block.type || "custom"),
        sourceKey: String(block.sourceKey || ""),
        injectionMode: mode,
        derivedFromWorldInfo: blockDerivedFromWorldInfo,
        contentOrigin: blockOwnership.contentOrigin,
        sanitizationEligible: blockOwnership.sanitizationEligible,
        regexSourceType: blockOwnership.regexSourceType,
      });
    }
  }

  const atDepthExecutionMessages = (worldInfoResolution.additionalMessages || [])
    .map((message) =>
      createExecutionMessage(
        message.role,
        message.content,
        {
          source: "worldInfo-atDepth",
          sourceKey: "taskAdditionalMessages",
          contentOrigin:
            String(message.contentOrigin || "") ||
            PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
          sanitizationEligible: message.sanitizationEligible === true,
          regexSourceType: String(message.regexSourceType || "world_info"),
          depth: Number.isFinite(Number(message?.depth))
            ? Number(message.depth)
            : null,
          order: Number.isFinite(Number(message?.order))
            ? Number(message.order)
            : 0,
        },
      ),
    )
    .filter(Boolean);
  const finalExecutionMessages = deliveredAtDepthViaChatMessages
    ? executionMessages
    : [...atDepthExecutionMessages, ...executionMessages];

  const privateTaskMessages = deliveredAtDepthViaChatMessages
    ? [...customMessages]
    : [...worldInfoResolution.additionalMessages, ...customMessages];
  debugLog(
    `[ST-BME][prompt-diag] buildTaskPrompt done: ` +
      `executionMessages=${finalExecutionMessages.length}, ` +
      `userBlocks=${userRoleBlockCount}, systemBlocks=${systemRoleBlockCount}, ` +
      `customMessages=${customMessages.length}, ` +
      `atDepthMessages=${atDepthExecutionMessages.length}, ` +
      `atDepthViaChatMessages=${deliveredAtDepthViaChatMessages}`,
  );
  const hostInjectionPlan = buildHostInjectionPlan(
    renderedBlocks,
    worldInfoResolution,
  );

  const result = {
    profile,
    hostInjections: worldInfoResolution.injections,
    hostInjectionPlan,
    privateTaskPrompt: {
      systemPrompt,
      messages: privateTaskMessages,
    },
    executionMessages: finalExecutionMessages,
    privateTaskMessages,
    renderedBlocks,
    regexInput: mergeRegexCollectors(promptRegexInput),
    worldInfoResolution,
    systemPrompt,
    customMessages,
    additionalMessages: worldInfoResolution.additionalMessages,
    worldInfo: {
      beforeText: worldInfoResolution.beforeText,
      afterText: worldInfoResolution.afterText,
      beforeEntries: worldInfoResolution.beforeEntries,
      afterEntries: worldInfoResolution.afterEntries,
      atDepthEntries: worldInfoResolution.atDepthEntries,
      activatedEntryNames: worldInfoResolution.activatedEntryNames,
      debug: worldInfoResolution.debug,
    },
    debug: {
      taskType,
      profileId: profile?.id || "",
      profileName: profile?.name || "",
      usedLegacyPrompt: Boolean(legacyPrompt),
      blockCount: blocks.length,
      renderedBlockCount: renderedBlocks.length,
      worldInfoRequested,
      worldInfoBeforeCount: worldInfoResolution.beforeEntries.length,
      worldInfoAfterCount: worldInfoResolution.afterEntries.length,
      worldInfoAtDepthCount: worldInfoResolution.atDepthEntries.length,
      hostInjectionCount:
        worldInfoResolution.injections.before.length +
        worldInfoResolution.injections.after.length +
        worldInfoResolution.injections.atDepth.length,
      hostInjectionPlanCount:
        hostInjectionPlan.before.length +
        hostInjectionPlan.after.length +
        hostInjectionPlan.atDepth.length,
      hostInjectionPlanMode: "diagnostic-plan-only",
      customMessageCount: customMessages.length,
      additionalMessageCount: worldInfoResolution.additionalMessages.length,
      privateTaskMessageCount: privateTaskMessages.length,
      executionMessageCount: finalExecutionMessages.length,
      userRoleBlockCount,
      assistantRoleBlockCount,
      systemRoleBlockCount,
      effectiveDelivery: {
        profileBlocks: "ordered-private-messages",
        worldInfoBeforeAfter: "inline-in-ordered-messages",
        worldInfoAtDepth: deliveredAtDepthViaChatMessages
          ? "inserted-into-chat-messages-by-depth"
          : "appended-private-messages-fallback",
      },
      worldInfoCacheHit: Boolean(worldInfoResolution.debug?.cache?.hit),
      ejsRuntimeStatus: worldInfoResolution.debug?.ejsRuntimeStatus || "",
      mvu: {
        sanitizedFieldCount: mvuPromptDebug.sanitizedFieldCount,
        sanitizedFields: cloneRuntimeDebugValue(
          mvuPromptDebug.sanitizedFields,
          [],
        ),
        finalMessageStripCount: mvuPromptDebug.finalMessageStripCount,
        worldInfoBlockedContentHits: mvuPromptDebug.worldInfoBlockedContentHits,
        sanitizerAppliedFields: cloneRuntimeDebugValue(
          mvuPromptDebug.sanitizerAppliedFields,
          [],
        ),
        sanitizerHitKinds: cloneRuntimeDebugValue(
          mvuPromptDebug.sanitizerHitKinds,
          [],
        ),
        hostReuseAppliedFields: cloneRuntimeDebugValue(
          mvuPromptDebug.hostReuseAppliedFields,
          [],
        ),
        hostReuseSkippedDisplayOnlyRules: Number(
          mvuPromptDebug.hostReuseSkippedDisplayOnlyRules || 0,
        ),
        regexExecutionMode: String(
          mvuPromptDebug.regexExecutionMode || "host-unavailable",
        ),
        hostFormatterAvailable: Boolean(
          mvuPromptDebug.hostFormatterAvailable,
        ),
        hostFormatterSource: String(
          mvuPromptDebug.hostFormatterSource || "",
        ),
        fallbackReason: String(mvuPromptDebug.fallbackReason || ""),
      },
      inputContext: taskInputDebug,
      effectivePath: {
        promptAssembly: "ordered-private-messages",
        hostInjectionPlan: "diagnostic-plan-only",
        worldInfoInputContext: "raw-context-for-trigger-and-ejs",
        ejs:
          worldInfoResolution.debug?.ejsRuntimeStatus ||
          "unknown",
        worldInfo:
          worldInfoRequested !== false
            ? worldInfoResolution.activatedEntryNames.length > 0
              ? "matched"
              : "requested-but-missed"
            : "disabled",
      },
    },
  };

  Object.defineProperty(result, "__mvuRuntime", {
    value: {
      blockedContents: [...worldInfoRuntimeBlockedContents],
    },
    configurable: true,
    enumerable: false,
    writable: false,
  });

  recordTaskPromptBuild(taskType, {
    taskType,
    profileId: profile?.id || "",
    profileName: profile?.name || "",
    systemPrompt,
    privateTaskMessages,
    executionMessages: finalExecutionMessages,
    renderedBlocks,
    hostInjections: worldInfoResolution.injections,
    hostInjectionPlan,
    worldInfoResolution,
    mvu: result.debug.mvu,
    inputContext: taskInputDebug,
    regexInput: result.regexInput,
    debug: result.debug,
  });

  return result;
}

function clonePayloadMessage(message = {}) {
  return createExecutionMessage(message.role, message.content, {
    source: String(message.source || ""),
    blockId: String(message.blockId || ""),
    blockName: String(message.blockName || ""),
    blockType: String(message.blockType || ""),
    sourceKey: String(message.sourceKey || ""),
    injectionMode: String(message.injectionMode || ""),
    derivedFromWorldInfo: message.derivedFromWorldInfo === true,
    contentOrigin: String(message.contentOrigin || ""),
    sanitizationEligible: message.sanitizationEligible === true,
    regexSourceType: String(message.regexSourceType || ""),
  });
}

function splitSectionedTranscriptPayloadMessage(message = {}) {
  const normalizedRole = normalizeRole(message?.role);
  const sourceKey = String(message?.sourceKey || "").trim();
  const content = String(message?.content || "").trim();
  const targetSectionHeader = content.includes(RECALL_TARGET_CONTENT_HEADER)
    ? RECALL_TARGET_CONTENT_HEADER
    : content.includes(EXTRACTION_TARGET_CONTENT_HEADER)
      ? EXTRACTION_TARGET_CONTENT_HEADER
      : "";
  if (
    !["system", "user"].includes(normalizedRole) ||
    !["recentMessages", "dialogueText"].includes(sourceKey) ||
    !content.includes(EXTRACTION_CONTEXT_REVIEW_HEADER) ||
    !targetSectionHeader
  ) {
    return [message];
  }

  const headerMatches = [];
  let searchIndex = 0;
  while (searchIndex < content.length) {
    const contextIndex = content.indexOf(
      EXTRACTION_CONTEXT_REVIEW_HEADER,
      searchIndex,
    );
    const targetIndex = targetSectionHeader
      ? content.indexOf(targetSectionHeader, searchIndex)
      : -1;
    let nextIndex = -1;
    let nextHeader = "";
    if (contextIndex >= 0 && (targetIndex < 0 || contextIndex <= targetIndex)) {
      nextIndex = contextIndex;
      nextHeader = EXTRACTION_CONTEXT_REVIEW_HEADER;
    } else if (targetIndex >= 0) {
      nextIndex = targetIndex;
      nextHeader = targetSectionHeader;
    }
    if (nextIndex < 0 || !nextHeader) {
      break;
    }
    headerMatches.push({
      index: nextIndex,
      header: nextHeader,
    });
    searchIndex = nextIndex + nextHeader.length;
  }

  if (headerMatches.length < 2 || headerMatches[0].index !== 0) {
    return [message];
  }

  const { role: _role, content: _content, ...sharedMeta } = message;
  const splitMessages = [];

  for (let index = 0; index < headerMatches.length; index += 1) {
    const current = headerMatches[index];
    const next = headerMatches[index + 1];
    const sectionBody = content
      .slice(current.index + current.header.length, next ? next.index : content.length)
      .trim();
    const transcriptSection =
      current.header === EXTRACTION_CONTEXT_REVIEW_HEADER ? "context" : "target";
    splitMessages.push(
      createExecutionMessage(
        normalizedRole,
        sectionBody ? `${current.header}\n\n${sectionBody}` : current.header,
        {
          ...sharedMeta,
          sourceKey,
          transcriptSection,
          transcriptSectionPart: "section",
        },
      ),
    );
  }

  return splitMessages.filter(Boolean);
}

function expandSectionedTranscriptPayloadMessages(messages = []) {
  return (Array.isArray(messages) ? messages : []).flatMap((message) =>
    splitSectionedTranscriptPayloadMessage(message),
  );
}

function collectPayloadUserMessageTexts(messages = []) {
  return (Array.isArray(messages) ? messages : [])
    .filter((message) => String(message?.role || "").trim().toLowerCase() === "user")
    .map((message) => String(message?.content || "").trim())
    .filter(Boolean);
}

function buildSafeFallbackUserPrompt(
  settings = {},
  taskType,
  {
    fallbackUserPrompt = "",
    blockedContents = [],
    rawExecutionMessages = [],
    rawPrivateTaskMessages = [],
  } = {},
) {
  const structuredUserPrompt = [
    ...collectPayloadUserMessageTexts(rawExecutionMessages),
    ...collectPayloadUserMessageTexts(rawPrivateTaskMessages),
  ]
    .join("\n\n")
    .trim();
  const candidates = [
    {
      source: "structured-user-blocks",
      text: structuredUserPrompt,
    },
    {
      source: "fallback-user-prompt",
      text: String(fallbackUserPrompt || "").trim(),
    },
  ].filter((candidate) => candidate.text);

  for (const candidate of candidates) {
    const sanitized = sanitizeInjectionText(settings, taskType, candidate.text, {
      mode: "final-injection-safe",
      blockedContents,
      contentOrigin: PROMPT_CONTENT_ORIGIN.HOST_INJECTED,
      sanitizationEligible: true,
      role: "user",
      applySanitizer: true,
      applyHostRegex: false,
      path: "payload.fallbackUserPrompt",
      stage: "payload-fallback-user-prompt",
    });
    const text = String(sanitized.text || "").trim();
    if (text) {
      return {
        text,
        source: candidate.source,
        changed: Boolean(sanitized.changed),
        dropped: Boolean(sanitized.dropped),
      };
    }
  }

  return {
    text: "",
    source: candidates[0]?.source || "",
    changed: false,
    dropped: candidates.length > 0,
  };
}

export function buildTaskLlmPayload(promptBuild = null, fallbackUserPrompt = "") {
  const runtimeMvu = promptBuild?.__mvuRuntime || {};
  const taskType = String(promptBuild?.debug?.taskType || "");
  const settings = {};
  const isCustomFilter =
    String(
      promptBuild?.worldInfo?.debug?.customFilter?.mode ||
        promptBuild?.worldInfoResolution?.debug?.customFilter?.mode ||
        "default",
    ).trim() === "custom";
  const blockedContents = Array.isArray(runtimeMvu?.blockedContents)
    ? runtimeMvu.blockedContents
    : [];
  const rawExecutionMessages = Array.isArray(promptBuild?.executionMessages)
    ? promptBuild.executionMessages
        .map((message) => clonePayloadMessage(message))
        .filter(Boolean)
    : [];
  const rawPrivateTaskMessages = Array.isArray(promptBuild?.privateTaskMessages)
    ? promptBuild.privateTaskMessages
        .map((message) => clonePayloadMessage(message))
        .filter(Boolean)
    : [];
  const executionMessages = sanitizePromptMessages(
    settings,
    taskType,
    rawExecutionMessages,
    {
      blockedContents,
      applySanitizer: (message) =>
        !(isCustomFilter && messageUsesWorldInfoContent(message)),
    },
  );
  const expandedExecutionMessages = expandSectionedTranscriptPayloadMessages(
    executionMessages,
  );

  const hasUserMessage = expandedExecutionMessages.some(
    (message) => message.role === "user",
  );
  if (!hasUserMessage && rawExecutionMessages.length > 0) {
    const userBlocksBefore = (promptBuild?.executionMessages || []).filter(
      (m) => m?.role === "user",
    );
    const userBlocksAfterRaw = rawExecutionMessages.filter(
      (m) => m?.role === "user",
    );
    const userBlocksAfterSanitize = executionMessages.filter(
      (m) => m?.role === "user",
    );
    debugWarn(
      `[ST-BME] buildTaskLlmPayload fallback triggered: ` +
        `user blocks in promptBuild=${userBlocksBefore.length}, ` +
        `after recreate=${userBlocksAfterRaw.length}, ` +
        `after sanitize=${userBlocksAfterSanitize.length}, ` +
        `blockedContents count=${blockedContents.length}, ` +
        `total executionMessages=${expandedExecutionMessages.length}`,
    );
    if (userBlocksBefore.length > 0) {
      for (const block of userBlocksBefore) {
        debugWarn(
          `[ST-BME]   user block "${block.blockName || block.blockId}": ` +
            `content length=${String(block.content || "").length}, ` +
            `content preview="${String(block.content || "").slice(0, 80)}..."`,
        );
      }
    }
    if (blockedContents.length > 0) {
      debugWarn(
        `[ST-BME]   blockedContents lengths: [${blockedContents.map((c) => String(c || "").length).join(", ")}]`,
      );
    }
  }
  const additionalMessages =
    expandedExecutionMessages.length > 0
      ? []
      : expandSectionedTranscriptPayloadMessages(
          sanitizePromptMessages(
            settings,
            taskType,
            rawPrivateTaskMessages,
            {
              blockedContents,
              applySanitizer: (message) =>
                !(isCustomFilter && messageUsesWorldInfoContent(message)),
            },
          ),
        );
  const hasAdditionalUserMessage = additionalMessages.some(
    (message) => message.role === "user",
  );
  const fallbackUserPromptResult =
    hasUserMessage || hasAdditionalUserMessage
      ? {
          text: "",
          source: hasUserMessage ? "execution-messages" : "additional-messages",
          changed: false,
          dropped: false,
        }
      : buildSafeFallbackUserPrompt(settings, taskType, {
          fallbackUserPrompt,
          blockedContents,
          rawExecutionMessages,
          rawPrivateTaskMessages,
        });

  return {
    systemPrompt:
      expandedExecutionMessages.length > 0
        ? ""
        : String(promptBuild?.systemPrompt || ""),
    userPrompt: fallbackUserPromptResult.text,
    promptMessages: expandedExecutionMessages,
    additionalMessages,
    fallbackUserPromptSource: fallbackUserPromptResult.source,
    fallbackUserPromptApplied: Boolean(fallbackUserPromptResult.text),
  };
}

export function interpolateVariables(template, context = {}) {
  return String(template || "").replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, key) => {
    return stringifyInterpolatedValue(getByPath(context, key));
  });
}
