#!/bin/bash
# Comet State — unified interface for .comet.yaml state management
# Usage: comet-state.sh <subcommand> <change-name> [args...]
#
# Subcommands:
#   init <change-name> <workflow>  — Initialize .comet.yaml with workflow defaults
#   get <change-name> <field>       — Read a field value from .comet.yaml
#   set <change-name> <field> <val> — Update a field value
#   transition <change-name> <event> — Apply a validated state transition
#   check <change-name> <phase>    — Verify entry requirements for a phase
#   check <change-name> <phase> --recover — Output structured recovery context for compaction resume
#   scale <change-name>             — Assess and set verification mode based on metrics
#   task-checkoff <file> <task-text> — Verify one unique task is checked
#
# Workflows: full, hotfix, tweak
# Phases for check: open, design, build, verify, archive

set -euo pipefail

# --- Color output helpers ---

red() { echo -e "\033[31m$1\033[0m" >&2; }
green() { echo -e "\033[32m$1\033[0m" >&2; }
yellow() { echo -e "\033[33m$1\033[0m" >&2; }

# --- Script location ---

# shellcheck disable=SC2034
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# --- Input validation ---

validate_change_name() {
  local name="$1"
  # Reject empty names
  if [ -z "$name" ]; then
    red "ERROR: Change name cannot be empty" >&2
    exit 1
  fi
  # Only allow alphanumeric, hyphens, and underscores
  if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
    red "ERROR: Invalid change name: '$name'" >&2
    red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
    exit 1
  fi
  # Reject path traversal attempts
  if [[ "$name" =~ \.\. ]]; then
    red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
    exit 1
  fi
}

validate_enum() {
  local value="$1"
  shift
  local valid_values=("$@")

  for valid in "${valid_values[@]}"; do
    if [ "$value" = "$valid" ]; then
      return 0
    fi
  done

  red "ERROR: Invalid value: '$value'" >&2
  red "Valid values: ${valid_values[*]}" >&2
  exit 1
}

validate_path_field() {
  local value="$1"
  local field="$2"
  # null and empty are acceptable (means "not set")
  if [ -z "$value" ] || [ "$value" = "null" ]; then
    return 0
  fi
  # Reject absolute paths and home-directory references
  case "$value" in
    /*|~*|[A-Za-z]:*|\\*)
      red "ERROR: $field must be a relative path within the repo: '$value'" >&2
      exit 1
      ;;
  esac
  if [[ "$value" =~ \.\. ]]; then
    red "ERROR: $field cannot contain '..' (path traversal not allowed): '$value'" >&2
    exit 1
  fi
}

# --- Helper functions ---

yaml_field() {
  local field="$1"
  local yaml_file="$2"
  if [ -f "$yaml_file" ]; then
    local value
    value=$(grep "^${field}:" "$yaml_file" 2>/dev/null | sed "s/^${field}: *//" || true)
    value=$(strip_inline_comment "$value")
    strip_wrapping_quotes "$value"
  fi
}

strip_inline_comment() {
  local value="$1"
  printf '%s\n' "$value" | awk -v squote="'" '
    {
      out = ""
      quote = ""
      for (i = 1; i <= length($0); i++) {
        c = substr($0, i, 1)
        if (quote == "") {
          if (c == "\"" || c == squote) {
            quote = c
          } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
            sub(/[[:space:]]+$/, "", out)
            print out
            next
          }
        } else if (c == quote) {
          quote = ""
        }
        out = out c
      }
      print out
    }
  '
}

strip_wrapping_quotes() {
  local value="$1"
  case "$value" in
    \"*\")
      printf '%s\n' "${value:1:${#value}-2}"
      ;;
    \'*\')
      printf '%s\n' "${value:1:${#value}-2}"
      ;;
    *)
      printf '%s\n' "$value"
      ;;
  esac
}

replace_yaml_field() {
  local yaml_file="$1"
  local field="$2"
  local value="$3"
  local tmp_file

  tmp_file=$(mktemp)
  chmod 600 "$tmp_file"
  # Replace the target field, then deduplicate all fields keeping only the
  # last occurrence of each key. Prevents stale earlier values from
  # persisting when a field is set multiple times.
  awk -v field="$field" -v value="$value" '
    index($0, field ":") == 1 { $0 = field ": " value }
    { buf[NR] = $0; keys[NR] = $0; sub(/:.*$/, "", keys[NR]); n = NR }
    END {
      for (i = 1; i <= n; i++) last[keys[i]] = i
      for (i = 1; i <= n; i++) if (last[keys[i]] == i) print buf[i]
    }
  ' "$yaml_file" > "$tmp_file"
  mv "$tmp_file" "$yaml_file"
}

file_nonempty() {
  [ -f "$1" ] && [ -s "$1" ]
}

change_dir_for() {
  local change_name="$1"
  if [ -d "openspec/changes/$change_name" ]; then
    echo "openspec/changes/$change_name"
  elif [ -d "openspec/changes/archive/$change_name" ]; then
    echo "openspec/changes/archive/$change_name"
  else
    echo "openspec/changes/$change_name"
  fi
}

yaml_file_for() {
  local change_name="$1"
  local change_dir
  change_dir=$(change_dir_for "$change_name")
  echo "$change_dir/.comet.yaml"
}

project_context_compression() {
  local value="off"
  local source="default"
  if [ -n "${COMET_CONTEXT_COMPRESSION:-}" ]; then
    value="$COMET_CONTEXT_COMPRESSION"
    source="COMET_CONTEXT_COMPRESSION"
  elif [ -f ".comet/config.yaml" ]; then
    value=$(yaml_field "context_compression" ".comet/config.yaml")
    value="${value:-off}"
    source=".comet/config.yaml"
  fi

  case "$value" in
    off|beta)
      printf '%s\n' "$value"
      ;;
    *)
      red "ERROR: Invalid context_compression from ${source}: '$value'" >&2
      red "Valid values: off, beta" >&2
      exit 1
      ;;
  esac
}

project_auto_transition_default() {
  local value="true"
  local source="default"
  if [ -n "${COMET_AUTO_TRANSITION:-}" ]; then
    value="$COMET_AUTO_TRANSITION"
    source="COMET_AUTO_TRANSITION"
  elif [ -f ".comet/config.yaml" ]; then
    local raw
    raw=$(yaml_field "auto_transition" ".comet/config.yaml" 2>/dev/null || true)
    if [ -n "$raw" ]; then
      value="$raw"
      source=".comet/config.yaml"
    fi
  fi

  case "$value" in
    true|false)
      printf '%s\n' "$value"
      ;;
    *)
      red "ERROR: Invalid auto_transition from ${source}: '$value'" >&2
      red "Valid values: true, false" >&2
      exit 1
      ;;
  esac
}

project_review_mode_default() {
  local value="null"
  local source="default"
  if [ -n "${COMET_REVIEW_MODE:-}" ]; then
    value="$COMET_REVIEW_MODE"
    source="COMET_REVIEW_MODE"
  elif [ -f ".comet/config.yaml" ]; then
    local raw
    raw=$(yaml_field "review_mode" ".comet/config.yaml" 2>/dev/null || true)
    if [ -n "$raw" ]; then
      value="$raw"
      source=".comet/config.yaml"
    fi
  fi

  case "$value" in
    null|off|standard|thorough)
      printf '%s\n' "$value"
      ;;
    *)
      red "ERROR: Invalid review_mode from ${source}: '$value'" >&2
      red "Valid values: off, standard, thorough" >&2
      exit 1
      ;;
  esac
}

# --- Subcommands ---

cmd_init() {
  local change_name="$1"
  local workflow="$2"

  validate_change_name "$change_name"
  validate_enum "$workflow" "full" "hotfix" "tweak"

  local change_dir yaml_file
  change_dir=$(change_dir_for "$change_name")
  yaml_file=$(yaml_file_for "$change_name")

  # Check if .comet.yaml already exists
  if [ -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml already exists at $yaml_file"
    exit 1
  fi

  # Create change directory if it doesn't exist
  mkdir -p "$change_dir"

  # Set workflow-appropriate defaults
  local phase build_mode isolation verify_mode context_compression auto_transition review_mode
  phase="open"
  context_compression=$(project_context_compression)
  auto_transition="$(project_auto_transition_default)"

  case "$workflow" in
    full)
      build_mode="null"
      tdd_mode="null"
      review_mode="$(project_review_mode_default)"
      isolation="null"
      verify_mode="null"
      ;;
    hotfix|tweak)
      build_mode="direct"
      tdd_mode="direct"
      review_mode="off"
      isolation="branch"
      verify_mode="light"
      ;;
  esac

  # Write .comet.yaml
  # Record current HEAD as base_ref for scale assessment fallback
  local base_ref="null"
  if git rev-parse --verify HEAD >/dev/null 2>&1; then
    base_ref=$(git rev-parse HEAD 2>/dev/null || echo "null")
  fi

  cat > "$yaml_file" <<EOF
workflow: $workflow
phase: $phase
context_compression: $context_compression
build_mode: $build_mode
build_pause: null
subagent_dispatch: null
tdd_mode: $tdd_mode
review_mode: $review_mode
isolation: $isolation
verify_mode: $verify_mode
auto_transition: $auto_transition
base_ref: $base_ref
design_doc: null
plan: null
verify_result: pending
verification_report: null
branch_status: pending
created_at: $(date -u +%Y-%m-%d)
verified_at: null
archived: false
EOF

  green "Initialized: $yaml_file (workflow=$workflow)"
}

cmd_get() {
  local change_name="$1"
  local field="$2"

  validate_change_name "$change_name"

  local yaml_file
  yaml_file=$(yaml_file_for "$change_name")

  # Check if .comet.yaml exists
  if [ ! -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml not found at $yaml_file"
    exit 1
  fi

  # Read and output the field value
  local value
  value=$(yaml_field "$field" "$yaml_file")
  if [ "$field" = "auto_transition" ] && { [ -z "$value" ] || [ "$value" = "null" ]; }; then
    value="$(project_auto_transition_default)"
  fi
  echo "${value:-}"
}

cmd_set() {
  local change_name="$1"
  local field="$2"
  local value="$3"

  validate_change_name "$change_name"

  local yaml_file
  yaml_file=$(yaml_file_for "$change_name")

  # Check if .comet.yaml exists
  if [ ! -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml not found at $yaml_file"
    exit 1
  fi

  # Validate field name
  case "$field" in
    phase)
      # Direct phase writes bypass state-machine evidence checks (open artifacts,
      # design_doc, build decisions, verification evidence). Block them unless the
      # call originates from cmd_transition (dynamic-scope flag) or the operator
      # explicitly opts into the repair escape hatch.
      if [ "${_COMET_IN_TRANSITION:-}" != "1" ] && [ "${COMET_FORCE_PHASE:-}" != "1" ]; then
        red "ERROR: Setting 'phase' directly is not allowed; it bypasses state machine evidence checks." >&2
        red "  Use: comet-state.sh transition <change-name> <event>" >&2
        red "  Repair-only escape hatch: COMET_FORCE_PHASE=1 comet-state.sh set <change-name> phase <value>" >&2
        exit 1
      fi
      validate_enum "$value" "open" "design" "build" "verify" "archive"
      ;;
    workflow|context_compression|build_mode|build_pause|subagent_dispatch|tdd_mode|review_mode|isolation|verify_mode|auto_transition|verify_result|verification_report|branch_status|archived|design_doc|plan|verified_at|created_at|direct_override|build_command|verify_command|handoff_context|handoff_hash|base_ref)
      # Valid field
      ;;
    *)
      red "ERROR: Unknown field: '$field'" >&2
      red "Valid fields:" >&2
      red "  workflow, phase, context_compression, design_doc, plan, build_mode, build_pause, subagent_dispatch, tdd_mode, review_mode, isolation," >&2
      red "  verify_mode, auto_transition, verify_result, verification_report, branch_status," >&2
      red "  verified_at, created_at, archived, base_ref, direct_override," >&2
      red "  build_command, verify_command, handoff_context, handoff_hash" >&2
      exit 1
      ;;
  esac

  # Validate enum values
  case "$field" in
    workflow)
      validate_enum "$value" "full" "hotfix" "tweak"
      ;;
    context_compression)
      validate_enum "$value" "off" "beta"
      ;;
    phase)
      validate_enum "$value" "open" "design" "build" "verify" "archive"
      ;;
    build_mode)
      validate_enum "$value" "subagent-driven-development" "executing-plans" "direct"
      ;;
    build_pause)
      validate_enum "$value" "null" "plan-ready"
      ;;
    subagent_dispatch)
      validate_enum "$value" "null" "confirmed"
      ;;
    tdd_mode)
      validate_enum "$value" "tdd" "direct"
      ;;
    review_mode)
      validate_enum "$value" "off" "standard" "thorough"
      ;;
    isolation)
      validate_enum "$value" "branch" "worktree"
      ;;
    verify_mode)
      validate_enum "$value" "light" "full"
      ;;
    auto_transition)
      validate_enum "$value" "true" "false"
      ;;
    verify_result)
      validate_enum "$value" "pending" "pass" "fail"
      ;;
    branch_status)
      validate_enum "$value" "pending" "handled"
      ;;
    archived)
      validate_enum "$value" "true" "false"
      ;;
    direct_override)
      validate_enum "$value" "true" "false"
      ;;
    design_doc|plan|verification_report|handoff_context|handoff_hash)
      validate_path_field "$value" "$field"
      ;;
    verified_at|created_at|build_command|verify_command)
      # No validation for date fields or project command strings
      ;;
  esac

  # Write or update the field
  if grep -q "^${field}:" "$yaml_file"; then
    replace_yaml_field "$yaml_file" "$field" "$value"
  else
    # Field doesn't exist, append it
    echo "${field}: ${value}" >> "$yaml_file"
  fi

  green "[SET] ${field}=${value}"
}

require_phase() {
  local change_name="$1"
  local expected="$2"
  local actual
  actual=$(cmd_get "$change_name" "phase")
  if [ "$actual" != "$expected" ]; then
    red "ERROR: Cannot transition '$change_name': expected phase ${expected}, got ${actual}" >&2
    exit 1
  fi
}

require_open_artifacts() {
  local change_name="$1"
  local change_dir workflow f
  change_dir=$(change_dir_for "$change_name")
  workflow=$(cmd_get "$change_name" "workflow")
  for f in proposal.md tasks.md; do
    if [ ! -s "$change_dir/$f" ]; then
      red "ERROR: Cannot transition '$change_name': $f must exist and be non-empty before leaving open" >&2
      exit 1
    fi
  done
  if [ "$workflow" = "full" ] && [ ! -s "$change_dir/design.md" ]; then
    red "ERROR: Cannot transition '$change_name': design.md must exist and be non-empty before leaving open" >&2
    exit 1
  fi
}

require_design_evidence() {
  local change_name="$1"
  local design_doc
  design_doc=$(cmd_get "$change_name" "design_doc")
  if [ -z "$design_doc" ] || [ "$design_doc" = "null" ] || [ ! -s "$design_doc" ]; then
    red "ERROR: Cannot transition '$change_name': design_doc must point to an existing Design Doc before leaving design" >&2
    exit 1
  fi
}

require_verification_evidence() {
  local change_name="$1"
  local report branch_status
  report=$(cmd_get "$change_name" "verification_report")
  branch_status=$(cmd_get "$change_name" "branch_status")

  if [ -z "$report" ] || [ "$report" = "null" ] || [ ! -f "$report" ]; then
    red "ERROR: Cannot transition '$change_name': verification_report must point to an existing report file" >&2
    exit 1
  fi

  if [ "$branch_status" != "handled" ]; then
    red "ERROR: Cannot transition '$change_name': branch_status must be handled" >&2
    exit 1
  fi
}

require_build_decisions() {
  local change_name="$1"
  local workflow build_mode isolation direct_override subagent_dispatch tdd_mode review_mode
  workflow=$(cmd_get "$change_name" "workflow")
  build_mode=$(cmd_get "$change_name" "build_mode")
  isolation=$(cmd_get "$change_name" "isolation")
  direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true)
  subagent_dispatch=$(cmd_get "$change_name" "subagent_dispatch" 2>/dev/null || true)
  tdd_mode=$(cmd_get "$change_name" "tdd_mode" 2>/dev/null || true)
  review_mode=$(cmd_get "$change_name" "review_mode" 2>/dev/null || true)

  case "$isolation" in
    branch|worktree) ;;
    *)
      red "ERROR: Cannot transition '$change_name': isolation must be branch or worktree, got '${isolation:-null}'" >&2
      exit 1
      ;;
  esac

  case "$build_mode" in
    subagent-driven-development|executing-plans|direct) ;;
    *)
      red "ERROR: Cannot transition '$change_name': build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2
      exit 1
      ;;
  esac

  if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ] && [ "$direct_override" != "true" ]; then
    red "ERROR: Cannot transition '$change_name': build_mode=direct is only allowed for hotfix/tweak unless direct_override=true" >&2
    exit 1
  fi

  if [ "$build_mode" = "subagent-driven-development" ] && [ "$subagent_dispatch" != "confirmed" ]; then
    red "ERROR: Cannot transition '$change_name': subagent_dispatch must be confirmed before using build_mode=subagent-driven-development" >&2
    exit 1
  fi

  if [ "$workflow" = "full" ] && { [ "$tdd_mode" = "null" ] || [ -z "$tdd_mode" ]; }; then
    red "ERROR: Cannot transition '$change_name': tdd_mode must be selected before leaving build (full workflow)" >&2
    exit 1
  fi

  if [ "$workflow" = "full" ]; then
    case "$review_mode" in
      off|standard|thorough) ;;
      *)
        red "ERROR: Cannot transition '$change_name': review_mode must be selected before leaving build (full workflow); review_mode must be off, standard, or thorough, got '${review_mode:-null}'" >&2
        exit 1
        ;;
    esac
  fi
}

cmd_transition() {
  local change_name="$1"
  local event="$2"
  # Dynamic-scope flag: authorizes the internal cmd_set phase writes below while
  # still blocking direct `set <name> phase` from the CLI.
  local _COMET_IN_TRANSITION=1

  validate_change_name "$change_name"
  validate_enum "$event" "open-complete" "design-complete" "build-complete" "verify-pass" "verify-fail" "archive-reopen" "archived"

  case "$event" in
    open-complete)
      require_phase "$change_name" "open"
      require_open_artifacts "$change_name"
      local workflow
      workflow=$(cmd_get "$change_name" "workflow")
      if [ "$workflow" = "full" ]; then
        cmd_set "$change_name" phase design
      else
        cmd_set "$change_name" phase build
      fi
      ;;
    design-complete)
      require_phase "$change_name" "design"
      require_design_evidence "$change_name"
      cmd_set "$change_name" phase build
      ;;
    build-complete)
      require_phase "$change_name" "build"
      require_build_decisions "$change_name"
      local current_verify_result
      current_verify_result=$(cmd_get "$change_name" "verify_result")
      cmd_set "$change_name" phase verify
      cmd_set "$change_name" verify_result pending
      # Preserve verification evidence on re-verify (verify-fail → build → build-complete)
      # so the fix can reference the original failure report
      if [ "$current_verify_result" != "fail" ]; then
        cmd_set "$change_name" verification_report null
        cmd_set "$change_name" branch_status pending
      fi
      ;;
    verify-pass)
      require_phase "$change_name" "verify"
      require_verification_evidence "$change_name"
      cmd_set "$change_name" verify_result pass
      cmd_set "$change_name" phase archive
      cmd_set "$change_name" verified_at "$(date -u +%Y-%m-%d)"
      ;;
    verify-fail)
      require_phase "$change_name" "verify"
      cmd_set "$change_name" verify_result fail
      cmd_set "$change_name" phase build
      # Preserve branch_status so re-verify doesn't require re-handling branches
      ;;
    archive-reopen)
      require_phase "$change_name" "archive"
      local archived
      archived=$(cmd_get "$change_name" "archived")
      if [ "$archived" = "true" ]; then
        red "ERROR: Cannot transition '$change_name': already archived" >&2
        exit 1
      fi
      cmd_set "$change_name" verify_result pending
      cmd_set "$change_name" phase verify
      cmd_set "$change_name" verified_at null
      ;;
    archived)
      require_phase "$change_name" "archive"
      local archived_verify_result
      archived_verify_result=$(cmd_get "$change_name" "verify_result")
      if [ "$archived_verify_result" != "pass" ]; then
        red "ERROR: Cannot transition '$change_name': verify_result must be pass before archiving" >&2
        exit 1
      fi
      cmd_set "$change_name" archived true
      ;;
  esac

  green "[TRANSITION] ${event}"
}

# --- Check helpers for entry verification ---

CHECK_BLOCK=0

check_pass() {
  local msg="$1"
  echo "  $(green "[PASS]") $msg"
}

check_fail() {
  local msg="$1"
  echo "  $(red "[FAIL]") $msg"
  CHECK_BLOCK=1
}

check_nonempty() {
  local desc="$1"
  local path="$2"
  if file_nonempty "$path"; then
    check_pass "$desc non-empty"
  else
    check_fail "$desc missing or empty"
  fi
}

check_yaml_is() {
  local field="$1"
  local expected="$2"
  local change_name="$3"
  local actual
  actual=$(cmd_get "$change_name" "$field")
  if [ "$actual" = "$expected" ]; then
    check_pass "${field}=${actual} (expected: ${expected})"
  else
    check_fail "${field}=${actual} (expected: ${expected})"
  fi
}

check_yaml_empty() {
  local field="$1"
  local change_name="$2"
  local value
  value=$(cmd_get "$change_name" "$field")
  if [ -z "$value" ] || [ "$value" = "null" ]; then
    check_pass "${field} is empty/null"
  else
    check_fail "${field}=${value} (expected: empty/null)"
  fi
}

check_file_not_exists() {
  local desc="$1"
  local path="$2"
  if [ ! -f "$path" ]; then
    check_pass "$desc does not exist"
  else
    check_fail "$desc exists (should not exist)"
  fi
}

cmd_check() {
  local change_name="$1"
  local phase="$2"

  validate_change_name "$change_name"
  validate_enum "$phase" "open" "design" "build" "verify" "archive"

  local change_dir="openspec/changes/$change_name"
  local yaml_file="$change_dir/.comet.yaml"
  local proposal_file="$change_dir/proposal.md"
  local design_file="$change_dir/design.md"
  local tasks_file="$change_dir/tasks.md"

  echo "=== Entry Check: comet-${phase} ==="

  # .comet.yaml must exist for all phases (state machine core)
  if [ ! -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml not found at $yaml_file"
    exit 1
  fi

  # Phase-specific checks
  case "$phase" in
    open)
      check_pass ".comet.yaml exists"
      check_yaml_is "phase" "open" "$change_name"
      ;;
    design)
      check_pass ".comet.yaml exists"
      check_yaml_is "phase" "design" "$change_name"
      check_yaml_is "workflow" "full" "$change_name"
      check_yaml_empty "design_doc" "$change_name"
      check_nonempty "proposal.md" "$proposal_file"
      check_nonempty "design.md" "$design_file"
      check_nonempty "tasks.md" "$tasks_file"
      ;;
    build)
      check_pass ".comet.yaml exists"
      check_yaml_is "phase" "build" "$change_name"
      # design_doc required for full workflow only
      local workflow
      workflow=$(cmd_get "$change_name" "workflow")
      if [ "$workflow" = "full" ]; then
        local design_doc
        design_doc=$(cmd_get "$change_name" "design_doc")
        if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
          check_pass "design_doc=${design_doc} (file exists)"
        else
          check_fail "design_doc=${design_doc} (expected: non-null and file exists)"
        fi
      else
        check_pass "workflow=${workflow} (design_doc not required)"
      fi
      check_nonempty "proposal.md" "$proposal_file"
      check_nonempty "tasks.md" "$tasks_file"
      ;;
    verify)
      check_pass ".comet.yaml exists"
      check_yaml_is "phase" "verify" "$change_name"
      # Check verify_result is pending or null
      local verify_result
      verify_result=$(cmd_get "$change_name" "verify_result")
      if [ "$verify_result" = "pending" ] || [ -z "$verify_result" ] || [ "$verify_result" = "null" ]; then
        check_pass "verify_result=${verify_result} (expected: pending or null)"
      else
        check_fail "verify_result=${verify_result} (expected: pending or null)"
      fi
      ;;
    archive)
      check_pass ".comet.yaml exists"
      check_yaml_is "phase" "archive" "$change_name"
      check_yaml_is "verify_result" "pass" "$change_name"
      # Check archived is NOT true
      local archived
      archived=$(cmd_get "$change_name" "archived")
      if [ "$archived" != "true" ]; then
        check_pass "archived=${archived} (expected: not true)"
      else
        check_fail "archived=${archived} (expected: not true)"
      fi
      ;;
    *)
      red "ERROR: Unknown phase for check: $phase"
      exit 1
      ;;
  esac

  echo ""
  if [ "$CHECK_BLOCK" -eq 1 ]; then
    red "BLOCKED — fix failing checks before proceeding"
    exit 1
  else
    green "ALL CHECKS PASSED — ready to proceed"
    exit 0
  fi
}

# --- Recovery context for compaction resume ---

field_status() {
  # Args: field_name value [file_path]
  # Prints: "field_name: DONE (value)" or "field_name: PENDING"
  local field="$1"
  local value="$2"
  local file_path="${3:-}"

  if [ -z "$value" ] || [ "$value" = "null" ]; then
    echo "  - ${field}: PENDING"
  elif [ -n "$file_path" ] && [ ! -f "$file_path" ]; then
    echo "  - ${field}: BROKEN (path ${value} does not exist)"
  else
    echo "  - ${field}: DONE (${value})"
  fi
}

cmd_recover() {
  local change_name="$1"

  validate_change_name "$change_name"

  local change_dir="openspec/changes/$change_name"
  local yaml_file="$change_dir/.comet.yaml"

  if [ ! -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml not found at $yaml_file"
    exit 1
  fi

  local phase workflow
  phase=$(cmd_get "$change_name" "phase")
  workflow=$(cmd_get "$change_name" "workflow")

  echo "=== Recovery Context: ${change_name} ==="
  echo "Phase: ${phase}"
  echo "Workflow: ${workflow}"
  echo ""

  # Read all relevant fields
  local design_doc plan verify_result verify_mode verification_report
  local branch_status handoff_context handoff_hash isolation build_mode build_pause subagent_dispatch tdd_mode review_mode direct_override
  design_doc=$(cmd_get "$change_name" "design_doc")
  plan=$(cmd_get "$change_name" "plan")
  verify_result=$(cmd_get "$change_name" "verify_result")
  verify_mode=$(cmd_get "$change_name" "verify_mode")
  verification_report=$(cmd_get "$change_name" "verification_report")
  branch_status=$(cmd_get "$change_name" "branch_status")
  handoff_context=$(cmd_get "$change_name" "handoff_context")
  handoff_hash=$(cmd_get "$change_name" "handoff_hash")
  isolation=$(cmd_get "$change_name" "isolation")
  build_mode=$(cmd_get "$change_name" "build_mode")
  build_pause=$(cmd_get "$change_name" "build_pause" 2>/dev/null || true)
  subagent_dispatch=$(cmd_get "$change_name" "subagent_dispatch" 2>/dev/null || true)
  tdd_mode=$(cmd_get "$change_name" "tdd_mode" 2>/dev/null || true)
  review_mode=$(cmd_get "$change_name" "review_mode" 2>/dev/null || true)
  direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true)

  echo "State fields:"

  # Phase-specific field reporting
  case "$phase" in
    open)
      echo "  Artifacts:"
      local artifacts_done=0
      for f in proposal.md design.md tasks.md; do
        if file_nonempty "$change_dir/$f"; then
          echo "  - ${f}: DONE"
          artifacts_done=$((artifacts_done + 1))
        else
          echo "  - ${f}: PENDING"
        fi
      done
      echo ""
      if [ "$artifacts_done" -eq 3 ]; then
        echo "Recovery action: All artifacts complete. Run /comet-open user confirmation, then guard to transition."
      elif [ "$artifacts_done" -eq 0 ]; then
        echo "Recovery action: No artifacts created yet. Start from /comet-open Step 1 (explore and clarify)."
      else
        echo "Recovery action: Some artifacts incomplete. Resume /comet-open from the first missing artifact."
      fi
      ;;
    design)
      echo "  Artifacts:"
      for f in proposal.md design.md tasks.md; do
        if file_nonempty "$change_dir/$f"; then
          echo "  - ${f}: DONE"
        else
          echo "  - ${f}: MISSING (unexpected in design phase)"
        fi
      done
      echo ""
      echo "  Design progress:"
      field_status "handoff_context" "$handoff_context" "$handoff_context"
      field_status "handoff_hash" "$handoff_hash"
      field_status "design_doc" "$design_doc" "$design_doc"
      echo ""
      if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
        echo "Recovery action: Design Doc already created and linked. Run guard to transition to build."
      elif [ -n "$handoff_context" ] && [ "$handoff_context" != "null" ] && [ -f "$handoff_context" ]; then
        echo "Recovery action: Handoff generated but Design Doc not yet created. Resume from brainstorming confirmation (Step 1c)."
      else
        echo "Recovery action: No handoff generated yet. Start from Step 1a (generate handoff package)."
      fi
      ;;
    build)
      echo "  Build decisions:"
      field_status "isolation" "$isolation"
      field_status "build_mode" "$build_mode"
      field_status "build_pause" "$build_pause"
      field_status "tdd_mode" "$tdd_mode"
      field_status "review_mode" "$review_mode"
      if [ "$build_mode" = "subagent-driven-development" ] || { [ -n "$subagent_dispatch" ] && [ "$subagent_dispatch" != "null" ]; }; then
        field_status "subagent_dispatch" "$subagent_dispatch"
      fi
      if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ]; then
        field_status "direct_override" "$direct_override"
      fi
      echo ""
      echo "  Plan:"
      field_status "plan" "$plan" "$plan"
      echo ""
      # Count completed vs pending tasks
      local tasks_file="$change_dir/tasks.md"
      local total=0 done=0 pending=0
      local plan_total=0 plan_done=0 plan_pending=0
      if [ -f "$tasks_file" ]; then
        total=$(grep -c '^[[:space:]]*- \[' "$tasks_file" 2>/dev/null || true)
        done=$(grep -c '^[[:space:]]*- \[x\]' "$tasks_file" 2>/dev/null || true)
        total="${total:-0}"
        done="${done:-0}"
        pending=$((total - done))
        echo "  Tasks: ${done}/${total} done, ${pending} pending"
      else
        echo "  Tasks: tasks.md MISSING"
      fi
      if [ -n "$plan" ] && [ "$plan" != "null" ] && [ -f "$plan" ]; then
        plan_total=$(grep -c '^[[:space:]]*- \[' "$plan" 2>/dev/null || true)
        plan_done=$(grep -c '^[[:space:]]*- \[x\]' "$plan" 2>/dev/null || true)
        plan_total="${plan_total:-0}"
        plan_done="${plan_done:-0}"
        plan_pending=$((plan_total - plan_done))
        if [ "$plan_total" -gt 0 ]; then
          echo "  Plan tasks: ${plan_done}/${plan_total} done, ${plan_pending} pending"
        fi
      fi
      echo ""
      if [ "$build_pause" = "plan-ready" ] && [ -n "$plan" ] && [ "$plan" != "null" ] && [ -f "$plan" ] && { [ "$isolation" = "null" ] || [ -z "$isolation" ] || [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; }; then
        echo "Recovery action: Plan-ready pause detected. Ask the user whether to continue, then choose isolation and build mode without regenerating the plan."
      elif [ "$build_pause" = "plan-ready" ] && { [ -z "$plan" ] || [ "$plan" = "null" ] || [ ! -f "$plan" ]; }; then
        echo "Recovery action: Plan-ready pause is recorded, but the plan file is missing. Restore the plan file or rerun writing-plans before choosing execution."
      elif [ "$build_pause" = "plan-ready" ]; then
        if [ "$build_mode" = "subagent-driven-development" ] && { [ "$pending" -gt 0 ] || [ "$plan_pending" -gt 0 ]; }; then
          if [ "$subagent_dispatch" = "confirmed" ]; then
            echo "Recovery action: Plan-ready pause is stale because build decisions are already selected. Clear build_pause to null, then inspect the first unchecked task (OpenSpec or plan additions) against recent git history/diff. If implemented, check it off; otherwise dispatch a real background subagent. Do not execute the pending task directly in the main window."
          else
            echo "Recovery action: Plan-ready pause is stale and subagent dispatch is not confirmed. Confirm a real background subagent/Task/multi-agent dispatcher and set subagent_dispatch to confirmed, or set build_mode to executing-plans before continuing."
          fi
        elif [ "$pending" -gt 0 ] || [ "$plan_pending" -gt 0 ]; then
          echo "Recovery action: Plan-ready pause is stale because build decisions are already selected. Clear build_pause to null, then continue from the first unchecked task."
        else
          echo "Recovery action: Plan-ready pause is stale and all tasks are done. Clear build_pause to null, then run guard to transition to verify."
        fi
      elif [ "$isolation" = "null" ] || [ -z "$isolation" ]; then
        echo "Recovery action: Isolation not selected. Use the current platform's user confirmation mechanism to ask user for branch/worktree choice."
      elif [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; then
        echo "Recovery action: Build mode not selected. Use the current platform's user confirmation mechanism to ask user for execution method."
      elif [ -z "$tdd_mode" ] || [ "$tdd_mode" = "null" ]; then
        echo "Recovery action: TDD mode not selected. Use the current platform's user confirmation mechanism to ask user for tdd or direct."
      elif [ ! -f "$tasks_file" ]; then
        echo "Recovery action: tasks.md missing. Verify change directory integrity."
      elif [ "$pending" -gt 0 ]; then
        if [ "$build_mode" = "subagent-driven-development" ]; then
          if [ "$subagent_dispatch" = "confirmed" ]; then
            echo "Recovery action: Read tasks.md and the Superpowers plan (which may include additions beyond OpenSpec), then inspect the first unchecked task against recent git history/diff. If implemented, check it off; otherwise dispatch a real background subagent. Do not execute the pending task directly in the main window."
          else
            echo "Recovery action: Subagent dispatch is not confirmed. Confirm a real background subagent/Task/multi-agent dispatcher and set subagent_dispatch to confirmed, or set build_mode to executing-plans before continuing."
          fi
        else
          echo "Recovery action: Read tasks.md and continue from first unchecked task."
        fi
      elif [ "$plan_pending" -gt 0 ]; then
        if [ "$build_mode" = "subagent-driven-development" ]; then
          if [ "$subagent_dispatch" = "confirmed" ]; then
            echo "Recovery action: Read the Superpowers plan, then inspect the first unchecked Superpowers plan task against recent git history/diff. If implemented, check it off; otherwise dispatch a real background subagent. Do not execute the pending task directly in the main window."
          else
            echo "Recovery action: Subagent dispatch is not confirmed. Confirm a real background subagent/Task/multi-agent dispatcher and set subagent_dispatch to confirmed, or set build_mode to executing-plans before continuing."
          fi
        else
          echo "Recovery action: Read the Superpowers plan and continue from the first unchecked plan task."
        fi
      else
        echo "Recovery action: All tasks done. Run guard to transition to verify."
      fi
      ;;
    verify)
      echo "  Verification:"
      field_status "verify_result" "$verify_result"
      field_status "verify_mode" "$verify_mode"
      field_status "verification_report" "$verification_report" "$verification_report"
      field_status "branch_status" "$branch_status"
      echo ""
      if [ "$verify_result" = "pass" ] && [ "$branch_status" = "handled" ]; then
        echo "Recovery action: Verification complete. Run guard to transition to archive."
      elif [ "$verify_result" = "pass" ]; then
        echo "Recovery action: Verification passed but branch not yet handled. Complete branch handling and set branch_status to handled."
      elif [ "$verify_result" = "fail" ]; then
        echo "Recovery action: Verification failed and rolled back to build. Resume from /comet-build."
      else
        echo "Recovery action: Verification not yet started or in progress. Run scale assessment then verify."
      fi
      ;;
    archive)
      echo "  Archive:"
      field_status "verify_result" "$verify_result"
      field_status "archived" "$(cmd_get "$change_name" "archived")"
      echo ""
      echo "Recovery action: Run /comet-archive to complete archiving."
      ;;
    *)
      red "ERROR: Unknown phase: $phase"
      exit 1
      ;;
  esac

  echo ""
  echo "=== End Recovery Context ==="
}

cmd_scale() {
  local change_name="$1"

  validate_change_name "$change_name"

  local change_dir="openspec/changes/$change_name"
  local yaml_file="$change_dir/.comet.yaml"

  # Verify .comet.yaml exists
  if [ ! -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml not found at $yaml_file"
    exit 1
  fi

  # Read metrics
  # 1. Task count: count lines matching `- [` in tasks.md
  local tasks_file="$change_dir/tasks.md"
  local task_count=0
  if [ -f "$tasks_file" ]; then
    task_count=$(grep -c '^\- \[' "$tasks_file" 2>/dev/null || echo "0")
  fi

  # 2. Delta spec count: count files named spec.md under specs/*/spec.md
  local delta_spec_count=0
  if [ -d "$change_dir/specs" ]; then
    delta_spec_count=$(find "$change_dir/specs" -name "spec.md" -type f 2>/dev/null | wc -l | tr -d ' ')
  fi

  # 3. Changed files: prefer plan base-ref, then .comet.yaml base_ref, fall back to worktree diff
  local changed_files=0
  if git rev-parse --git-dir > /dev/null 2>&1; then
    local plan_file base_ref=""
    plan_file=$(cmd_get "$change_name" "plan" 2>/dev/null || true)
    if [ -n "$plan_file" ] && [ "$plan_file" != "null" ] && [ -f "$plan_file" ]; then
      base_ref=$(grep '^base-ref:' "$plan_file" 2>/dev/null | head -1 | sed 's/^base-ref: *//' || true)
    fi
    # Fallback to base_ref stored in .comet.yaml (set during init)
    if [ -z "$base_ref" ] || [ "$base_ref" = "null" ]; then
      base_ref=$(cmd_get "$change_name" "base_ref" 2>/dev/null || true)
    fi

    if [ -n "${base_ref:-}" ] && [ "$base_ref" != "null" ] && git rev-parse --verify "$base_ref" >/dev/null 2>&1; then
      changed_files=$(git diff --name-only "$base_ref"...HEAD 2>/dev/null | wc -l | tr -d ' ')
    else
      changed_files=$(git diff --name-only HEAD 2>/dev/null | wc -l | tr -d ' ')
    fi
  fi

  # Decision rules
  local result="light"
  if [ "$task_count" -gt 3 ] || [ "$delta_spec_count" -gt 1 ] || [ "$changed_files" -gt 4 ]; then
    result="full"
  fi

  # Output assessment to stderr
  echo "=== Scale Assessment: $change_name ===" >&2
  echo "  Tasks: $task_count (threshold: 3)" >&2
  echo "  Delta specs: $delta_spec_count capabilities (threshold: 1)" >&2
  echo "  Changed files: $changed_files (threshold: 4)" >&2
  echo "  → Result: $result" >&2

  # Update verify_mode in .comet.yaml
  replace_yaml_field "$yaml_file" "verify_mode" "$result"

  green "[SCALE] verify_mode=$result"
}

cmd_task_checkoff() {
  local task_file="$1"
  local task_text="$2"

  validate_path_field "$task_file" "task file"

  if [ -z "$task_text" ]; then
    red "ERROR: Task text cannot be empty" >&2
    exit 1
  fi

  if [ ! -f "$task_file" ]; then
    red "ERROR: Task file not found: $task_file" >&2
    exit 1
  fi

  local counts
  counts=$(TASK_TEXT="$task_text" awk '
    BEGIN {
      task = ENVIRON["TASK_TEXT"]
    }
    {
      sub(/\r$/, "")
      if ($0 == "- [ ] " task || $0 == "- [x] " task || $0 == "- [X] " task) {
        total++
      }
      if ($0 == "- [x] " task || $0 == "- [X] " task) {
        checked++
      }
    }
    END {
      printf "%d %d\n", total + 0, checked + 0
    }
  ' "$task_file")

  local total="${counts%% *}"
  local checked="${counts##* }"

  if [ "$total" -ne 1 ]; then
    red "ERROR: task text must appear exactly once in $task_file (found $total): $task_text" >&2
    exit 1
  fi

  if [ "$checked" -ne 1 ]; then
    red "ERROR: task is not checked in $task_file: $task_text" >&2
    exit 1
  fi

  echo "TASK_CHECKOFF: PASS"
  echo "FILE: $task_file"
  echo "TASK: $task_text"
}

# Resolve the next workflow step after a guard --apply phase advance.
# Reads the (already advanced) phase, workflow, and auto_transition, then emits
# a deterministic next-step contract so skills don't hardcode the next skill name.
#
# Output contract (stdout):
#   NEXT: auto|manual|done
#   SKILL: <skill-name>      (omitted when NEXT=done)
#   HINT: <message>          (only when NEXT=manual)
cmd_next() {
  local change_name="$1"
  validate_change_name "$change_name"

  local change_dir="openspec/changes/$change_name"
  local yaml_file="$change_dir/.comet.yaml"
  if [ ! -f "$yaml_file" ]; then
    red "ERROR: .comet.yaml not found at $yaml_file" >&2
    exit 1
  fi

  local phase workflow auto_transition archived
  phase=$(cmd_get "$change_name" "phase" 2>/dev/null || true)
  workflow=$(cmd_get "$change_name" "workflow" 2>/dev/null || true)
  auto_transition=$(cmd_get "$change_name" "auto_transition" 2>/dev/null || true)
  archived=$(cmd_get "$change_name" "archived" 2>/dev/null || true)

  # Change-level auto_transition overrides project-level; fall back to project default
  if [ -z "$auto_transition" ] || [ "$auto_transition" = "null" ]; then
    auto_transition="$(project_auto_transition_default)"
  fi

  # Terminal state: archived change has no next step.
  if [ "$archived" = "true" ]; then
    echo "NEXT: done"
    return 0
  fi

  # Map the current (post-advance) phase to the skill that owns it.
  local skill=""
  case "$phase" in
    open)
      skill="comet-open"
      ;;
    design)
      skill="comet-design"
      ;;
    build)
      case "$workflow" in
        hotfix) skill="comet-hotfix" ;;
        tweak)  skill="comet-tweak" ;;
        *)      skill="comet-build" ;;
      esac
      ;;
    verify)
      skill="comet-verify"
      ;;
    archive)
      skill="comet-archive"
      ;;
    *)
      red "ERROR: Cannot resolve next step for '$change_name': unknown phase '${phase:-null}'" >&2
      exit 1
      ;;
  esac

  # auto_transition=false pauses the next skill invocation only; phase is already advanced.
  if [ "$auto_transition" = "false" ]; then
    echo "NEXT: manual"
    echo "SKILL: $skill"
    echo "HINT: phase is '$phase'; run /$skill manually to continue"
  else
    echo "NEXT: auto"
    echo "SKILL: $skill"
  fi
}

# --- Main ---

SUBCOMMAND="${1:-}"
shift || true

case "$SUBCOMMAND" in
  init)
    if [ $# -lt 2 ]; then
      red "Usage: comet-state.sh init <change-name> <workflow>" >&2
      red "Workflows: full, hotfix, tweak" >&2
      exit 1
    fi
    cmd_init "$@"
    ;;
  get)
    if [ $# -lt 2 ]; then
      red "Usage: comet-state.sh get <change-name> <field>" >&2
      exit 1
    fi
    cmd_get "$@"
    ;;
  set)
    if [ $# -lt 3 ]; then
      red "Usage: comet-state.sh set <change-name> <field> <value>" >&2
      exit 1
    fi
    cmd_set "$@"
    ;;
  transition)
    if [ $# -lt 2 ]; then
      red "Usage: comet-state.sh transition <change-name> <event>" >&2
      red "Events: open-complete, design-complete, build-complete, verify-pass, verify-fail, archive-reopen, archived" >&2
      exit 1
    fi
    cmd_transition "$@"
    ;;
  check)
    if [ $# -lt 2 ]; then
      red "Usage: comet-state.sh check <change-name> <phase> [--recover]" >&2
      red "Phases: open, design, build, verify, archive" >&2
      exit 1
    fi
    # Detect --recover flag (3rd argument)
    if [ "${3:-}" = "--recover" ]; then
      cmd_recover "$1"
    else
      cmd_check "$@"
    fi
    ;;
  scale)
    if [ $# -lt 1 ]; then
      red "Usage: comet-state.sh scale <change-name>" >&2
      exit 1
    fi
    cmd_scale "$@"
    ;;
  task-checkoff)
    if [ $# -lt 2 ]; then
      red "Usage: comet-state.sh task-checkoff <file> <task-text>" >&2
      exit 1
    fi
    cmd_task_checkoff "$@"
    ;;
  next)
    if [ $# -lt 1 ]; then
      red "Usage: comet-state.sh next <change-name>" >&2
      exit 1
    fi
    cmd_next "$@"
    ;;
  *)
    red "Unknown subcommand: $SUBCOMMAND" >&2
    echo "" >&2
    echo "Usage: comet-state.sh <subcommand> <change-name> [args...]" >&2
    echo "" >&2
    echo "Subcommands:" >&2
    echo "  init <change-name> <workflow>  — Initialize .comet.yaml with workflow defaults" >&2
    echo "  get <change-name> <field>       — Read a field value from .comet.yaml" >&2
    echo "  set <change-name> <field> <val> — Update a field value in .comet.yaml" >&2
    echo "  transition <change-name> <event> — Apply a validated state transition" >&2
    echo "  check <change-name> <phase>    — Verify entry requirements for a phase" >&2
    echo "  scale <change-name>             — Assess and set verification mode based on metrics" >&2
    echo "  task-checkoff <file> <task-text> — Verify one unique task is checked" >&2
    echo "  next <change-name>              — Resolve the next workflow step (auto/manual/done)" >&2
    echo "" >&2
    echo "Workflows: full, hotfix, tweak" >&2
    echo "Phases for check: open, design, build, verify, archive" >&2
    exit 1
    ;;
esac
