// ST-BME: 持久化召回记录纯函数

export const BME_RECALL_EXTRA_KEY = "bme_recall";
export const BME_RECALL_VERSION = 1;

function toIsoString(value) {
  if (typeof value === "string" && value.trim()) return value;
  return new Date().toISOString();
}

function cloneStringArray(value) {
  return Array.isArray(value)
    ? value
        .map((item) => String(item || "").trim())
        .filter(Boolean)
    : [];
}

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

export function readPersistedRecallFromUserMessage(chat, userMessageIndex) {
  if (!Array.isArray(chat) || !Number.isFinite(userMessageIndex)) return null;
  const message = chat[userMessageIndex];
  const raw = message?.extra?.[BME_RECALL_EXTRA_KEY];
  const record = cloneRecord(raw);
  if (!record) return null;

  const injectionText = String(record.injectionText || "").trim();
  if (!injectionText) return null;

  return {
    version: Number.isFinite(Number(record.version))
      ? Number(record.version)
      : BME_RECALL_VERSION,
    injectionText,
    selectedNodeIds: cloneStringArray(record.selectedNodeIds),
    recallInput: String(record.recallInput || ""),
    recallSource: String(record.recallSource || ""),
    hookName: String(record.hookName || ""),
    tokenEstimate: Number.isFinite(Number(record.tokenEstimate))
      ? Number(record.tokenEstimate)
      : 0,
    createdAt: toIsoString(record.createdAt),
    updatedAt: toIsoString(record.updatedAt),
    generationCount: Math.max(0, Number.parseInt(record.generationCount, 10) || 0),
    manuallyEdited: Boolean(record.manuallyEdited),
    authoritativeInputUsed: Boolean(record.authoritativeInputUsed),
    boundUserFloorText: String(record.boundUserFloorText || ""),
  };
}

export function buildPersistedRecallRecord(payload = {}, existingRecord = null) {
  const nowIso = toIsoString(payload.nowIso);
  const previous = cloneRecord(existingRecord) || {};
  const injectionText = String(payload.injectionText || "").trim();

  return {
    version: BME_RECALL_VERSION,
    injectionText,
    selectedNodeIds: cloneStringArray(payload.selectedNodeIds),
    recallInput: String(payload.recallInput || ""),
    recallSource: String(payload.recallSource || ""),
    hookName: String(payload.hookName || ""),
    tokenEstimate: Number.isFinite(Number(payload.tokenEstimate))
      ? Number(payload.tokenEstimate)
      : 0,
    createdAt: toIsoString(previous.createdAt || nowIso),
    updatedAt: nowIso,
    generationCount: 0,
    manuallyEdited: Boolean(payload.manuallyEdited),
    authoritativeInputUsed: Boolean(payload.authoritativeInputUsed),
    boundUserFloorText: String(payload.boundUserFloorText || ""),
  };
}

export function writePersistedRecallToUserMessage(chat, userMessageIndex, record) {
  if (!Array.isArray(chat) || !Number.isFinite(userMessageIndex)) return false;
  const message = chat[userMessageIndex];
  if (!message || !message.is_user) return false;

  const normalized = cloneRecord(record);
  if (!normalized || !String(normalized.injectionText || "").trim()) return false;

  message.extra ||= {};
  message.extra[BME_RECALL_EXTRA_KEY] = normalized;
  return true;
}

export function removePersistedRecallFromUserMessage(chat, userMessageIndex) {
  if (!Array.isArray(chat) || !Number.isFinite(userMessageIndex)) return false;
  const message = chat[userMessageIndex];
  if (!message?.extra || typeof message.extra !== "object") return false;
  if (!(BME_RECALL_EXTRA_KEY in message.extra)) return false;
  delete message.extra[BME_RECALL_EXTRA_KEY];
  return true;
}

export function markPersistedRecallManualEdit(
  chat,
  userMessageIndex,
  manuallyEdited = true,
  nowIso = new Date().toISOString(),
) {
  const current = readPersistedRecallFromUserMessage(chat, userMessageIndex);
  if (!current) return null;
  const nextRecord = {
    ...current,
    manuallyEdited: Boolean(manuallyEdited),
    updatedAt: toIsoString(nowIso),
  };
  if (!writePersistedRecallToUserMessage(chat, userMessageIndex, nextRecord)) {
    return null;
  }
  return nextRecord;
}

export function bumpPersistedRecallGenerationCount(chat, userMessageIndex) {
  const current = readPersistedRecallFromUserMessage(chat, userMessageIndex);
  if (!current) return null;
  const nextRecord = {
    ...current,
    generationCount: Math.max(0, Number(current.generationCount || 0)) + 1,
  };
  if (!writePersistedRecallToUserMessage(chat, userMessageIndex, nextRecord)) {
    return null;
  }
  return nextRecord;
}

export function resolveGenerationTargetUserMessageIndex(
  chat,
  { generationType = "normal" } = {},
) {
  if (!Array.isArray(chat) || chat.length === 0) return null;

  const normalizedType = String(generationType || "normal").trim() || "normal";

  // normal：取「最后一条非系统用户楼层」。若直接 return 末条非 user（常见为刚追加的助手回合），
  // 会得到 null，导致持久化无法回绑到本轮 user，`hasRecordForLatest` 长期为 false。
  if (normalizedType === "normal") {
    for (let index = chat.length - 1; index >= 0; index--) {
      const message = chat[index];
      if (message?.is_system) continue;
      if (message?.is_user) return index;
    }
    return null;
  }

  for (let index = chat.length - 1; index >= 0; index--) {
    if (chat[index]?.is_user) return index;
  }

  return null;
}

export function resolveFinalRecallInjectionSource({
  freshRecallResult = null,
  persistedRecord = null,
} = {}) {
  const freshInjection = String(freshRecallResult?.injectionText || "").trim();
  if (
    freshRecallResult?.status === "completed" &&
    freshRecallResult?.didRecall &&
    freshInjection
  ) {
    return {
      source: "fresh",
      injectionText: freshInjection,
      record: null,
    };
  }

  const persistedInjection = String(persistedRecord?.injectionText || "").trim();
  if (persistedInjection) {
    return {
      source: "persisted",
      injectionText: persistedInjection,
      record: persistedRecord,
    };
  }

  return {
    source: "none",
    injectionText: "",
    record: null,
  };
}
