#!/usr/bin/env node
/**
 * OMS Transition CLI — Self-contained standalone script.
 *
 * Validated state transitions for OMS workflow modes. This script is the
 * single source of truth for state mutations — the LLM agent MUST use it
 * instead of writing to state files directly.
 *
 * Published with the package (scripts/oms-transition.cjs) and invoked via:
 *   node <package-root>/scripts/oms-transition.cjs <mode> <action> [args...]
 *
 * Actions:
 *   update <key=value>...     Validated state transition
 *   complete                  Quality-gate check, then mark complete
 *   cancel                    Mark workflow cancelled
 *   status                    Print current state as JSON
 *   phases                    Print valid next transitions
 *   validate-quality          Run quality gate without completing
 *
 * Exit codes: 0 = success, 1 = validation/transition error, 2 = usage error
 *
 * Zero external dependencies — uses only Node.js built-in modules.
 */

'use strict'

const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const VALID_MODES = new Set([
  'team',
  'ralph',
  'ralplan',
  'autopilot',
  'ultrawork',
  'clarify',
  'autoresearch',
  'goal',
])

const VALID_ACTIONS = new Set([
  'update',
  'complete',
  'cancel',
  'status',
  'phases',
  'validate-quality',
  'check-goal-state',
])

const VALID_STATUSES = new Set(['running', 'complete', 'cancelled', 'failed'])

const STATUS_TRANSITIONS = {
  running: new Set(['complete', 'cancelled', 'failed']),
  complete: new Set(),
  cancelled: new Set(),
  failed: new Set(['running']),
}

const PHASE_TRANSITIONS = {
  autopilot: {
    field: 'phase',
    transitions: {
      0: new Set([1]),
      1: new Set([2]),
      2: new Set([3]),
      3: new Set([4]),
      4: new Set([5]),
      5: new Set(),
    },
  },
  ralplan: {
    field: 'phase',
    transitions: {
      planning: new Set(['architect_review']),
      architect_review: new Set(['critic_review']),
      critic_review: new Set(['revision', 'approval']),
      revision: new Set(['planning']),
      approval: new Set(),
    },
  },
  clarify: {
    field: 'current_phase',
    transitions: {
      interview: new Set(['challenge', 'crystallize']),
      challenge: new Set(['interview', 'crystallize']),
      crystallize: new Set(['bridge']),
      bridge: new Set(),
    },
  },
}

const VALID_RALPLAN_PHASES = new Set([
  'planning',
  'architect_review',
  'critic_review',
  'revision',
  'approval',
])

const VALID_CLARIFY_PHASES = new Set([
  'interview',
  'challenge',
  'crystallize',
  'bridge',
])

/** Per-mode auxiliary artifact paths (relative to .oms/) */
const MODE_AUXILIARY_PATHS = {
  team: ['state/team-results'],
  ralph: ['state/ralph-prd.json', 'state/progress.txt'],
  autopilot: ['autopilot'],
  goal: ['ultragoal'],
  ralplan: ['state/handoff-tracking.json'],
  clarify: ['state/handoff-tracking.json'],
}

// ---------------------------------------------------------------------------
// Project root detection
// ---------------------------------------------------------------------------

function findProjectRoot(startDir) {
  const start = path.resolve(startDir || process.cwd())
  let current = start
  const markers = ['.oms', '.git', 'package.json']

  for (let i = 0; i < 10; i++) {
    for (const marker of markers) {
      const candidate = path.join(current, marker)
      if (fs.existsSync(candidate)) {
        if (marker === '.oms' || marker === '.git') {
          try {
            if (fs.statSync(candidate).isDirectory()) return current
          } catch (_) {
            /* skip */
          }
        } else {
          return current
        }
      }
    }
    const parent = path.dirname(current)
    if (parent === current) break
    current = parent
  }
  return start
}

// ---------------------------------------------------------------------------
// State I/O
// ---------------------------------------------------------------------------

function getOmsStateDir(projectRoot) {
  return path.join(projectRoot, '.oms', 'state')
}

function ensureOmsStateDir(projectRoot) {
  const dir = getOmsStateDir(projectRoot)
  fs.mkdirSync(dir, { recursive: true })
  return dir
}

function getStateFilePath(projectRoot, mode) {
  return path.join(getOmsStateDir(projectRoot), `${mode}-state.json`)
}

function readModeState(projectRoot, mode) {
  const filePath = getStateFilePath(projectRoot, mode)
  if (!fs.existsSync(filePath)) return null
  try {
    const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
    const v = validateModeState(parsed)
    if (!v.valid) return null
    return parsed
  } catch (_) {
    return null
  }
}

function writeModeState(projectRoot, state) {
  const dir = ensureOmsStateDir(projectRoot)
  const filePath = path.join(dir, `${state.mode}-state.json`)
  fs.writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8')
}

function clearModeState(projectRoot, mode) {
  const filePath = getStateFilePath(projectRoot, mode)
  if (fs.existsSync(filePath)) fs.unlinkSync(filePath)
}

function clearModeArtifacts(projectRoot, mode) {
  const omsDir = path.join(projectRoot, '.oms')
  const paths = MODE_AUXILIARY_PATHS[mode]
  if (!paths) return
  for (const relPath of paths) {
    const fullPath = path.join(omsDir, relPath)
    if (!fs.existsSync(fullPath)) continue
    try {
      const stat = fs.statSync(fullPath)
      if (stat.isDirectory()) {
        fs.rmSync(fullPath, { recursive: true, force: true })
      } else {
        fs.unlinkSync(fullPath)
      }
    } catch (_) {
      /* best-effort */
    }
  }
}

// ---------------------------------------------------------------------------
// Validation
// ---------------------------------------------------------------------------

function validateBase(obj) {
  const errors = []
  if (typeof obj.mode !== 'string' || !VALID_MODES.has(obj.mode))
    errors.push(`mode must be one of: ${[...VALID_MODES].join(', ')}`)
  if (typeof obj.status !== 'string' || !VALID_STATUSES.has(obj.status))
    errors.push(`status must be one of: ${[...VALID_STATUSES].join(', ')}`)
  if (typeof obj.active !== 'boolean') errors.push('active must be boolean')
  if (typeof obj.started_at !== 'string' || obj.started_at.length === 0)
    errors.push('started_at must be a non-empty ISO string')
  return { valid: errors.length === 0, errors }
}

function validateTeamState(obj) {
  const e = []
  if (
    typeof obj.worker_count !== 'number' ||
    obj.worker_count < 1 ||
    obj.worker_count > 10
  )
    e.push('worker_count must be 1-10')
  if (typeof obj.task !== 'string' || obj.task.length === 0)
    e.push('task must be non-empty')
  return { valid: e.length === 0, errors: e }
}

function validateRalphState(obj) {
  const e = []
  if (typeof obj.iteration !== 'number' || obj.iteration < 0)
    e.push('iteration must be >= 0')
  return { valid: e.length === 0, errors: e }
}

function validateRalplanState(obj) {
  const e = []
  if (typeof obj.phase !== 'string' || !VALID_RALPLAN_PHASES.has(obj.phase))
    e.push(`phase must be one of: ${[...VALID_RALPLAN_PHASES].join(', ')}`)
  if (typeof obj.iteration !== 'number' || obj.iteration < 0)
    e.push('iteration must be >= 0')
  if (typeof obj.max_iterations !== 'number' || obj.max_iterations < 1)
    e.push('max_iterations must be >= 1')
  return { valid: e.length === 0, errors: e }
}

function validateAutopilotState(obj) {
  const e = []
  if (typeof obj.phase !== 'number' || obj.phase < 0 || obj.phase > 5)
    e.push('phase must be 0-5')
  return { valid: e.length === 0, errors: e }
}

function validateUltraworkState(obj) {
  const e = []
  if (typeof obj.task !== 'string' || obj.task.length === 0)
    e.push('task must be non-empty')
  return { valid: e.length === 0, errors: e }
}

function validateClarifyState(obj) {
  const e = []
  if (
    typeof obj.current_phase !== 'string' ||
    !VALID_CLARIFY_PHASES.has(obj.current_phase)
  )
    e.push(
      `current_phase must be one of: ${[...VALID_CLARIFY_PHASES].join(', ')}`,
    )
  if (typeof obj.rounds !== 'number' || obj.rounds < 0)
    e.push('rounds must be >= 0')
  if (
    typeof obj.current_ambiguity !== 'number' ||
    obj.current_ambiguity < 0 ||
    obj.current_ambiguity > 1
  )
    e.push('current_ambiguity must be 0-1')
  if (
    typeof obj.threshold !== 'number' ||
    obj.threshold < 0 ||
    obj.threshold > 1
  )
    e.push('threshold must be 0-1')
  return { valid: e.length === 0, errors: e }
}

function validateAutoresearchState(obj) {
  const e = []
  if (typeof obj.mission_slug !== 'string' || obj.mission_slug.length === 0)
    e.push('mission_slug must be non-empty')
  if (typeof obj.iteration !== 'number' || obj.iteration < 0)
    e.push('iteration must be >= 0')
  return { valid: e.length === 0, errors: e }
}

const MODE_VALIDATORS = {
  team: validateTeamState,
  ralph: validateRalphState,
  ralplan: validateRalplanState,
  autopilot: validateAutopilotState,
  ultrawork: validateUltraworkState,
  clarify: validateClarifyState,
  autoresearch: validateAutoresearchState,
  goal: null,
}

function validateModeState(state) {
  if (typeof state !== 'object' || state === null)
    return { valid: false, errors: ['state must be a non-null object'] }
  const base = validateBase(state)
  if (!base.valid) return base
  const validator = MODE_VALIDATORS[state.mode]
  if (!validator) return { valid: true, errors: [] }
  return validator(state)
}

function assertValidModeState(state) {
  const result = validateModeState(state)
  if (!result.valid) {
    throw new Error(
      `Invalid ${state.mode || 'unknown'} state: ${result.errors.join('; ')}`,
    )
  }
}

// ---------------------------------------------------------------------------
// State machine
// ---------------------------------------------------------------------------

class TransitionError extends Error {
  constructor(mode, from, to, message) {
    super(message || `Invalid ${mode} transition: ${from} -> ${to}`)
    this.name = 'TransitionError'
    this.mode = mode
    this.from = from
    this.to = to
  }
}

function transitionState(projectRoot, mode, updates) {
  const current = readModeState(projectRoot, mode)
  if (!current) {
    throw new TransitionError(
      mode,
      'null',
      'any',
      `Cannot transition ${mode}: no state file found`,
    )
  }

  const next = Object.assign({}, current, updates)

  // Validate status transition
  if (updates.status && updates.status !== current.status) {
    const allowed = STATUS_TRANSITIONS[current.status]
    if (!allowed || !allowed.has(updates.status)) {
      throw new TransitionError(
        mode,
        `status:${current.status}`,
        `status:${updates.status}`,
      )
    }
  }

  // Validate phase transition
  const phaseConfig = PHASE_TRANSITIONS[mode]
  if (phaseConfig) {
    const currentPhase = String(current[phaseConfig.field])
    const nextPhase = String(next[phaseConfig.field])
    if (currentPhase !== nextPhase) {
      const allowed = phaseConfig.transitions[currentPhase]
      if (!allowed || !allowed.has(next[phaseConfig.field])) {
        throw new TransitionError(
          mode,
          `${phaseConfig.field}:${currentPhase}`,
          `${phaseConfig.field}:${nextPhase}`,
        )
      }
    }
  }

  assertValidModeState(next)
  writeModeState(projectRoot, next)
  return next
}

function completeMode(projectRoot, mode) {
  const final = transitionState(projectRoot, mode, {
    status: 'complete',
    active: false,
    completed_at: new Date().toISOString(),
  })
  clearModeState(projectRoot, mode)
  clearModeArtifacts(projectRoot, mode)
  return final
}

function cancelMode(projectRoot, mode) {
  const final = transitionState(projectRoot, mode, {
    status: 'cancelled',
    active: false,
    completed_at: new Date().toISOString(),
  })
  clearModeState(projectRoot, mode)
  clearModeArtifacts(projectRoot, mode)
  return final
}

function getValidTransitions(projectRoot, mode) {
  const current = readModeState(projectRoot, mode)
  if (!current) return { statuses: [], phases: [] }

  const statuses = [...(STATUS_TRANSITIONS[current.status] || [])]

  const phaseConfig = PHASE_TRANSITIONS[mode]
  if (!phaseConfig) return { statuses, phases: [] }

  const currentPhase = String(current[phaseConfig.field])
  const phases = [...(phaseConfig.transitions[currentPhase] || [])]

  return { statuses, phases }
}

// ---------------------------------------------------------------------------
// Quality gates
// ---------------------------------------------------------------------------

function checkRalphQualityGate(projectRoot) {
  const prdPath = path.join(projectRoot, '.oms', 'state', 'ralph-prd.json')
  if (!fs.existsSync(prdPath)) {
    return {
      mode: 'ralph',
      passed: false,
      reason:
        'Cannot complete ralph: no PRD file found at .oms/state/ralph-prd.json. Create the PRD first.',
    }
  }
  let prd
  try {
    prd = JSON.parse(fs.readFileSync(prdPath, 'utf-8'))
  } catch (_) {
    return {
      mode: 'ralph',
      passed: false,
      reason: 'Cannot complete ralph: failed to parse ralph-prd.json',
    }
  }
  const stories = prd.stories
  if (!Array.isArray(stories) || stories.length === 0) {
    return {
      mode: 'ralph',
      passed: false,
      reason: 'Cannot complete ralph: no stories found in PRD.',
    }
  }
  const allPass = stories.every(
    s => typeof s === 'object' && s !== null && s.passes === true,
  )
  if (!allPass) {
    const passed = stories.filter(
      s => typeof s === 'object' && s !== null && s.passes === true,
    ).length
    return {
      mode: 'ralph',
      passed: false,
      reason: `Cannot complete ralph: only ${passed}/${stories.length} PRD stories pass verification.`,
    }
  }
  return { mode: 'ralph', passed: true, reason: 'All PRD stories pass' }
}

function checkAutopilotQualityGate(projectRoot) {
  const statePath = path.join(
    projectRoot,
    '.oms',
    'state',
    'autopilot-state.json',
  )
  if (!fs.existsSync(statePath)) {
    return {
      mode: 'autopilot',
      passed: false,
      reason: 'Cannot complete autopilot: no state file found.',
    }
  }
  let state
  try {
    state = JSON.parse(fs.readFileSync(statePath, 'utf-8'))
  } catch (_) {
    return {
      mode: 'autopilot',
      passed: false,
      reason: 'Cannot complete autopilot: failed to parse state file',
    }
  }
  if (typeof state.phase !== 'number' || state.phase < 5) {
    return {
      mode: 'autopilot',
      passed: false,
      reason: `Cannot complete autopilot: at phase ${state.phase || '?'}/5. Must reach phase 5.`,
    }
  }
  return {
    mode: 'autopilot',
    passed: true,
    reason: 'Autopilot at phase 5 (cleanup)',
  }
}

/**
 * Inline goal quality gate — checks essential invariants from
 * check-goal-state.ts without requiring bun or the TS source file.
 */
function checkGoalQualityGate(projectRoot) {
  const statePath = path.join(projectRoot, '.oms', 'ultragoal', 'state.json')
  if (!fs.existsSync(statePath)) {
    return {
      mode: 'goal',
      passed: true,
      reason: 'No goal state.json found (early workflow)',
    }
  }
  let state
  try {
    state = JSON.parse(fs.readFileSync(statePath, 'utf-8'))
  } catch (_) {
    return {
      mode: 'goal',
      passed: false,
      reason: 'Cannot complete goal: failed to parse state.json',
    }
  }

  const issues = []

  // Multiple in-progress goals
  if (Array.isArray(state.goals)) {
    const inProgress = state.goals.filter(g => g.status === 'in_progress')
    if (inProgress.length > 1) {
      issues.push(`${inProgress.length} goals are in_progress (max 1)`)
    }

    // activeGoalId consistency
    if (state.activeGoalId) {
      const active = state.goals.find(g => g.id === state.activeGoalId)
      if (!active) {
        issues.push(
          `activeGoalId "${state.activeGoalId}" does not match any goal`,
        )
      } else if (active.status !== 'in_progress') {
        issues.push(
          `activeGoalId points to goal with status "${active.status}"`,
        )
      }
    }

    // Complete goals should have oracle pass
    for (const goal of state.goals) {
      if (
        goal.status === 'complete' &&
        goal.oracle &&
        goal.oracle.last_result !== 'pass'
      ) {
        issues.push(
          `${goal.id}: complete but oracle.last_result is "${goal.oracle.last_result}"`,
        )
      }
    }

    // Complete slices need receipts
    for (const goal of state.goals) {
      for (const slice of goal.slices || []) {
        if (slice.status === 'complete' && !slice.receipt) {
          issues.push(`${goal.id}/${slice.id}: completed without receipt`)
        }
      }
    }
  }

  if (issues.length > 0) {
    return {
      mode: 'goal',
      passed: false,
      reason: `Cannot complete goal: state validation failed:\n${issues.join('\n')}`,
    }
  }
  return { mode: 'goal', passed: true, reason: 'Goal state validation passed' }
}

function readHandoffTracking(projectRoot) {
  const trackingPath = path.join(
    projectRoot,
    '.oms',
    'state',
    'handoff-tracking.json',
  )
  if (!fs.existsSync(trackingPath)) return null
  try {
    const data = JSON.parse(fs.readFileSync(trackingPath, 'utf-8'))
    if (data && Array.isArray(data.calls)) return data
    return null
  } catch (_) {
    return null
  }
}

function hasHandoffTo(tracking, targets) {
  if (!tracking || !tracking.calls || tracking.calls.length === 0) return false
  return tracking.calls.some(call => targets.has(call.skill))
}

const RALPLAN_HANDOFF_TARGETS = new Set([
  'oms-team',
  'oms-ralph',
  'team',
  'ralph',
])

function checkRalplanQualityGate(projectRoot) {
  const tracking = readHandoffTracking(projectRoot)
  if (!hasHandoffTo(tracking, RALPLAN_HANDOFF_TARGETS)) {
    return {
      mode: 'ralplan',
      passed: false,
      reason:
        'Cannot complete ralplan: no Skill() handoff to an execution mode (oms-team or oms-ralph) was recorded.',
    }
  }
  return { mode: 'ralplan', passed: true, reason: 'Ralplan handoff verified' }
}

const CLARIFY_HANDOFF_TARGETS = new Set([
  'oms-ralplan',
  'oms-autopilot',
  'oms-ralph',
  'oms-team',
  'ralplan',
  'autopilot',
  'ralph',
  'team',
])

function checkClarifyQualityGate(projectRoot) {
  const tracking = readHandoffTracking(projectRoot)
  if (!hasHandoffTo(tracking, CLARIFY_HANDOFF_TARGETS)) {
    return {
      mode: 'clarify',
      passed: false,
      reason:
        'Cannot complete clarify: no Skill() handoff to an execution mode was recorded.',
    }
  }
  return { mode: 'clarify', passed: true, reason: 'Clarify handoff verified' }
}

function checkQualityGate(projectRoot, mode) {
  switch (mode) {
    case 'ralph':
      return checkRalphQualityGate(projectRoot)
    case 'autopilot':
      return checkAutopilotQualityGate(projectRoot)
    case 'goal':
      return checkGoalQualityGate(projectRoot)
    case 'ralplan':
      return checkRalplanQualityGate(projectRoot)
    case 'clarify':
      return checkClarifyQualityGate(projectRoot)
    default:
      return null
  }
}

// ---------------------------------------------------------------------------
// Goal state validator (full check — inlined from check-goal-state.ts)
// ---------------------------------------------------------------------------

function goalCheckSchemaCompliance(state) {
  const issues = []

  if (state.version !== 2) {
    issues.push(`version must be 2, got ${state.version}`)
  }

  if (!state.intake) {
    issues.push('missing intake block')
  } else {
    const requiredIntakeFields = [
      'interpreted_outcome',
      'input_shape',
      'proof_type',
      'completion_proof',
      'likely_misfire',
    ]
    for (const field of requiredIntakeFields) {
      const val = state.intake[field]
      if (!val || (typeof val === 'string' && val.trim() === '')) {
        issues.push(`intake.${field} is empty or missing`)
      }
    }
    const validShapes = [
      'vague',
      'specific',
      'existing_plan',
      'recovery',
      'audit',
    ]
    if (
      state.intake.input_shape &&
      !validShapes.includes(state.intake.input_shape)
    ) {
      issues.push(`intake.input_shape must be one of ${validShapes.join(', ')}`)
    }
    const validProofTypes = ['test', 'demo', 'artifact', 'metric', 'review']
    if (
      state.intake.proof_type &&
      !validProofTypes.includes(state.intake.proof_type)
    ) {
      issues.push(
        `intake.proof_type must be one of ${validProofTypes.join(', ')}`,
      )
    }
  }

  if (!state.oracle) {
    issues.push('missing oracle block')
  } else {
    if (!state.oracle.signal || state.oracle.signal.trim() === '') {
      issues.push('oracle.signal is empty or missing')
    }
    if (!state.oracle.final_proof || state.oracle.final_proof.trim() === '') {
      issues.push('oracle.final_proof is empty or missing')
    }
    const validCadences = ['per_goal', 'per_slice', 'final']
    if (state.oracle.cadence && !validCadences.includes(state.oracle.cadence)) {
      issues.push(`oracle.cadence must be one of ${validCadences.join(', ')}`)
    }
  }

  if (!Array.isArray(state.goals)) {
    issues.push('goals must be an array')
  }

  return {
    category: 'Schema compliance',
    status: issues.length > 0 ? 'FAIL' : 'PASS',
    details: issues,
  }
}

function goalCheckActiveGoalInvariants(state) {
  const issues = []
  const inProgress = state.goals.filter(g => g.status === 'in_progress')

  if (inProgress.length > 1) {
    issues.push(
      `${inProgress.length} goals are in_progress (max 1): ${inProgress.map(g => g.id).join(', ')}`,
    )
  }

  if (state.activeGoalId) {
    const activeGoal = state.goals.find(g => g.id === state.activeGoalId)
    if (!activeGoal) {
      issues.push(
        `activeGoalId "${state.activeGoalId}" does not match any goal`,
      )
    } else if (activeGoal.status !== 'in_progress') {
      issues.push(
        `activeGoalId "${state.activeGoalId}" points to goal with status "${activeGoal.status}" (expected "in_progress")`,
      )
    }
  } else if (inProgress.length === 1) {
    issues.push(
      `goal "${inProgress[0].id}" is in_progress but activeGoalId is null`,
    )
  }

  for (const goal of inProgress) {
    const activeSlices = (goal.slices || []).filter(
      s => s.status === 'in_progress' || s.status === 'pending',
    )
    if (
      activeSlices.length === 0 &&
      (goal.slices ? goal.slices.length : 0) > 0
    ) {
      issues.push(
        `goal "${goal.id}" is in_progress but has no active/pending slices`,
      )
    }
  }

  return {
    category: 'Active goal invariants',
    status: issues.length > 0 ? 'FAIL' : 'PASS',
    details: issues,
  }
}

function goalCheckReceiptCompliance(state) {
  const issues = []

  for (const goal of state.goals) {
    for (const slice of goal.slices || []) {
      if (slice.status === 'complete') {
        if (!slice.receipt) {
          issues.push(
            `${goal.id}/${slice.id}: ${slice.role} slice completed without receipt`,
          )
          continue
        }

        if (slice.role === 'worker') {
          const changedFiles = slice.receipt.changed_files
          if (!Array.isArray(changedFiles) || changedFiles.length === 0) {
            issues.push(
              `${goal.id}/${slice.id}: WorkerReceipt missing or empty changed_files`,
            )
          }
        }

        if (slice.role === 'judge') {
          if (
            typeof slice.receipt.decision !== 'string' ||
            slice.receipt.decision.trim() === ''
          ) {
            issues.push(`${goal.id}/${slice.id}: JudgeReceipt missing decision`)
          }
          if (typeof slice.receipt.full_outcome_complete !== 'boolean') {
            issues.push(
              `${goal.id}/${slice.id}: JudgeReceipt missing full_outcome_complete boolean`,
            )
          }
        }

        if (slice.role === 'scout') {
          if (
            typeof slice.receipt.summary !== 'string' ||
            slice.receipt.summary.trim() === ''
          ) {
            issues.push(`${goal.id}/${slice.id}: ScoutReceipt missing summary`)
          }
        }
      }
    }
  }

  return {
    category: 'Receipt schema compliance',
    status: issues.length > 0 ? 'FAIL' : 'PASS',
    details: issues,
  }
}

function goalCheckSliceSizing(state) {
  const warnings = []

  for (const goal of state.goals) {
    let consecutiveTiny = 0
    for (const slice of goal.slices || []) {
      if (slice.size === 'tiny' && slice.status === 'complete') {
        consecutiveTiny++
        if (consecutiveTiny > 2) {
          warnings.push(
            `${goal.id}: ${consecutiveTiny} consecutive tiny slices (max recommended: 2)`,
          )
        }
      } else {
        consecutiveTiny = 0
      }
    }
  }

  return {
    category: 'Slice sizing policy',
    status: warnings.length > 0 ? 'WARN' : 'PASS',
    details: warnings,
  }
}

function goalCheckOracleConsistency(state) {
  const issues = []

  for (const goal of state.goals) {
    if (goal.status === 'complete') {
      if (!goal.oracle) {
        issues.push(`${goal.id}: complete goal has no oracle defined`)
        continue
      }
      if (goal.oracle.last_result !== 'pass') {
        issues.push(
          `${goal.id}: complete goal but oracle.last_result is "${goal.oracle.last_result}" (expected "pass")`,
        )
      }
    }
  }

  return {
    category: 'Oracle consistency',
    status: issues.length > 0 ? 'FAIL' : 'PASS',
    details: issues,
  }
}

function goalCheckTemporalConsistency(state) {
  const issues = []

  for (const goal of state.goals) {
    if (goal.startedAt && goal.completedAt) {
      if (new Date(goal.startedAt) > new Date(goal.completedAt)) {
        issues.push(
          `${goal.id}: startedAt (${goal.startedAt}) is after completedAt (${goal.completedAt})`,
        )
      }
    }
    if (goal.createdAt && goal.startedAt) {
      if (new Date(goal.createdAt) > new Date(goal.startedAt)) {
        issues.push(
          `${goal.id}: createdAt (${goal.createdAt}) is after startedAt (${goal.startedAt})`,
        )
      }
    }

    for (const slice of goal.slices || []) {
      if (slice.startedAt && slice.completedAt) {
        if (new Date(slice.startedAt) > new Date(slice.completedAt)) {
          issues.push(`${goal.id}/${slice.id}: startedAt is after completedAt`)
        }
      }
    }
  }

  return {
    category: 'Temporal consistency',
    status: issues.length > 0 ? 'FAIL' : 'PASS',
    details: issues,
  }
}

function formatGoalReport(filePath, state, results) {
  const lines = []

  lines.push('OMS Goal State Validation')
  lines.push('=========================')
  lines.push(`File: ${filePath}`)

  if (state) {
    lines.push(`Version: ${state.version}`)
    const statusCounts = {}
    for (const g of state.goals) {
      statusCounts[g.status] = (statusCounts[g.status] || 0) + 1
    }
    const countsStr = Object.entries(statusCounts)
      .map(([s, c]) => `${c} ${s}`)
      .join(', ')
    lines.push(`Goals: ${state.goals.length} (${countsStr})`)
  }

  lines.push('')

  let violations = 0
  let warnings = 0

  for (const r of results) {
    const prefix = `[${r.status}]`
    lines.push(`${prefix} ${r.category}`)
    for (const d of r.details) {
      lines.push(`       ${d}`)
    }
    if (r.status === 'FAIL') violations++
    if (r.status === 'WARN') warnings++
  }

  lines.push('')

  if (violations === 0 && warnings === 0) {
    lines.push('Result: PASS')
  } else if (violations === 0) {
    lines.push(`Result: PASS (${warnings} warning${warnings > 1 ? 's' : ''})`)
  } else {
    lines.push(
      `Result: FAIL (${violations} violation${violations > 1 ? 's' : ''}, ${warnings} warning${warnings > 1 ? 's' : ''})`,
    )
  }

  return lines.join('\n')
}

function doCheckGoalState(projectRoot, args) {
  const filePath = args[2]
  const absPath = filePath
    ? path.resolve(filePath)
    : path.join(projectRoot, '.oms', 'ultragoal', 'state.json')

  if (!fs.existsSync(absPath)) {
    process.stderr.write(`File not found: ${absPath}\n`)
    process.exit(2)
  }

  let raw
  try {
    raw = fs.readFileSync(absPath, 'utf-8')
  } catch (err) {
    process.stderr.write(`Failed to read file: ${err.message}\n`)
    process.exit(2)
  }

  let state
  try {
    state = JSON.parse(raw)
  } catch (err) {
    process.stderr.write(`Invalid JSON: ${err.message}\n`)
    process.exit(2)
  }

  const results = [
    goalCheckSchemaCompliance(state),
    goalCheckActiveGoalInvariants(state),
    goalCheckReceiptCompliance(state),
    goalCheckSliceSizing(state),
    goalCheckOracleConsistency(state),
    goalCheckTemporalConsistency(state),
  ]

  const report = formatGoalReport(absPath, state, results)
  console.log(report)

  const hasViolations = results.some(r => r.status === 'FAIL')
  process.exit(hasViolations ? 1 : 0)
}

// ---------------------------------------------------------------------------
// CLI argument parsing
// ---------------------------------------------------------------------------

function parseKeyValuePairs(args) {
  const result = {}
  for (const arg of args) {
    const eqIdx = arg.indexOf('=')
    if (eqIdx === -1) continue
    const key = arg.slice(0, eqIdx)
    const raw = arg.slice(eqIdx + 1)
    if (raw === 'true') result[key] = true
    else if (raw === 'false') result[key] = false
    else if (/^-?\d+(\.\d+)?$/.test(raw)) result[key] = Number.parseFloat(raw)
    else result[key] = raw
  }
  return result
}

// ---------------------------------------------------------------------------
// Actions
// ---------------------------------------------------------------------------

function doUpdate(projectRoot, mode, kvPairs) {
  if (Object.keys(kvPairs).length === 0) {
    process.stderr.write('Error: update requires at least one key=value pair\n')
    process.exit(2)
  }
  try {
    const result = transitionState(projectRoot, mode, kvPairs)
    console.log(JSON.stringify(result, null, 2))
  } catch (err) {
    if (err instanceof TransitionError) {
      process.stderr.write(
        `TransitionError: ${err.mode} ${err.from} -> ${err.to}: ${err.message}\n`,
      )
    } else {
      process.stderr.write(`Error: ${err.message}\n`)
    }
    process.exit(1)
  }
}

function doComplete(projectRoot, mode) {
  const gate = checkQualityGate(projectRoot, mode)
  if (gate && !gate.passed) {
    process.stderr.write(`QualityGateError: ${gate.reason}\n`)
    process.exit(1)
  }
  try {
    const result = completeMode(projectRoot, mode)
    console.log(JSON.stringify(result, null, 2))
  } catch (err) {
    process.stderr.write(`Error: ${err.message}\n`)
    process.exit(1)
  }
}

function doCancel(projectRoot, mode) {
  try {
    const result = cancelMode(projectRoot, mode)
    console.log(JSON.stringify(result, null, 2))
  } catch (err) {
    process.stderr.write(`Error: ${err.message}\n`)
    process.exit(1)
  }
}

function doStatus(projectRoot, mode) {
  const state = readModeState(projectRoot, mode)
  if (!state) {
    console.log('null')
    return
  }
  console.log(JSON.stringify(state, null, 2))
}

function doPhases(projectRoot, mode) {
  const transitions = getValidTransitions(projectRoot, mode)
  console.log(JSON.stringify(transitions, null, 2))
}

function doValidateQuality(projectRoot, mode) {
  const gate = checkQualityGate(projectRoot, mode)
  if (!gate) {
    console.log(
      JSON.stringify(
        { mode, passed: true, reason: 'No quality gate configured' },
        null,
        2,
      ),
    )
    return
  }
  console.log(JSON.stringify(gate, null, 2))
  if (!gate.passed) process.exit(1)
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

function main() {
  const args = process.argv.slice(2)

  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
    console.log(
      [
        'Usage: oms-transition <mode> <action> [args...]',
        '',
        'Modes: team, ralph, ralplan, autopilot, ultrawork, clarify, autoresearch, goal',
        '',
        'Actions:',
        '  update <key=value>...   Validated state transition',
        '  complete                Quality-gate check, then mark complete (deletes state file)',
        '  cancel                  Mark workflow cancelled (deletes state file)',
        '  status                  Print current state as JSON',
        '  phases                  Print valid next transitions',
        '  validate-quality        Run quality gate without completing',
        '  check-goal-state [path]  Validate goal state.json (default: .oms/ultragoal/state.json)',
        '',
        'Examples:',
        '  oms-transition autopilot update phase=1 phase_name=planning',
        '  oms-transition ralph update iteration=2 current_story=US-002',
        '  oms-transition team complete',
        '  oms-transition ralplan status',
      ].join('\n'),
    )
    process.exit(0)
  }

  const mode = args[0]
  const action = args[1]

  if (!VALID_MODES.has(mode)) {
    process.stderr.write(
      `Error: invalid mode "${mode}". Valid: ${[...VALID_MODES].join(', ')}\n`,
    )
    process.exit(2)
  }

  if (!action || !VALID_ACTIONS.has(action)) {
    process.stderr.write(
      `Error: invalid action "${action}". Valid: ${[...VALID_ACTIONS].join(', ')}\n`,
    )
    process.exit(2)
  }

  const projectRoot = findProjectRoot()

  switch (action) {
    case 'update':
      doUpdate(projectRoot, mode, parseKeyValuePairs(args.slice(2)))
      break
    case 'complete':
      doComplete(projectRoot, mode)
      break
    case 'cancel':
      doCancel(projectRoot, mode)
      break
    case 'status':
      doStatus(projectRoot, mode)
      break
    case 'phases':
      doPhases(projectRoot, mode)
      break
    case 'validate-quality':
      doValidateQuality(projectRoot, mode)
      break
    case 'check-goal-state':
      doCheckGoalState(projectRoot, args)
      break
  }
}

main()
