import {
  createDefaultStoryTimeSpan,
  normalizeStoryTimeSpan,
} from "./story-timeline.js";

export const SUMMARY_STATE_VERSION = 1;
const ACTIVE_STATUS = "active";
const FOLDED_STATUS = "folded";
const SUMMARY_KINDS = new Set(["small", "rollup", "legacy-import"]);

function summaryId() {
  return `summary-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}

function normalizeStringArray(values = []) {
  return [...new Set(
    (Array.isArray(values) ? values : [])
      .map((value) => String(value || "").trim())
      .filter(Boolean),
  )];
}

function normalizeNumberRange(range, fallback = [-1, -1]) {
  if (!Array.isArray(range) || range.length < 2) {
    return [...fallback];
  }
  const start = Number.isFinite(Number(range[0])) ? Number(range[0]) : fallback[0];
  const end = Number.isFinite(Number(range[1])) ? Number(range[1]) : fallback[1];
  return [start, end];
}

export function createDefaultSummaryState(state = {}) {
  const source =
    state && typeof state === "object" && !Array.isArray(state) ? state : {};
  return {
    version: SUMMARY_STATE_VERSION,
    enabled: source.enabled !== false,
    entries: Array.isArray(source.entries)
      ? source.entries.map((entry, index) =>
          normalizeSummaryEntry(entry, {
            fallbackId: `summary-import-${index + 1}`,
          }),
        )
      : [],
    activeEntryIds: normalizeStringArray(source.activeEntryIds),
    lastSummarizedExtractionCount: Number.isFinite(
      Number(source.lastSummarizedExtractionCount),
    )
      ? Math.max(0, Number(source.lastSummarizedExtractionCount))
      : 0,
    lastSummarizedAssistantFloor: Number.isFinite(
      Number(source.lastSummarizedAssistantFloor),
    )
      ? Number(source.lastSummarizedAssistantFloor)
      : -1,
  };
}

export function normalizeSummaryEntry(entry = {}, options = {}) {
  const fallbackId = String(options?.fallbackId || "").trim() || summaryId();
  const source =
    entry && typeof entry === "object" && !Array.isArray(entry) ? entry : {};
  const status = String(source.status || ACTIVE_STATUS).trim().toLowerCase();
  const kind = String(source.kind || "small").trim().toLowerCase();
  return {
    id: String(source.id || fallbackId),
    level: Number.isFinite(Number(source.level))
      ? Math.max(0, Number(source.level))
      : 0,
    kind: SUMMARY_KINDS.has(kind) ? kind : "small",
    status: status === FOLDED_STATUS ? FOLDED_STATUS : ACTIVE_STATUS,
    text: String(source.text || "").trim(),
    sourceTask: String(source.sourceTask || "synopsis").trim() || "synopsis",
    extractionRange: normalizeNumberRange(source.extractionRange),
    messageRange: normalizeNumberRange(source.messageRange),
    dialogueRange: normalizeNumberRange(source.dialogueRange),
    sourceBatchIds: normalizeStringArray(source.sourceBatchIds),
    sourceSummaryIds: normalizeStringArray(source.sourceSummaryIds),
    sourceNodeIds: normalizeStringArray(source.sourceNodeIds),
    storyTimeSpan: normalizeStoryTimeSpan(
      source.storyTimeSpan,
      createDefaultStoryTimeSpan(),
    ),
    regionHints: normalizeStringArray(source.regionHints),
    ownerHints: normalizeStringArray(source.ownerHints),
    createdAt: Number.isFinite(Number(source.createdAt))
      ? Number(source.createdAt)
      : Date.now(),
    updatedAt: Number.isFinite(Number(source.updatedAt))
      ? Number(source.updatedAt)
      : Date.now(),
  };
}

export function normalizeGraphSummaryState(graph) {
  if (!graph || typeof graph !== "object") {
    return graph;
  }
  const normalized = createDefaultSummaryState(graph.summaryState);
  const entryMap = new Map();
  for (const entry of normalized.entries) {
    if (!entry?.id) continue;
    entryMap.set(entry.id, entry);
  }
  normalized.entries = [...entryMap.values()];
  normalized.activeEntryIds = normalizeStringArray(normalized.activeEntryIds)
    .filter((entryId) => {
      const entry = entryMap.get(entryId);
      return Boolean(entry) && entry.status === ACTIVE_STATUS;
    });
  graph.summaryState = normalized;
  return graph;
}

export function getSummaryEntry(graph, entryId = "") {
  normalizeGraphSummaryState(graph);
  const normalizedEntryId = String(entryId || "").trim();
  if (!normalizedEntryId) return null;
  return (
    (Array.isArray(graph?.summaryState?.entries)
      ? graph.summaryState.entries
      : []
    ).find((entry) => entry.id === normalizedEntryId) || null
  );
}

export function getActiveSummaryEntries(graph) {
  normalizeGraphSummaryState(graph);
  const entries = Array.isArray(graph?.summaryState?.entries)
    ? graph.summaryState.entries
    : [];
  const activeIds = new Set(graph?.summaryState?.activeEntryIds || []);
  return entries
    .filter((entry) => entry.status === ACTIVE_STATUS && activeIds.has(entry.id))
    .sort(compareSummaryEntriesForDisplay);
}

export function compareSummaryEntriesForDisplay(left, right) {
  const leftMessageRange = normalizeNumberRange(left?.messageRange);
  const rightMessageRange = normalizeNumberRange(right?.messageRange);
  if (leftMessageRange[0] !== rightMessageRange[0]) {
    return leftMessageRange[0] - rightMessageRange[0];
  }
  if (leftMessageRange[1] !== rightMessageRange[1]) {
    return leftMessageRange[1] - rightMessageRange[1];
  }
  if (left?.level !== right?.level) {
    return Number(left?.level || 0) - Number(right?.level || 0);
  }
  if (left?.createdAt !== right?.createdAt) {
    return Number(left?.createdAt || 0) - Number(right?.createdAt || 0);
  }
  return String(left?.id || "").localeCompare(String(right?.id || ""));
}

export function createSummaryEntry(data = {}) {
  return normalizeSummaryEntry(
    {
      ...data,
      id: data?.id || summaryId(),
      createdAt: data?.createdAt || Date.now(),
      updatedAt: data?.updatedAt || Date.now(),
    },
    {
      fallbackId: summaryId(),
    },
  );
}

export function appendSummaryEntry(graph, entryLike = {}) {
  normalizeGraphSummaryState(graph);
  const entry = createSummaryEntry(entryLike);
  graph.summaryState.entries.push(entry);
  if (!graph.summaryState.activeEntryIds.includes(entry.id)) {
    graph.summaryState.activeEntryIds.push(entry.id);
  }
  return entry;
}

export function markSummaryEntriesFolded(graph, entryIds = []) {
  normalizeGraphSummaryState(graph);
  const targetIds = new Set(normalizeStringArray(entryIds));
  if (targetIds.size === 0) return 0;

  let changed = 0;
  for (const entry of graph.summaryState.entries) {
    if (!targetIds.has(entry.id)) continue;
    if (entry.status !== FOLDED_STATUS) {
      entry.status = FOLDED_STATUS;
      entry.updatedAt = Date.now();
      changed += 1;
    }
  }
  graph.summaryState.activeEntryIds = graph.summaryState.activeEntryIds
    .filter((entryId) => !targetIds.has(entryId));
  return changed;
}

export function resetSummaryState(graph, state = null) {
  if (!graph || typeof graph !== "object") return graph;
  graph.summaryState = createDefaultSummaryState(state || {});
  return graph.summaryState;
}

export function importLegacySynopsisToSummaryState(graph) {
  normalizeGraphSummaryState(graph);
  const summaryState = graph.summaryState;
  if ((summaryState.entries || []).length > 0) {
    return null;
  }
  const legacySynopsis = (Array.isArray(graph?.nodes) ? graph.nodes : [])
    .filter((node) => node?.type === "synopsis" && node?.archived !== true)
    .sort((left, right) => {
      const leftSeq = Number(left?.seqRange?.[1] ?? left?.seq ?? -1);
      const rightSeq = Number(right?.seqRange?.[1] ?? right?.seq ?? -1);
      return rightSeq - leftSeq;
    })[0];
  const summaryText = String(legacySynopsis?.fields?.summary || "").trim();
  if (!legacySynopsis || !summaryText) {
    return null;
  }
  const entry = appendSummaryEntry(graph, {
    kind: "legacy-import",
    level: 0,
    text: summaryText,
    sourceTask: "synopsis",
    extractionRange: normalizeNumberRange(legacySynopsis?.seqRange, [
      Number.isFinite(Number(legacySynopsis?.seq)) ? Number(legacySynopsis.seq) : -1,
      Number.isFinite(Number(legacySynopsis?.seq)) ? Number(legacySynopsis.seq) : -1,
    ]),
    messageRange: normalizeNumberRange(legacySynopsis?.seqRange, [
      Number.isFinite(Number(legacySynopsis?.seq)) ? Number(legacySynopsis.seq) : -1,
      Number.isFinite(Number(legacySynopsis?.seq)) ? Number(legacySynopsis.seq) : -1,
    ]),
    sourceNodeIds: [String(legacySynopsis.id || "")],
    storyTimeSpan: legacySynopsis?.storyTimeSpan || createDefaultStoryTimeSpan(),
  });
  summaryState.lastSummarizedExtractionCount = Math.max(
    summaryState.lastSummarizedExtractionCount,
    Number.isFinite(Number(graph?.historyState?.extractionCount))
      ? Number(graph.historyState.extractionCount)
      : 0,
  );
  summaryState.lastSummarizedAssistantFloor = Math.max(
    summaryState.lastSummarizedAssistantFloor,
    Number.isFinite(Number(legacySynopsis?.seqRange?.[1]))
      ? Number(legacySynopsis.seqRange[1])
      : Number.isFinite(Number(legacySynopsis?.seq))
        ? Number(legacySynopsis.seq)
        : -1,
  );
  return entry;
}

export function getSummaryEntriesByStatus(graph, status = ACTIVE_STATUS) {
  normalizeGraphSummaryState(graph);
  const normalizedStatus = String(status || ACTIVE_STATUS).trim().toLowerCase();
  return (Array.isArray(graph?.summaryState?.entries)
    ? graph.summaryState.entries
    : []
  )
    .filter((entry) => String(entry?.status || ACTIVE_STATUS) === normalizedStatus)
    .sort(compareSummaryEntriesForDisplay);
}

