#!/usr/bin/env bash
# @cmd Set up development environment (install git hooks)
# @alias s
setup() {
  echo "Setting up pre-commit config..."
  if [ ! -f .pre-commit-config.yaml ]; then
    cp .pre-commit-config.yaml.template .pre-commit-config.yaml
    echo "✓ Created .pre-commit-config.yaml from template"
  else
    echo "✓ .pre-commit-config.yaml already exists"
  fi

  echo "Installing git hooks..."
  prek install
  prek install -t pre-merge-commit
  echo "✅ Development environment ready"
}

# @cmd Prepare a release commit and annotated tag
# @arg version! Version to release (X.Y.Z)
# @alias r
release() {
  bin/release "${argc_version}"
}

# @cmd Create a release zip file
release-zip() {
  echo "Building Rust binary..."
  cargo build --release
  echo ""
  echo "Building release zip..."
  nix build
  local checksum
  checksum=$(shasum -a 256 result/yx.zip | cut -d' ' -f1)
  echo "✅ Release ready: result/yx.zip"
  echo "   SHA256: $checksum"
}

# @cmd Build a Linux release in Docker (for testing installer on macOS)
# @alias rl
release-linux() {
  echo "Building Linux release in Docker..."

  # Create a temporary directory for the build
  local build_dir
  build_dir=$(mktemp -d)
  trap 'rm -rf "$build_dir"' EXIT

  # Detect the Docker platform architecture
  local docker_arch
  docker_arch=$(docker run --rm busybox uname -m)
  local rust_target
  case "$docker_arch" in
    x86_64)
      rust_target="x86_64-unknown-linux-musl"
      ;;
    aarch64)
      rust_target="aarch64-unknown-linux-musl"
      ;;
    *)
      echo "Error: Unsupported Docker architecture: $docker_arch"
      return 1
      ;;
  esac

  # Create a Dockerfile for static build using musl
  cat > "$build_dir/Dockerfile" <<EOF
FROM rust:alpine
WORKDIR /workspace

# Install musl-dev and other build dependencies
RUN apk add --no-cache musl-dev pkgconfig openssl-dev openssl-libs-static zip

COPY . .

# Build static binary
RUN cargo build --release --target $rust_target

# Create release zip
RUN mkdir -p /output/release-bundle/bin /output/release-bundle/completions && \\
    cp target/$rust_target/release/yx /output/release-bundle/bin/yx && \\
    chmod +x /output/release-bundle/bin/yx && \\
    cp -r completions/* /output/release-bundle/completions/ && \\
    cd /output/release-bundle && \\
    zip -r /output/yx-linux.zip .
EOF

  # Copy source files
  rsync -a --exclude='.git' --exclude='result' --exclude='result-linux' \
    --exclude='.yaks' --exclude='node_modules' --exclude='.worktrees' \
    --exclude='target' \
    ./ "$build_dir/"

  # Build in Docker
  echo "Building static binary for $rust_target..."
  docker build -t yx-linux-builder "$build_dir" || {
    echo "Error: Docker build failed"
    return 1
  }

  # Extract the release zip
  mkdir -p result-linux
  # Create a temporary container to extract the file
  container_id=$(docker create yx-linux-builder)
  docker cp "$container_id:/output/yx-linux.zip" result-linux/yx-linux.zip
  docker rm "$container_id" >/dev/null

  # Clean up Docker image
  docker rmi yx-linux-builder >/dev/null 2>&1 || true

  echo "✓ Linux release built (static $rust_target): result-linux/yx-linux.zip"
}

# @cmd Run linting (shellcheck + Rust clippy + rustfmt)
# @alias l
lint() {
  local exit_code=0

  echo "Running shellcheck..."
  # Find and check all .sh files
  while IFS= read -r -d '' file; do
    if ! shellcheck -S warning "$file"; then
      exit_code=1
    fi
  done < <(git ls-files -z '*.sh')

  echo ""
  echo "Running Rust clippy..."
  if ! cargo clippy --features test-support -- -D warnings -W clippy::cognitive_complexity; then
    exit_code=1
  fi

  echo ""
  echo "Running Rust format check..."
  if ! cargo fmt --check; then
    exit_code=1
  fi

  echo ""
  if ! dead-steps; then
    exit_code=1
  fi

  return $exit_code
}

# @cmd Run code complexity analysis (rust-code-analysis)
# @alias cx
complexity() {
  # Thresholds — tighten these over time as code is refactored
  local max_cognitive=42
  local max_cyclomatic=34

  local metrics_output
  metrics_output=$(rust-code-analysis-cli --metrics -p src/ --output-format json 2>&1)
  local metrics_exit=$?
  if [ $metrics_exit -ne 0 ]; then
    echo "❌ Code metrics failed (exit code $metrics_exit)"
    echo "$metrics_output"
    return 1
  fi
  local file_count
  file_count=$(echo "$metrics_output" | wc -l | tr -d ' ')
  echo "Files analyzed: $file_count"
  echo ""
  echo "Complexity thresholds: cognitive ≤ $max_cognitive, cyclomatic ≤ $max_cyclomatic"
  echo ""

  # Find all functions exceeding thresholds
  local violations
  violations=$(echo "$metrics_output" \
    | jq -rs --argjson max_cog "$max_cognitive" --argjson max_cyc "$max_cyclomatic" '
      [.[] | .name as $file | .spaces[]? | recurse(.spaces[]?) | select(.kind == "function") |
        {name: .name, file: $file, cyclomatic: .metrics.cyclomatic.sum, cognitive: .metrics.cognitive.sum, sloc: .metrics.loc.sloc}
      ] | map(select(.cognitive > $max_cog or .cyclomatic > $max_cyc))
      | sort_by(-.cognitive) | .[] |
      "  ❌ \(.file)::\(.name): cognitive=\(.cognitive) cyclomatic=\(.cyclomatic) sloc=\(.sloc)"
    ')

  echo "Top 5 most complex functions (cyclomatic):"
  echo "$metrics_output" \
    | jq -rs '
      [.[] | .name as $file | .spaces[]? | recurse(.spaces[]?) | select(.kind == "function") |
        {name: .name, file: $file, cyclomatic: .metrics.cyclomatic.sum, cognitive: .metrics.cognitive.sum, sloc: .metrics.loc.sloc}
      ] | sort_by(-.cyclomatic) | .[0:5][] |
      "  \(.file)::\(.name): cyclomatic=\(.cyclomatic) cognitive=\(.cognitive) sloc=\(.sloc)"
    '
  echo ""

  if [ -n "$violations" ]; then
    echo "Functions exceeding thresholds:"
    echo "$violations"
    echo ""
    local count
    count=$(echo "$violations" | wc -l | tr -d ' ')
    echo "❌ $count function(s) exceed complexity thresholds"
    return 1
  fi

  echo "✓ All functions within complexity thresholds"
}

# @cmd Check for unused Cucumber step definitions
# @alias ds
dead-steps() {
  local steps_file="tests/features/steps.rs"
  local features_dir="features"

  echo "Checking for unused step definitions..."

  # Extract all step texts from feature files
  # (Given/When/Then/And/But/* lines, stripped to just the step text)
  local step_texts
  step_texts=$(grep -rhE '^\s*(Given|When|Then|And|But|\*)\s+' "$features_dir"/ \
    | sed -E 's/^\s*(Given|When|Then|And|But|\*)\s+//' \
    | sed 's/[[:space:]]*$//')

  # Extract unique step patterns from steps.rs and check each one.
  # The perl script outputs: line_number<TAB>attribute<TAB>regex_to_match
  # It deduplicates patterns (each appears twice for FullStack/InProcess).
  local found_unused=false
  local checked=0
  local unused=0

  while IFS=$'\t' read -r lineno display regex; do
    [ -z "$regex" ] && continue
    checked=$((checked + 1))

    if ! echo "$step_texts" | PATTERN="$regex" perl -ne \
      '$f=1 if /^$ENV{PATTERN}$/; END { exit($f ? 0 : 1) }'; then
      echo "  UNUSED $steps_file:$lineno"
      echo "    $display"
      found_unused=true
      unused=$((unused + 1))
    fi
  done < <(perl -ne '
    if (/^\s*#\[(given|when|then)\((expr|regex)\s*=/) {
      my $kind = $2;
      my ($pattern, $regex);
      if ($kind eq "expr") {
        ($pattern) = /expr\s*=\s*"([^"]*)"/;
        $regex = $pattern;
        $regex =~ s/\{string\}/"[^"]*"/g;
        $regex =~ s/\{int\}/[0-9]+/g;
        $regex =~ s/\{float\}/[0-9]+\\.?[0-9]*/g;
        $regex =~ s/\{word\}/\\S+/g;
        $regex =~ s/\{\}/.*?/g;
      } elsif ($kind eq "regex") {
        ($pattern) = /regex\s*=\s*r#"(.*)"#\)/;
        $regex = $pattern;
        $regex =~ s/^\^//;
        $regex =~ s/\$$//;
      }
      if (defined $pattern && !$seen{$pattern}++) {
        my ($display) = /^\s*(#\[.*\])/;
        print "$.\t$display\t$regex\n";
      }
    }
  ' "$steps_file")

  if [ "$found_unused" = true ]; then
    echo ""
    echo "Found $unused unused step definition(s) (of $checked checked)"
    return 1
  else
    echo "All $checked step definitions are matched by feature files"
    return 0
  fi
}

# @cmd Run tests (shellspec)
# @alias t
test() {
  # Build Linux release for installer test
  echo "Building Linux release for installer test..."
  release-linux || {
    echo "Failed to build Linux release"
    return 1
  }
  echo ""

  shellspec
  cargo test --features test-support
}

# @cmd Run mutation tests (unit + in-process cucumber)
# @alias m
mutate() {
  # Config in .cargo/mutants.toml sets features, test args, and exclusions
  # In-process cucumber is now the default, no need to set CUCUMBER_MODE
  YX_SKIP_GIT_CHECKS=1 cargo mutants "$@"
}

# @cmd Run mutation tests on changed files only (fast)
# @alias md
mutate-diff() {
  local diff_file
  diff_file=$(mktemp)
  trap 'rm -f "$diff_file"' EXIT
  git diff main...HEAD -- '*.rs' > "$diff_file"
  if [ ! -s "$diff_file" ]; then
    echo "No Rust changes vs main — nothing to mutate."
    return 0
  fi
  YX_SKIP_GIT_CHECKS=1 \
    cargo mutants --in-diff "$diff_file" "$@"
}

# @cmd Sync mutation test results to yaks
# @alias ms
mutate-sync() {
  # Ensure yx is on PATH (needed in CI where direnv isn't active)
  export PATH="$PWD/bin:$PWD/target/release:$PATH"

  local missed="mutants.out/missed.txt"
  if [ ! -f "$missed" ]; then
    echo "Error: $missed not found."
    echo "Run 'dev mutate' first to generate results."
    return 1
  fi

  local parent="fix missed mutants"

  # Ensure parent yak exists
  if ! yx ls --format plain | grep -Fqx "dx/mutation-tests/$parent"; then
    yx add "$parent" --under mutation-tests
  fi

  # Build list of expected yak names from missed.txt
  local -A missed_files
  while IFS= read -r line; do
    # Extract file path (everything before first :line:col)
    local file_path
    file_path=$(echo "$line" | sed 's/^\(src\/[^:]*\.rs\):.*/\1/')
    if [ -n "$file_path" ]; then
      missed_files["$file_path"]=1
    fi
  done < "$missed"

  # For each file with misses, create/update yak
  for file_path in "${!missed_files[@]}"; do
    # Build yak name: strip src/ and .rs, replace / with -
    local yak_name
    yak_name=$(echo "$file_path" \
      | sed 's|^src/||' \
      | sed 's|\.rs$||' \
      | sed 's|/|-|g')
    yak_name="$yak_name mutants"

    # Build context from all mutants for this file
    local context
    context=$(grep -F "${file_path}:" "$missed" | sort)

    # Check if yak exists and its state
    local full_path="dx/mutation-tests/$parent/$yak_name"
    local exists=false
    local state=""
    if yx ls --format plain | grep -Fqx "$full_path"; then
      exists=true
      state=$(yx field --show "$yak_name" state 2>/dev/null \
        | tail -1)
    fi

    if [ "$exists" = true ]; then
      # Update context (even for wip yaks)
      echo "$context" | yx context "$yak_name"
      # Reopen done yaks
      if [ "$state" = "done" ]; then
        echo "  ↻ Reopening $yak_name (new misses)"
        yx state "$yak_name" todo
      else
        echo "  ↻ Updated $yak_name ($state)"
      fi
    else
      echo "  + Creating $yak_name"
      yx add "$yak_name" --under "$parent" \
        --context "$context"
    fi
  done

  # Mark yaks done if their file has zero misses now
  local existing_yaks
  existing_yaks=$(yx ls --format plain \
    | grep "^dx/mutation-tests/$parent/" \
    | sed "s|^dx/mutation-tests/$parent/||")

  while IFS= read -r yak_name; do
    [ -z "$yak_name" ] && continue
    # Skip sub-children (only process direct children)
    echo "$yak_name" | grep -q '/' && continue

    # Reconstruct file path from yak name
    local file_path
    file_path="src/$(echo "$yak_name" \
      | sed 's| mutants$||' \
      | sed 's|-|/|g').rs"

    # Check if this file has any misses
    if [ -z "${missed_files[$file_path]+x}" ]; then
      local state
      state=$(yx field --show "$yak_name" state 2>/dev/null \
        | tail -1)
      if [ "$state" != "done" ] && [ "$state" != "wip" ]; then
        echo "  ✓ Marking $yak_name done (no more misses)"
        yx done "$yak_name"
      fi
    fi
  done <<< "$existing_yaks"

  echo ""
  echo "Sync complete. Run 'yx sync' to push changes."
}

# @cmd Run all checks (tests + lint + audit) and mark as verified
# @alias c
# @flag --force Force re-run even if nothing changed
check() {
  # Skip if nothing has changed since last check
  if [ -z "${argc_force:-}" ] && [ -z "${FORCE_CHECK:-}" ] && .githooks/verify-checks 2>/dev/null; then
    echo "✅ Already verified — nothing changed since last check"
    return 0
  fi

  echo "Building release binary..."
  if ! cargo build --release --quiet; then
    echo "❌ Build failed"
    return 1
  fi

  echo "Running acceptance tests..."
  # ShellSpec 0.28.1 has a reporter bug that causes exit code 102 even when
  # all tests pass. Work around by checking the summary line for failures.
  local shellspec_output
  shellspec_output=$(shellspec 2>&1)
  local shellspec_exit=$?
  echo "$shellspec_output"
  if echo "$shellspec_output" | grep -qE '[1-9][0-9]* failure'; then
    echo "❌ Acceptance tests failed"
    return 1
  fi
  if [ $shellspec_exit -ne 0 ] && ! echo "$shellspec_output" | grep -q '0 failures'; then
    echo "❌ Acceptance tests failed (exit code $shellspec_exit)"
    return 1
  fi

  echo "Running unit + in-process cucumber tests..."
  if ! cargo test --features test-support; then
    echo "❌ Unit / in-process cucumber tests failed"
    return 1
  fi

  echo "Running full-stack cucumber tests..."
  if ! CUCUMBER_MODE=fullstack cargo test --test cucumber --features test-support; then
    echo "❌ Full-stack cucumber tests failed"
    return 1
  fi

  echo ""
  echo "Running linter..."
  if ! lint; then
    echo "❌ Linting failed"
    return 1
  fi

  echo ""
  echo "Running code complexity analysis..."
  if ! complexity; then
    echo "❌ Code complexity analysis failed"
    return 1
  fi

  echo ""
  echo "Running security audit..."
  if command -v cargo-audit >/dev/null 2>&1; then
    if ! cargo audit; then
      echo "❌ Security audit failed"
      return 1
    fi
  else
    echo "⚠️  cargo-audit not installed (run: cargo install cargo-audit)"
    echo "   Skipping security audit check"
  fi

  echo ""
  echo "Running mutation tests on changed files..."
  if ! mutate-diff; then
    echo "❌ Mutation testing failed"
    return 1
  fi

  echo ""
  touch .last-checked
  echo "✅ All checks passed - changes verified"
}

# @cmd Run Ralph Wiggum loop (iterations with pause)
# @arg yak-name! Name of the yak to work on
# @option --max-iterations <NUM> Number of iterations to run (default: 3)
ralph() {
  local yak_name="${argc_yak_name}"
  local max_iterations="${argc_max_iterations:-3}"

  for ((i=1; i<=max_iterations; i++)); do
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo " Iteration $i/$max_iterations"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    echo "Checking if ready to proceed..."

    local schema
    schema=$(cat <<'SCHEMA'
{
  "type": "object",
  "properties": {
    "needs_human_help": {"type": "boolean"},
    "reason": {"type": "string"},
    "next_step": {"type": "string"}
  },
  "required": ["needs_human_help", "reason", "next_step"]
}
SCHEMA
)

    local prompt
    prompt=$(cat <<PROMPT
Review the current state of the \`${yak_name}\` yak.

Look at:
- The yak context (yx context --show ${yak_name})
- The context of any relevant sub-yaks
- Current git status and recent commits

Determine if the yak needs human help to proceed. This includes situations where:
- The next step is unclear or ambiguous
- There are multiple viable approaches and a decision is needed
- There's a blocker that requires human intervention
- The acceptance criteria are unclear

Always provide the next_step:
- If ready to proceed: describe what you plan to do next
- If human help is needed: describe what decision or input is needed

Respond with whether human help is needed, the reason, and the next step.
PROMPT
)

    local result
    result=$(echo "$prompt" | claude -p --output-format json --json-schema "$schema")

    local needs_help
    needs_help=$(echo "$result" | jq -r '.structured_output.needs_human_help')

    echo "Next step: $(echo "$result" | jq -r '.structured_output.next_step')"
    if [ "$needs_help" = "true" ]; then
      echo "⚠️  Human help needed!"
      echo "$result" | jq -r '.structured_output.reason'
      echo ""
      break
    else
      echo "✓ Ready to proceed..."
    fi

    echo ""
    cat <<EOF | claude -p --allowedTools "Read,Write,Edit,Bash"
Work on the \`${yak_name}\` yak.

Your goal for THIS TURN: Complete ONE single (sub-)yak and then STOP.

Steps:
1. Check the context of \`${yak_name}\` to identify the next unblocked (sub-)yak to work on
2. Mark that (sub-)yak as "wip" using: yx state <name> wip
3. Work on ONLY that one (sub-)yak until complete
4. When done, mark it as "done" using: yx done <name>
5. Update the context on the relevant yak(s) with anything you've learned and what needs to be done next
6. STOP - do not continue to another (sub-)yak

Key skills:
- yak-worktree-workflow
- test-driven-development
- verification-before-completion

If the acceptance criteria are clear, you do not need to ask me for confirmation.

IMPORTANT:
- Print regular updates to ./thoughts about what you're about to do, what you've achieved, and anything you're puzzled about so I can watch what's going on.
- When you finish the (sub-)yak, update the context on the relevant yak(s) with anything you've learned and what needs to be done next.

Remember: ONE (sub-)yak per turn, then STOP!

EOF
  done
}

# @cmd Record README demo GIF using VHS
# @alias d
demo() {
  local tape="demo.tape"
  if [ ! -f "$tape" ]; then
    echo "Error: $tape not found"
    return 1
  fi

  local demo_dir
  demo_dir=$(mktemp -d)
  trap 'rm -rf "$demo_dir"' EXIT

  # Set up a shared bare origin with two clones (alice and bob)
  git init --bare "$demo_dir/origin.git" --quiet
  git clone --quiet "$demo_dir/origin.git" "$demo_dir/alice"
  git -C "$demo_dir/alice" -c user.name="Alice" -c user.email="alice@example.com" \
    commit --allow-empty -m "init" --quiet --no-verify
  git -C "$demo_dir/alice" push --quiet
  git clone --quiet "$demo_dir/origin.git" "$demo_dir/bob"
  git -C "$demo_dir/alice" config user.name "Alice"
  git -C "$demo_dir/alice" config user.email "alice@example.com"
  git -C "$demo_dir/bob" config user.name "Bob"
  git -C "$demo_dir/bob" config user.email "bob@example.com"

  local yx_path="$PWD/target/release"
  local tape_path="$PWD/$tape"

  echo "Recording demo..."
  (cd "$demo_dir" && PATH="$yx_path:$PATH" vhs "$tape_path")
  cp "$demo_dir/demo.gif" demo.gif
  echo "✅ Demo recorded: demo.gif"
}

# @cmd Fix the next missed mutant yak
# @alias fm
fix-mutant() {
  local parent="dx/mutation-tests/fix missed mutants"

  # Find the first todo mutation yak
  local yak_name
  yak_name=$(
    yx ls --format plain \
      | grep "^${parent}/" \
      | sed "s|^${parent}/||" \
      | while read -r name; do
          state=$(yx field --show "$name" state 2>/dev/null \
            | tail -1)
          if [ "$state" = "todo" ]; then
            echo "$name"
            break
          fi
        done
  )

  if [ -z "$yak_name" ]; then
    echo "No todo mutation yaks found."
    return 0
  fi

  echo "Picking up: $yak_name"
  yx state "$yak_name" wip

  # Read context (missed mutant descriptions)
  local context
  context=$(yx context --show "$yak_name")

  # Reconstruct source file path from yak name
  local source_file
  source_file="src/$(echo "$yak_name" \
    | sed 's| mutants$||' \
    | sed 's|-|/|g').rs"

  echo "Source file: $source_file"

  # Create worktree
  local branch_name="fix-mutant/${yak_name// /-}"
  local worktree_path=".worktrees/${yak_name// /-}"

  # Clean up stale branch/worktree from previous attempts
  if git worktree list | grep -q "$worktree_path"; then
    echo "Removing stale worktree: $worktree_path"
    git worktree remove --force "$worktree_path"
  fi
  if git branch --list "$branch_name" | grep -q .; then
    echo "Removing stale branch: $branch_name"
    git branch -D "$branch_name"
  fi

  echo "Creating worktree: $worktree_path"
  git worktree add "$worktree_path" -b "$branch_name"

  local prompt
  prompt=$(cat <<EOF
You are fixing missed mutants in this project.

## Yak: $yak_name

## Source file: $source_file

## Missed mutants:
$context

## Instructions

1. Read the source file and understand the code
2. Use TDD to write tests that catch each missed mutant:
   - Write a failing test that would pass if the mutant were applied
   - Verify it passes against the real code
3. Run \`dev mutate-diff\` to verify mutants are now caught
4. Run \`dev check\` to verify nothing is broken
5. Commit with a descriptive message
6. Mark the yak done: \`yx done "$yak_name"\`
EOF
  )

  echo "Launching Claude..."
  (cd "$worktree_path" && claude -p --output-format stream-json --verbose --dangerously-skip-permissions "$prompt") \
    | jq -r --unbuffered '
      if .type == "assistant" then
        .message.content[]? |
        if .type == "text" then
          "\n\(.text)"
        elif .type == "tool_use" then
          "\n→ \(.name): \(.input.description // .input.command // .input.pattern // .input.file_path // "")"
        else empty end
      elif .type == "user" then
        .message.content[]? |
        if .type == "tool_result" then
          if .is_error then "✗ \(.content | split("\n")[0])"
          else "✓ \(.content | split("\n")[0])"
          end
        else empty end
      else empty end
    '

  local exit_code=$?
  if [ $exit_code -ne 0 ]; then
    echo "Claude exited with code $exit_code"
    echo "Worktree left at: $worktree_path"
    return $exit_code
  fi

  # Merge to main and clean up
  echo "Merging $branch_name to main..."
  if git merge "$branch_name" --no-edit; then
    git worktree remove "$worktree_path"
    git branch -d "$branch_name"
    echo "Done! Yak '$yak_name' merged and cleaned up."
  else
    echo "Merge failed — resolve conflicts in $worktree_path"
    return 1
  fi
}

# @cmd Triage missed mutant yaks (decide: write test vs restructure code)
# @alias tm
triage-mutants() {
  local parent="dx/mutation-tests/fix missed mutants"

  # Collect all todo mutation yaks
  local yaks=()
  while IFS= read -r name; do
    [ -z "$name" ] && continue
    # Skip sub-children (only process direct children)
    echo "$name" | grep -q '/' && continue
    local state
    state=$(yx field --show "$name" state 2>/dev/null | tail -1)
    if [ "$state" = "todo" ]; then
      yaks+=("$name")
    fi
  done < <(
    yx ls --format plain \
      | grep "^${parent}/" \
      | sed "s|^${parent}/||"
  )

  if [ ${#yaks[@]} -eq 0 ]; then
    echo "No todo mutation yaks found."
    return 0
  fi

  echo "Found ${#yaks[@]} todo mutation yak(s) to triage:"

  # Build the yaks section of the prompt
  local yaks_section=""
  for yak_name in "${yaks[@]}"; do
    echo "  - $yak_name"

    local context
    context=$(yx context --show "$yak_name")

    local source_file
    source_file="src/$(echo "$yak_name" \
      | sed 's| mutants$||' \
      | sed 's|-|/|g').rs"

    yaks_section+="
### $yak_name
Source file: $source_file
Current context:
$context

"
  done

  local prompt
  prompt=$(cat <<EOF
You are triaging missed mutants for this project.

For each yak below, read the source file and review the missed mutants.
For each mutant, decide:

1. Is this behaviour we WANT from the system?
   → If YES: update the yak context with analysis explaining what the
     behaviour is, why it matters, and a suggested test approach.
   → If NO: update the yak context with restructuring instructions
     explaining what's wrong with the current code and how to change it
     so the unwanted behaviour is eliminated.

Use \`yx context "<yak-name>"\` (pipe content via stdin) to update each
yak's context.

## Yaks to triage:
$yaks_section
EOF
  )

  echo ""
  echo "Launching Claude for triage..."
  claude -p --output-format stream-json --dangerously-skip-permissions "$prompt" \
    | jq -r --unbuffered '
      if .type == "assistant" then
        .message.content[]? |
        if .type == "text" then
          "\n\(.text)"
        elif .type == "tool_use" then
          "\n→ \(.name): \(.input.description // .input.command // .input.pattern // .input.file_path // "")"
        else empty end
      elif .type == "user" then
        .message.content[]? |
        if .type == "tool_result" then
          if .is_error then "✗ \(.content | split("\n")[0])"
          else "✓ \(.content | split("\n")[0])"
          end
        else empty end
      else empty end
    '

  echo ""
  echo "Triage complete. Review results with:"
  for yak_name in "${yaks[@]}"; do
    echo "  yx context --show \"$yak_name\""
  done
}

# @cmd Start working on a yak (create worktree + mark wip)
# @arg yak-name! Name of the yak to start
# @alias st
start() {
  local yak_name="${argc_yak_name}"

  # Get yak ID from yx show
  local yak_json
  yak_json=$(yx show --format json "$yak_name" 2>&1) || {
    echo "❌ Failed to find yak: $yak_name"
    echo "$yak_json"
    return 1
  }

  local yak_id
  yak_id=$(echo "$yak_json" | jq -r '.id')
  if [ -z "$yak_id" ] || [ "$yak_id" = "null" ]; then
    echo "❌ Could not get yak ID for '$yak_name'"
    return 1
  fi

  local worktree_path=".worktrees/$yak_id"

  # Check if worktree already exists
  if [ -d "$worktree_path" ]; then
    echo "Worktree already exists: $worktree_path"
    yx start "$yak_name" 2>/dev/null || true
    echo "$worktree_path"
    return 0
  fi

  # Mark as wip
  yx start "$yak_name"

  # Create worktree
  mkdir -p .worktrees
  git worktree add "$worktree_path" -b "$yak_id" || {
    echo "❌ Failed to create worktree"
    return 1
  }

  # Record worktree and branch on the yak
  echo "$worktree_path" | yx field "$yak_name" worktree
  echo "$yak_id" | yx field "$yak_name" branch

  echo "✅ Worktree ready: $worktree_path (branch: $yak_id)"
}

# @cmd Merge a branch into main (check + rebase + fast-forward)
# @arg branch! Branch to merge
# @alias mg
merge() {
  local branch="${argc_branch}"

  # Must be on main
  local current
  current=$(git branch --show-current)
  if [ "$current" != "main" ]; then
    echo "❌ Must be on main (currently on $current)"
    return 1
  fi

  # Branch must exist
  if ! git rev-parse --verify "$branch" >/dev/null 2>&1; then
    echo "❌ Branch '$branch' does not exist"
    return 1
  fi

  # Find the worktree for this branch (if any)
  local worktree
  worktree=$(git worktree list --porcelain \
    | grep -B2 "branch refs/heads/$branch" \
    | grep "^worktree " | sed 's/^worktree //')

  # Run checks in the worktree if it exists, otherwise in a temp worktree.
  # NEVER switch the main repo away from main.
  if [ -n "$worktree" ]; then
    echo "Running checks in worktree: $worktree"
    if ! (cd "$worktree" && FORCE_CHECK=1 check); then
      echo ""
      echo "❌ dev check failed on $branch — fix before merging"
      return 1
    fi

    echo ""
    echo "Rebasing $branch onto main..."
    if ! (cd "$worktree" && git rebase main --no-verify); then
      echo "❌ Rebase failed — resolve conflicts in $worktree, then run again"
      return 1
    fi
  else
    # No worktree — create a temporary one for checks and rebase.
    # mktemp gives us a unique name; we remove the dir so git worktree
    # can create it (it insists the path not exist yet).
    local tmp_worktree
    tmp_worktree=$(mktemp -d "${TMPDIR:-/tmp}/yx-merge-XXXXXX")
    rmdir "$tmp_worktree"
    echo "Creating temporary worktree at $tmp_worktree..."
    git worktree add "$tmp_worktree" "$branch"

    echo "Running checks in temporary worktree..."
    if ! (cd "$tmp_worktree" && FORCE_CHECK=1 check); then
      echo ""
      echo "❌ dev check failed on $branch — fix before merging"
      git worktree remove "$tmp_worktree"
      return 1
    fi

    echo ""
    echo "Rebasing $branch onto main..."
    if ! (cd "$tmp_worktree" && git rebase main --no-verify); then
      echo "❌ Rebase failed — resolve conflicts, then run again"
      git worktree remove "$tmp_worktree"
      return 1
    fi

    git worktree remove "$tmp_worktree"
  fi

  # Fast-forward main (we never left main)
  if ! git merge --ff-only "$branch" --no-verify; then
    echo "❌ Fast-forward merge failed — is main's working tree clean?"
    return 1
  fi

  # Clean up worktree and branch
  if [ -n "$worktree" ]; then
    echo "Removing worktree: $worktree"
    git worktree remove "$worktree"
  fi
  git branch -d "$branch"

  echo ""
  echo "✅ Branch $branch merged to main"
}

eval "$(argc --argc-eval "$0" "$@")"
