import fs from 'node:fs';
import path from 'node:path';

const PHASES = [
  'DISCOVERY',
  'BRAINSTORM',
  'PRUNE',
  'DISPATCH',
  'BUILD',
  'REVIEW',
  'VALIDATE',
  'REFACTOR',
  'DOC',
  'COMPLETED',
  'ABORTED',
];

// Backward-allowed transitions: REFACTOR loops back to BUILD by design.
const BACKWARD_ALLOWED = new Set([
  'REFACTOR->BUILD',
]);

function resolveRoot(projectRoot) {
  return projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
}

function statePath(projectRoot) {
  return path.join(resolveRoot(projectRoot), '_byan-output', 'fd-state.json');
}

function ensureDir(filePath) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
}

function readState(projectRoot) {
  const p = statePath(projectRoot);
  if (!fs.existsSync(p)) return null;
  try {
    return JSON.parse(fs.readFileSync(p, 'utf8'));
  } catch {
    return null;
  }
}

function writeState(state, projectRoot) {
  const p = statePath(projectRoot);
  ensureDir(p);
  fs.writeFileSync(p, JSON.stringify(state, null, 2));
  return p;
}

function slugify(s) {
  return String(s || 'feature')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)/g, '')
    .slice(0, 40);
}

function stampId(now = new Date(), slug) {
  const pad = (n) => String(n).padStart(2, '0');
  const s =
    now.getFullYear().toString() +
    pad(now.getMonth() + 1) +
    pad(now.getDate()) +
    '-' +
    pad(now.getHours()) +
    pad(now.getMinutes()) +
    pad(now.getSeconds());
  return `${s}-${slugify(slug)}`;
}

export function start({ featureName, projectRoot, now = new Date(), force = false } = {}) {
  const existing = readState(projectRoot);
  if (existing && !['COMPLETED', 'ABORTED'].includes(existing.phase) && !force) {
    throw new Error(
      `FD already in progress (phase ${existing.phase}, fd_id ${existing.fd_id}). Abort or complete it first, or pass force=true.`
    );
  }

  const state = {
    fd_id: stampId(now, featureName),
    feature_name: featureName || 'unnamed',
    phase: 'DISCOVERY',
    started_at: now.toISOString(),
    updated_at: now.toISOString(),
    phase_history: [{ phase: 'DISCOVERY', entered_at: now.toISOString() }],
    project_context: null,
    raw_ideas: [],
    backlog: [],
    dispatch_table: [],
    commits: [],
    review_findings: [],
    validate_verdict: null,
    refactor_log: [],
    doc_log: [],
    notes: [],
  };
  writeState(state, projectRoot);
  return state;
}

export function status({ projectRoot } = {}) {
  const state = readState(projectRoot);
  if (!state) {
    return { active: false, phase: null, fd_id: null };
  }
  return {
    active: !['COMPLETED', 'ABORTED'].includes(state.phase),
    ...state,
  };
}

const BRAINSTORM_MIN_IDEAS = 10;

export function advance({ to, note, projectRoot, now = new Date(), force = false } = {}) {
  if (!PHASES.includes(to)) {
    throw new Error(`Invalid target phase ${to}. Must be one of ${PHASES.join(', ')}`);
  }
  const state = readState(projectRoot);
  if (!state) throw new Error('No active FD session. Call start() first.');
  if (['COMPLETED', 'ABORTED'].includes(state.phase)) {
    throw new Error(`Current FD session is ${state.phase} and cannot advance.`);
  }

  const order = PHASES.indexOf(state.phase);
  const target = PHASES.indexOf(to);
  const transitionKey = `${state.phase}->${to}`;
  const isBackward = target < order;
  const isTerminal = ['ABORTED', 'COMPLETED'].includes(to);

  if (isBackward && !isTerminal && !BACKWARD_ALLOWED.has(transitionKey)) {
    throw new Error(
      `Cannot move backwards from ${state.phase} to ${to}. Allowed loop: REFACTOR->BUILD. Use abort() or fix the workflow.`
    );
  }

  // DISCOVERY exit gate : need project_context populated (unless forced)
  if (
    state.phase === 'DISCOVERY' &&
    to !== 'DISCOVERY' &&
    !isTerminal &&
    !force
  ) {
    if (!state.project_context) {
      throw new Error(
        `DISCOVERY requires project_context to be set before advancing. Populate via update({ patch: { project_context: { name, summary, source } } }), or pass force=true to skip.`
      );
    }
  }

  // BRAINSTORM exit gate : need >= BRAINSTORM_MIN_IDEAS raw ideas
  if (
    state.phase === 'BRAINSTORM' &&
    to !== 'BRAINSTORM' &&
    !isTerminal &&
    !force
  ) {
    const n = Array.isArray(state.raw_ideas) ? state.raw_ideas.length : 0;
    if (n < BRAINSTORM_MIN_IDEAS) {
      throw new Error(
        `BRAINSTORM requires at least ${BRAINSTORM_MIN_IDEAS} raw ideas before advancing (currently ${n}). Add more via update({ patch: { raw_ideas: [...] } }), or pass force=true to skip.`
      );
    }
  }

  state.phase = to;
  state.updated_at = now.toISOString();
  state.phase_history.push({ phase: to, entered_at: now.toISOString(), note: note || null });

  writeState(state, projectRoot);
  return state;
}

export function update({ patch = {}, projectRoot, now = new Date() } = {}) {
  const state = readState(projectRoot);
  if (!state) throw new Error('No active FD session.');

  const allowed = [
    'project_context',
    'raw_ideas',
    'backlog',
    'dispatch_table',
    'commits',
    'review_findings',
    'validate_verdict',
    'refactor_log',
    'doc_log',
    'notes',
    'feature_name',
  ];
  for (const key of Object.keys(patch)) {
    if (!allowed.includes(key)) {
      throw new Error(`Field "${key}" is not patchable. Allowed: ${allowed.join(', ')}`);
    }
    state[key] = patch[key];
  }
  state.updated_at = now.toISOString();

  writeState(state, projectRoot);
  return state;
}

export function abort({ reason, projectRoot, now = new Date() } = {}) {
  const state = readState(projectRoot);
  if (!state) throw new Error('No FD session to abort.');
  state.phase = 'ABORTED';
  state.updated_at = now.toISOString();
  state.phase_history.push({ phase: 'ABORTED', entered_at: now.toISOString(), note: reason || null });
  writeState(state, projectRoot);
  return state;
}

export const ALL_PHASES = PHASES;
export const BRAINSTORM_MIN = BRAINSTORM_MIN_IDEAS;
