import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { projectPublicDiffData } from "./public-diff.js";
import { sanitizePublicData, sanitizePublicText } from "./public-sanitizer.js";
import { writeSafeLog } from "./safe-logger.js";

export function createSessionStore(dataDir, publicSanitizerConfig = {}, logger = console) {
  const filePath = join(dataDir, "sessions.json");
  const temporaryPath = join(dataDir, "sessions.json.tmp");
  const sanitizedSnapshots = new Map();

  return {
    async load() {
      try {
        const sessions = JSON.parse(await readFile(filePath, "utf8"));
        if (!Array.isArray(sessions)) throw new SyntaxError("sessions.json must contain an array");
        return sessions;
      } catch (error) {
        if (error.code === "ENOENT") return [];
        if (error instanceof SyntaxError) {
          await rename(filePath, `${filePath}.broken.${Date.now()}`);
          await writeFile(filePath, "[]", "utf8");
          writeSafeLog(logger, "warn", "session_store_recovered", {}, publicSanitizerConfig);
          return [];
        }
        throw error;
      }
    },
    async save(sessions, { dirtySessionIds } = {}) {
      await mkdir(dataDir, { recursive: true });
      const currentIds = new Set(sessions.map((session) => session.id));
      for (const id of sanitizedSnapshots.keys()) {
        if (!currentIds.has(id)) sanitizedSnapshots.delete(id);
      }
      const serializable = sessions.map((session) => {
        if (!dirtySessionIds || dirtySessionIds.has(session.id) || !sanitizedSnapshots.has(session.id)) {
          sanitizedSnapshots.set(session.id, serializeSession(session, publicSanitizerConfig));
        }
        return sanitizedSnapshots.get(session.id);
      });
      await writeFile(temporaryPath, JSON.stringify(serializable, null, 2), "utf8");
      await rename(temporaryPath, filePath);
    }
  };
}

function serializeSession(session, publicSanitizerConfig) {
  return sanitizePublicData(projectPublicDiffData({
    id: session.id,
    accountId: session.accountId,
    workspaceId: session.workspaceId,
    projectLabel: session.projectLabel,
    conversationTitle: session.conversationTitle,
    codexThreadId: session.codexThreadId ?? session.runtime?.threadId,
    threadRefs: session.threadRefs ?? undefined,
    status: session.status,
    createdAt: session.createdAt,
    updatedAt: session.updatedAt,
    events: sanitizePublicData(session.events ?? [], publicSanitizerConfig),
    handoffBaseEvents: sanitizePublicData(session.handoffBaseEvents, publicSanitizerConfig) ?? undefined,
    handoffPromptText: sanitizePublicText(session.handoffPromptText, publicSanitizerConfig) || undefined,
    handoffUserText: sanitizePublicText(session.handoffUserText, publicSanitizerConfig) || undefined,
    approvals: sanitizePublicData([...(session.approvals?.values?.() ?? [])], publicSanitizerConfig),
    files: session.files ?? [],
    attachments: (session.attachments ?? []).map(publicAttachment),
    queuedMessages: (session.queuedMessages ?? []).map((item) => publicQueuedMessage(item, publicSanitizerConfig))
  }), publicSanitizerConfig);
}

function publicAttachment({ id, kind, name, resourcePath, mimeType, size, createdAt }) {
  return { id, kind, name, resourcePath, mimeType, size, createdAt };
}

function publicQueuedMessage(item, publicSanitizerConfig) {
  return {
    id: item.id,
    clientMessageId: item.clientMessageId,
    text: sanitizePublicText(item.text, publicSanitizerConfig),
    executionAccountId: sanitizePublicText(item.executionAccountId, publicSanitizerConfig) || undefined,
    options: sanitizePublicData(item.options ?? {}, publicSanitizerConfig),
    attachments: (item.attachments ?? []).map(({ id }) => ({ id })),
    skills: (item.skills ?? []).map(({ name }) => ({ name: sanitizePublicText(name, publicSanitizerConfig) })),
    createdAt: item.createdAt
  };
}
