// lib/util/cleanup.mjs
// Bug 4 — operator-invoked cleanup of completed-project state files.
//
// The ultraplan-local pipeline does NOT auto-cleanup state on session-end:
// stale .session-state.local.json + NEXT-SESSION-PROMPT.local.md across many
// projects accumulate over time. This util removes them safely once the
// project is fully done (status === 'completed' as seen by validateSessionState).
//
// Invariants:
// - Strict equality on parsed.status === 'completed' (no soft-match).
// - Idempotent: re-running on a partially-cleaned dir succeeds with deleted: [].
// - Refuses dryRun: false without an explicit confirm: true (prevents accidents).
// - ENOENT counts as "already absent" — never an error.
// - Cleanup is operator-invoked from /ultracontinue --cleanup; no Bash binding here.

import { existsSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { issue, fail, ok } from './result.mjs';
import { validateSessionState } from '../validators/session-state-validator.mjs';

const CANDIDATE_FILES = Object.freeze([
  '.session-state.local.json',
  'NEXT-SESSION-PROMPT.local.md',
]);

/**
 * Clean up state files for a completed ultraplan project.
 *
 * @param {string} projectDir - absolute or cwd-relative path to the project directory
 * @param {{dryRun?: boolean, confirm?: boolean}} [opts]
 * @returns {{valid: boolean, errors: object[], warnings: object[], parsed?: {wouldDelete?: string[], deleted?: string[]}}}
 */
export function cleanupProject(projectDir, opts = {}) {
  const dryRun = opts.dryRun !== false; // default true
  const confirm = opts.confirm === true;

  if (!dryRun && !confirm) {
    return fail(issue(
      'CLEANUP_REQUIRES_CONFIRM',
      'Refused: dryRun=false requires confirm=true (explicit operator confirmation)',
      'Re-run with {dryRun: false, confirm: true} to actually delete files.',
    ));
  }

  if (typeof projectDir !== 'string' || projectDir.length === 0) {
    return fail(issue('CLEANUP_INVALID_PROJECT_DIR', 'projectDir must be a non-empty string'));
  }

  const stateFile = join(projectDir, '.session-state.local.json');

  if (!existsSync(stateFile)) {
    return fail(issue(
      'CLEANUP_NO_STATE_FILE',
      `No state file at ${stateFile}; nothing to clean up`,
      'cleanup is only valid for projects that have a .session-state.local.json with status: completed',
    ));
  }

  const validation = validateSessionState(stateFile);
  if (!validation.valid) {
    return fail(issue(
      'CLEANUP_INVALID_STATE_FILE',
      `State file at ${stateFile} is invalid: ${validation.errors.map(e => e.code).join(', ')}`,
    ));
  }

  if (validation.parsed.status !== 'completed') {
    return fail(issue(
      'CLEANUP_NOT_COMPLETED',
      `Refused: status is "${validation.parsed.status}", not "completed"`,
      'cleanup is reserved for fully-finished projects. Resume via /ultracontinue or wait until the run completes.',
    ));
  }

  const candidates = CANDIDATE_FILES.map(f => join(projectDir, f));

  if (dryRun) {
    const wouldDelete = candidates.filter(p => existsSync(p));
    return { valid: true, errors: [], warnings: [], parsed: { wouldDelete, deleted: [] } };
  }

  const deleted = [];
  for (const p of candidates) {
    try {
      unlinkSync(p);
      deleted.push(p);
    } catch (e) {
      if (e && e.code === 'ENOENT') continue; // idempotent: already absent
      return fail(issue('CLEANUP_UNLINK_FAILED', `Failed to delete ${p}: ${e.message}`));
    }
  }

  return ok({ wouldDelete: [], deleted });
}
