'use strict';

const path = require('node:path');
const { execFileSync } = require('node:child_process');

const BranchFreshness = Object.freeze({
  FRESH: 'fresh',
  STALE: 'stale',
  DIVERGED: 'diverged',
  UNKNOWN: 'unknown',
});

const StaleBranchPolicy = Object.freeze({
  AUTO_REBASE: 'auto_rebase',
  AUTO_MERGE_FORWARD: 'auto_merge_forward',
  WARN_ONLY: 'warn_only',
  BLOCK: 'block',
});

const GreenLevel = Object.freeze({
  NONE: 'none',
  TARGETED_TESTS: 'targeted_tests',
  PACKAGE: 'package',
  WORKSPACE: 'workspace',
  MERGE_READY: 'merge_ready',
});

const GREEN_RANK = Object.freeze({
  [GreenLevel.NONE]: 0,
  [GreenLevel.TARGETED_TESTS]: 1,
  [GreenLevel.PACKAGE]: 2,
  [GreenLevel.WORKSPACE]: 3,
  [GreenLevel.MERGE_READY]: 4,
});

function branchLockCollision(candidate = {}, existingLocks = []) {
  const branch = String(candidate.branch || '');
  const modules = normalizeModules(candidate.modules || candidate.paths || candidate.writeScope || ['.']);
  for (const lock of existingLocks || []) {
    if (!lock || lock === candidate) continue;
    if (branch && lock.branch && String(lock.branch) !== branch) continue;
    const lockModules = normalizeModules(lock.modules || lock.paths || lock.writeScope || ['.']);
    if (modulesOverlap(modules, lockModules)) {
      return {
        collision: true,
        lock,
        reason: `branch ${branch || '<unknown>'} overlaps ${lock.laneId || lock.sessionId || 'existing lock'}`,
      };
    }
  }
  return { collision: false, lock: null, reason: '' };
}

function evaluateStaleBase({ expectedBase = '', currentBase = '', head = '' } = {}) {
  if (!expectedBase || !currentBase) {
    return { status: BranchFreshness.UNKNOWN, expectedBase, currentBase, head, stale: false, reason: 'missing_base' };
  }
  if (expectedBase === currentBase) {
    return { status: BranchFreshness.FRESH, expectedBase, currentBase, head, stale: false, reason: '' };
  }
  return { status: BranchFreshness.STALE, expectedBase, currentBase, head, stale: true, reason: 'base_changed' };
}

function evaluateBranchFreshness({ behind = 0, ahead = 0, missingFixes = 0, diverged = false } = {}) {
  const b = Number(behind) || 0;
  const a = Number(ahead) || 0;
  const missing = Number(missingFixes) || 0;
  if (diverged || (b > 0 && a > 0)) {
    return { status: BranchFreshness.DIVERGED, behind: b, ahead: a, missingFixes: missing };
  }
  if (b > 0 || missing > 0) {
    return { status: BranchFreshness.STALE, behind: b, ahead: a, missingFixes: missing };
  }
  return { status: BranchFreshness.FRESH, behind: b, ahead: a, missingFixes: missing };
}

function applyStaleBranchPolicy(freshness = {}, policy = StaleBranchPolicy.WARN_ONLY) {
  if (!freshness || freshness.status === BranchFreshness.FRESH) {
    return { action: 'allow', reason: 'branch_fresh' };
  }
  if (freshness.status === BranchFreshness.UNKNOWN) {
    return { action: 'warn', reason: 'branch_freshness_unknown' };
  }
  if (policy === StaleBranchPolicy.BLOCK) return { action: 'block', reason: freshness.status };
  if (policy === StaleBranchPolicy.AUTO_REBASE) return { action: 'rebase', reason: freshness.status };
  if (policy === StaleBranchPolicy.AUTO_MERGE_FORWARD) return { action: 'merge_forward', reason: freshness.status };
  return { action: 'warn', reason: freshness.status };
}

function evaluateGreenContract(required = GreenLevel.TARGETED_TESTS, observed = GreenLevel.NONE) {
  const need = normalizeGreen(required);
  const have = normalizeGreen(observed);
  return {
    required: need,
    observed: have,
    ok: GREEN_RANK[have] >= GREEN_RANK[need],
    missing: GREEN_RANK[have] >= GREEN_RANK[need] ? [] : levelsBetween(have, need),
  };
}

function collectGitFacts(cwd = process.cwd(), { base = 'main' } = {}) {
  const root = git(cwd, ['rev-parse', '--show-toplevel']).trim();
  const branch = git(root, ['branch', '--show-current']).trim();
  const head = git(root, ['rev-parse', 'HEAD']).trim();
  let ahead = 0;
  let behind = 0;
  try {
    const counts = git(root, ['rev-list', '--left-right', '--count', `${base}...HEAD`]).trim().split(/\s+/);
    behind = Number(counts[0]) || 0;
    ahead = Number(counts[1]) || 0;
  } catch {}
  const status = git(root, ['status', '--porcelain']).split('\n').filter(Boolean);
  return {
    root,
    branch,
    head,
    base,
    status,
    dirty: status.length > 0,
    freshness: evaluateBranchFreshness({ behind, ahead }),
  };
}

function normalizeModules(modules) {
  return (Array.isArray(modules) ? modules : [modules])
    .map((entry) => normalizePath(entry || '.'))
    .filter(Boolean);
}

function modulesOverlap(left, right) {
  for (const a of left) {
    for (const b of right) {
      if (a === '.' || b === '.') return true;
      if (a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`)) return true;
    }
  }
  return false;
}

function normalizePath(value) {
  return path.posix.normalize(String(value || '.').replace(/\\/g, '/')).replace(/^\.\//, '') || '.';
}

function normalizeGreen(value) {
  const text = String(value || '').toLowerCase();
  return Object.values(GreenLevel).includes(text) ? text : GreenLevel.NONE;
}

function levelsBetween(have, need) {
  return Object.values(GreenLevel).filter((level) => GREEN_RANK[level] > GREEN_RANK[have] && GREEN_RANK[level] <= GREEN_RANK[need]);
}

function git(cwd, args) {
  return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
}

module.exports = {
  BranchFreshness,
  GreenLevel,
  StaleBranchPolicy,
  applyStaleBranchPolicy,
  branchLockCollision,
  collectGitFacts,
  evaluateBranchFreshness,
  evaluateGreenContract,
  evaluateStaleBase,
  modulesOverlap,
};
