import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { configDir } from "./paths";

export interface AbandonedEntry {
  abandonedAt: string;
}

const warnedPaths = new Set<string>();

function storePath(): string {
  return join(configDir(), "abandoned-plans.json");
}

function warnCorruptOnce(path: string, error: unknown): void {
  if (warnedPaths.has(path)) return;
  warnedPaths.add(path);
  const message = error instanceof Error ? error.message : String(error);
  console.warn(`[abandoned-store] ignored corrupt ${path}: ${message}`);
}

function isAbandonedStore(value: unknown): value is Record<string, AbandonedEntry> {
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  return Object.values(value).every(
    (entry) =>
      Boolean(entry) &&
      typeof entry === "object" &&
      !Array.isArray(entry) &&
      typeof (entry as { abandonedAt?: unknown }).abandonedAt === "string",
  );
}

export function readAbandoned(): Record<string, AbandonedEntry> {
  const path = storePath();
  try {
    const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
    if (!isAbandonedStore(parsed)) throw new Error("invalid store shape");
    return parsed;
  } catch (error) {
    if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
    warnCorruptOnce(path, error);
    return {};
  }
}

function writeAbandoned(entries: Record<string, AbandonedEntry>): void {
  const dir = configDir();
  const path = storePath();
  const temporaryPath = `${path}.${process.pid}.tmp`;
  mkdirSync(dir, { recursive: true, mode: 0o700 });
  writeFileSync(temporaryPath, `${JSON.stringify(entries, null, 2)}\n`, { mode: 0o600 });
  renameSync(temporaryPath, path);
}

export function abandonRun(runId: string, now: () => string = () => new Date().toISOString()): void {
  const entries = readAbandoned();
  if (entries[runId]) return;
  entries[runId] = { abandonedAt: now() };
  writeAbandoned(entries);
}

export function restoreRun(runId: string): void {
  const entries = readAbandoned();
  if (!entries[runId]) return;
  delete entries[runId];
  writeAbandoned(entries);
}
