// Sync scheduling, owned by the sidecar.
//
// These syncs used to be platform queue jobs, which gave them retries and a row in the Jobs list. That was
// the wrong home twice over: the credentials travelled through Postgres job metadata to get there, and a
// mailbox sync has nothing to do with what the Jobs screen is for. They run here now, tracked in memory,
// and the Jobs list is left to the things it actually describes.
//
// Deliberately NOT a queue: one run per account at a time, no persistence, no retry. A failed sync is
// retried by the ten-minute cron like any other, and a sync interrupted by a restart resumes from the
// stored sync cursor rather than from the beginning.

/** What a sync reports as it goes. `total: 0` means "unknown" — IMAP does not tell us up front. */
export type SyncProgress = { current: number; total: number; label: string };

export type SyncState = {
  accountId: number;
  status: 'running' | 'completed' | 'failed';
  progress: SyncProgress | null;
  startedAt: number;
  finishedAt?: number;
  error?: string;
};

const states = new Map<number, SyncState>();

/** The steps of a converted job handler: each is run in order with a synthesized context. */
export type SyncStep = { name: string; run: (ctx: SyncCtx) => Promise<void> | void };

/**
 * What the moved handlers expect on their context. `job.userId` is the OWNER'S EMAIL, not a numeric id —
 * the queue called it userId and the handlers use it to resolve the mail store path
 * (DATA_PATH/<owner>/email_accounts/…). Getting this wrong reads as an empty mailbox rather than an error.
 */
export type SyncCtx = {
  /** Same shape the queue's StepContext used, so the moved handlers read it unchanged. */
  meta: Record<string, unknown>;
  job: { userId: string };
  updateProgress: (p: SyncProgress) => Promise<void> | void;
};

export function isSyncing(accountId: number): boolean {
  return states.get(accountId)?.status === 'running';
}

export function getSyncStates(): SyncState[] {
  return [...states.values()];
}

/**
 * Run `steps` in the background for one account. Returns immediately — an initial mailbox sync takes
 * minutes to hours, so nothing waits on it. Re-entrant calls for an account already syncing are ignored.
 */
export function startSync(params: {
  accountId: number;
  ownerEmail: string;
  meta: Record<string, unknown>;
  steps: SyncStep[];
  onDone?: (ok: boolean) => Promise<void> | void;
}): SyncState {
  const existing = states.get(params.accountId);
  if (existing?.status === 'running') return existing;

  const state: SyncState = {
    accountId: params.accountId,
    status: 'running',
    progress: null,
    startedAt: Date.now(),
  };
  states.set(params.accountId, state);

  const ctx: SyncCtx = {
    meta: params.meta,
    job: { userId: params.ownerEmail },
    updateProgress: (p) => {
      state.progress = p;
    },
  };

  void (async () => {
    try {
      for (const step of params.steps) await step.run(ctx);
      state.status = 'completed';
    } catch (err) {
      state.status = 'failed';
      state.error = err instanceof Error ? err.message : String(err);
      console.error(`[email-sync] account ${params.accountId} failed:`, state.error);
    } finally {
      state.finishedAt = Date.now();
      await params.onDone?.(state.status === 'completed');
    }
  })();

  return state;
}
