/**
 * Checkpoint state for the imajin → @mac-sync ETL.
 *
 * Persisted to disk so re-running the script is idempotent: completed steps
 * are skipped, in-progress steps resume from `lastId`.
 *
 * File layout (JSON, one file):
 *   {
 *     "deviceMap": { "<imajinUuid>": "<macsyncUuid>", ... },
 *     "albumMap":  { ... },
 *     "photoMap":  { ... },        # large; written every flushEvery photos
 *     "steps": {
 *       "devices":      { "status": "completed", "completedAt": "..." },
 *       "albums":       { "status": "completed", "completedAt": "..." },
 *       "photos":       { "status": "in_progress", "lastId": "...", "n": 1234 },
 *       "album_photos": { "status": "pending" },
 *       "identities":   { "status": "pending" },
 *       "blobs":        { "status": "in_progress", "lastPhotoId": "...", "n": 800 }
 *     },
 *     "startedAt": "...",
 *     "updatedAt": "..."
 *   }
 */

import { existsSync, mkdirSync } from 'fs';
import { readFile, writeFile, rename } from 'fs/promises';
import { dirname } from 'path';

export type StepName =
  | 'devices'
  | 'albums'
  | 'photos'
  | 'album_photos'
  | 'identities'
  | 'blobs'
  | 'cover_backfill';

export type StepStatus = 'pending' | 'in_progress' | 'completed';

export interface StepState {
  status: StepStatus;
  lastId?: string;
  n?: number;
  startedAt?: string;
  completedAt?: string;
}

export interface CheckpointState {
  deviceMap: Record<string, string>;
  albumMap: Record<string, string>;
  photoMap: Record<string, string>;
  steps: Record<StepName, StepState>;
  startedAt: string;
  updatedAt: string;
}

const STEPS: readonly StepName[] = [
  'devices',
  'albums',
  'photos',
  'cover_backfill',
  'album_photos',
  'identities',
  'blobs',
];

function emptyState(): CheckpointState {
  const now = new Date().toISOString();
  const steps = Object.fromEntries(STEPS.map((s) => [s, { status: 'pending' as const }])) as Record<
    StepName,
    StepState
  >;
  return {
    deviceMap: {},
    albumMap: {},
    photoMap: {},
    steps,
    startedAt: now,
    updatedAt: now,
  };
}

export async function loadCheckpoint(path: string): Promise<CheckpointState> {
  if (!existsSync(path)) return emptyState();
  try {
    const raw = await readFile(path, 'utf8');
    const parsed = JSON.parse(raw) as Partial<CheckpointState>;
    const base = emptyState();
    return {
      deviceMap: parsed.deviceMap ?? base.deviceMap,
      albumMap: parsed.albumMap ?? base.albumMap,
      photoMap: parsed.photoMap ?? base.photoMap,
      steps: { ...base.steps, ...(parsed.steps ?? {}) },
      startedAt: parsed.startedAt ?? base.startedAt,
      updatedAt: parsed.updatedAt ?? base.updatedAt,
    };
  } catch (err) {
    throw new Error(`failed to load checkpoint at ${path}: ${err instanceof Error ? err.message : String(err)}`);
  }
}

export async function saveCheckpoint(path: string, state: CheckpointState): Promise<void> {
  const dir = dirname(path);
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  state.updatedAt = new Date().toISOString();
  const tmp = `${path}.tmp`;
  try {
    await writeFile(tmp, JSON.stringify(state, null, 2));
    await rename(tmp, path);
  } catch (err) {
    throw new Error(`failed to save checkpoint at ${path}: ${err instanceof Error ? err.message : String(err)}`);
  }
}

export function markStep(state: CheckpointState, step: StepName, updates: Partial<StepState>): void {
  const prev = state.steps[step] ?? { status: 'pending' };
  state.steps[step] = { ...prev, ...updates };
  if (updates.status === 'in_progress' && !prev.startedAt) {
    state.steps[step].startedAt = new Date().toISOString();
  }
  if (updates.status === 'completed') {
    state.steps[step].completedAt = new Date().toISOString();
  }
}

export function isStepCompleted(state: CheckpointState, step: StepName): boolean {
  return state.steps[step]?.status === 'completed';
}
