// ST-BME: Prompt 注入模块
// 将检索结果格式化为表格注入到 LLM 上下文中

import { getSchemaType } from "../graph/schema.js";
import { normalizeMemoryScope } from "../graph/memory-scope.js";
import {
  describeStoryTime,
  describeStoryTimeSpan,
} from "../graph/story-timeline.js";
import { compareSummaryEntriesForDisplay } from "../graph/summary-state.js";

/**
 * 将检索结果转换为注入文本
 *
 * @param {object} retrievalResult - retriever.retrieve() 的返回值
 * @param {object[]} schema - 节点类型 Schema
 * @returns {string} 注入文本
 */
export function formatInjection(retrievalResult, schema) {
  const {
    summaryEntries,
    coreNodes,
    recallNodes,
    groupedRecallNodes,
    scopeBuckets,
  } =
    retrievalResult;
  const showStoryTime =
    retrievalResult?.meta?.scopeContext?.injectStoryTimeLabel !== false;
  const parts = [];
  const appended = new Set();

  appendSummarySections(parts, summaryEntries || []);

  if (scopeBuckets && typeof scopeBuckets === "object") {
    appendCharacterPovSections(
      parts,
      scopeBuckets,
      retrievalResult?.meta?.retrieval?.sceneOwnerCandidates || [],
      schema,
      appended,
      showStoryTime,
    );
    appendScopeSection(
      parts,
      "[Memory - User POV / Not Character Facts]",
      scopeBuckets.userPov,
      schema,
      appended,
      showStoryTime,
      "这些是用户/玩家侧主观记忆，不等于角色已知事实；只能作为关系、承诺、情绪和长期互动背景参考。",
    );
    appendScopeSection(
      parts,
      "[Memory - Objective / Current Region]",
      scopeBuckets.objectiveCurrentRegion,
      schema,
      appended,
      showStoryTime,
    );
    appendScopeSection(
      parts,
      "[Memory - Objective / Global]",
      scopeBuckets.objectiveGlobal,
      schema,
      appended,
      showStoryTime,
    );

    if (parts.length > 0) {
      return parts.join("\n");
    }
  }

  // ========== Core 常驻注入 ==========
  if (coreNodes.length > 0) {
    parts.push("[Memory - Core]");

    const grouped = groupByType(coreNodes);

    for (const [typeId, nodes] of grouped) {
      const typeDef = getSchemaType(schema, typeId);
      if (!typeDef) continue;

      const table = formatTable(nodes, typeDef, appended, showStoryTime);
      if (table) parts.push(table);
    }
  }

  // ========== Recall 召回注入 ==========
  if (recallNodes.length > 0) {
    parts.push("");
    parts.push("[Memory - Recalled]");

    const buckets = groupedRecallNodes || {
      state: recallNodes.filter(
        (n) => n.type === "character" || n.type === "location",
      ),
      episodic: recallNodes.filter(
        (n) => n.type === "event" || n.type === "thread",
      ),
      reflective: recallNodes.filter(
        (n) => n.type === "reflection" || n.type === "synopsis",
      ),
      rule: recallNodes.filter((n) => n.type === "rule"),
      other: recallNodes.filter(
        (n) =>
          ![
            "character",
            "location",
            "event",
            "thread",
            "reflection",
            "synopsis",
            "rule",
          ].includes(n.type),
      ),
    };

    appendBucket(parts, "当前状态记忆", buckets.state, schema, appended, showStoryTime);
    appendBucket(parts, "情景事件记忆", buckets.episodic, schema, appended, showStoryTime);
    appendBucket(parts, "反思与长期锚点", buckets.reflective, schema, appended, showStoryTime);
    appendBucket(parts, "规则与约束", buckets.rule, schema, appended, showStoryTime);
    appendBucket(parts, "其他关联记忆", buckets.other, schema, appended, showStoryTime);
  }

  return parts.join("\n");
}

export function formatSummaryInjection(summaryEntries = []) {
  const parts = [];
  appendSummarySections(parts, summaryEntries);
  return parts.join("\n").trim();
}

function appendSummarySections(parts, summaryEntries = []) {
  const entries = (Array.isArray(summaryEntries) ? summaryEntries : [])
    .filter((entry) => String(entry?.status || "active") === "active" && String(entry?.text || "").trim())
    .sort(compareSummaryEntriesForDisplay);
  if (entries.length === 0) return;

  if (parts.length > 0) {
    parts.push("");
  }
  parts.push("[Summary - Active Frontier]");
  for (const entry of entries) {
    const level = Math.max(0, Number(entry?.level || 0));
    const range = Array.isArray(entry?.messageRange) ? entry.messageRange : ["?", "?"];
    const span = describeStoryTimeSpan(entry?.storyTimeSpan);
    const header =
      String(entry?.kind || "") === "rollup"
        ? `[Summary L${level} / Rolled Up / 楼 ${range[0]} ~ ${range[1]}]`
        : `[Summary L${level} / 楼 ${range[0]} ~ ${range[1]}]`;
    parts.push(header);
    if (span) {
      parts.push(`story_time_span: ${span}`);
    }
    parts.push(String(entry?.text || "").trim());
    parts.push("");
  }
  while (parts[parts.length - 1] === "") {
    parts.pop();
  }
}

function appendCharacterPovSections(
  parts,
  scopeBuckets,
  sceneOwnerCandidates,
  schema,
  appended,
  showStoryTime,
) {
  const byOwner =
    scopeBuckets?.characterPovByOwner &&
    typeof scopeBuckets.characterPovByOwner === "object"
      ? scopeBuckets.characterPovByOwner
      : {};
  const ownerOrder = Array.isArray(scopeBuckets?.characterPovOwnerOrder)
    ? scopeBuckets.characterPovOwnerOrder
    : [];

  if (ownerOrder.length > 0) {
    for (const ownerKey of ownerOrder) {
      const nodes = Array.isArray(byOwner[ownerKey]) ? byOwner[ownerKey] : [];
      if (nodes.length === 0) continue;
      appendScopeSection(
        parts,
        `[Memory - Character POV: ${resolveSceneOwnerLabel(ownerKey, nodes, sceneOwnerCandidates)}]`,
        nodes,
        schema,
        appended,
        showStoryTime,
      );
    }
    return;
  }

  appendScopeSection(
    parts,
    "[Memory - Character POV]",
    scopeBuckets?.characterPov,
    schema,
    appended,
    showStoryTime,
  );
}

function resolveSceneOwnerLabel(ownerKey, nodes = [], sceneOwnerCandidates = []) {
  const normalizedOwnerKey = String(ownerKey || "").trim();
  const candidateMatch = (Array.isArray(sceneOwnerCandidates) ? sceneOwnerCandidates : [])
    .find((candidate) => String(candidate?.ownerKey || "").trim() === normalizedOwnerKey);
  if (candidateMatch?.ownerName) {
    return String(candidateMatch.ownerName);
  }
  const nodeMatch = (Array.isArray(nodes) ? nodes : [])
    .map((node) => normalizeMemoryScope(node?.scope))
    .find((scope) => scope.ownerName || scope.ownerId);
  return String(nodeMatch?.ownerName || nodeMatch?.ownerId || normalizedOwnerKey || "未命名角色");
}

function appendScopeSection(parts, title, nodes, schema, appended, showStoryTime, note = "") {
  if (!Array.isArray(nodes) || nodes.length === 0) return;
  if (parts.length > 0) {
    parts.push("");
  }
  parts.push(title);
  if (note) {
    parts.push(note);
  }

  const grouped = groupByType(nodes);
  for (const [typeId, groupedNodes] of grouped) {
    const typeDef = getSchemaType(schema, typeId);
    if (!typeDef) continue;
    const table = formatTable(groupedNodes, typeDef, appended, showStoryTime);
    if (table) parts.push(table);
  }
}

/**
 * 按类型分组节点
 */
function groupByType(nodes) {
  const map = new Map();
  for (const node of nodes) {
    if (!map.has(node.type)) map.set(node.type, []);
    map.get(node.type).push(node);
  }
  return map;
}

function appendBucket(parts, title, nodes, schema, appended, showStoryTime) {
  if (!nodes || nodes.length === 0) return;
  parts.push(`## ${title}`);

  const grouped = groupByType(nodes);
  for (const [typeId, groupedNodes] of grouped) {
    const typeDef = getSchemaType(schema, typeId);
    if (!typeDef) continue;

    const table = formatTable(groupedNodes, typeDef, appended, showStoryTime);
    if (table) parts.push(table);
  }
}

/**
 * 将同类型节点格式化为 Markdown 表格
 */
function formatTable(nodes, typeDef, appended = new Set(), showStoryTime = true) {
  if (!Array.isArray(nodes) || nodes.length === 0) return "";

  const uniqueNodes = nodes.filter((node) => {
    if (!node?.id || appended.has(node.id)) return false;
    appended.add(node.id);
    return true;
  });

  if (uniqueNodes.length === 0) return "";

  // 确定要展示的列（有实际数据的列）
  const activeCols = typeDef.columns.filter((col) =>
    uniqueNodes.some(
      (n) => n.fields?.[col.name] != null && n.fields[col.name] !== "",
    ),
  );
  const derivedCols = buildDerivedColumns(uniqueNodes, typeDef, showStoryTime);
  const allCols = [...derivedCols, ...activeCols];

  if (allCols.length === 0) return "";

  // 表头
  const header = `| ${allCols.map((c) => c.name).join(" | ")} |`;
  const separator = `| ${allCols.map(() => "---").join(" | ")} |`;

  // 数据行
  const rows = uniqueNodes.map((node) => {
    const cells = allCols.map((col) => {
      const val =
        typeof col.getValue === "function"
          ? col.getValue(node)
          : node.fields?.[col.name] ?? "";
      // 转义管道符，限制单元格长度
      return String(val)
        .replace(/\|/g, "\\|")
        .replace(/\n/g, " ")
        .slice(0, 200);
    });
    return `| ${cells.join(" | ")} |`;
  });

  return `${typeDef.tableName}:\n${header}\n${separator}\n${rows.join("\n")}`;
}

function buildDerivedColumns(nodes, typeDef, showStoryTime = true) {
  const derived = [];

  if (typeDef?.id === "pov_memory") {
    derived.push({
      name: "owner",
      getValue(node) {
        const scope = normalizeMemoryScope(node?.scope);
        const ownerLabel = scope.ownerName || scope.ownerId || "未命名";
        if (scope.ownerType === "user") {
          return `用户: ${ownerLabel}`;
        }
        if (scope.ownerType === "character") {
          return `角色: ${ownerLabel}`;
        }
        return `POV: ${ownerLabel}`;
      },
    });
  }

  if (showStoryTime) {
    const pointTypes = new Set(["event", "pov_memory"]);
    const spanTypes = new Set(["thread", "synopsis", "reflection"]);
    if (
      pointTypes.has(typeDef?.id) &&
      nodes.some((node) => describeStoryTime(node?.storyTime))
    ) {
      derived.push({
        name: "story_time",
        getValue(node) {
          return describeStoryTime(node?.storyTime) || "";
        },
      });
    } else if (
      spanTypes.has(typeDef?.id) &&
      nodes.some((node) => describeStoryTimeSpan(node?.storyTimeSpan))
    ) {
      derived.push({
        name: "story_time_span",
        getValue(node) {
          return describeStoryTimeSpan(node?.storyTimeSpan) || "";
        },
      });
    }
  }

  return derived;
}

/**
 * 获取注入提示词的总 token 估算
 * 粗略估算：1 个 token ≈ 2 个中文字符 或 4 个英文字符
 *
 * @param {string} injectionText
 * @returns {number} 估算 token 数
 */
export function estimateTokens(injectionText) {
  if (!injectionText) return 0;
  // 简单估算：中文 2 字符/token，英文 4 字符/token
  const cnChars = (injectionText.match(/[\u4e00-\u9fff]/g) || []).length;
  const otherChars = injectionText.length - cnChars;
  return Math.ceil(cnChars / 2 + otherChars / 4);
}
