#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'EOF'
Usage: ai/commands/workflow-state.sh [--ready-for-qa|--ready-to-complete]

Reports read-only workflow state for the current active chunk.
Canonical state names follow ai/standards/workflow-state.md.

Modes:
  --ready-for-qa       Return success only when the active chunk is ready for QA.
  --ready-to-complete  Return success only when the active chunk is ready to complete.
EOF
}

repo_root() {
  local script_dir
  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  if git -C "$script_dir" rev-parse --show-toplevel >/dev/null 2>&1; then
    git -C "$script_dir" rev-parse --show-toplevel
    return
  fi
  cd "$script_dir/../.." && pwd
}

section() {
  local file="$1"
  local heading="$2"
  awk -v heading="## ${heading}" '
    $0 == heading { in_section = 1; next }
    in_section && /^## / { exit }
    in_section { print }
  ' "$file" | sed -E '/^[[:space:]]*$/d'
}

metadata_value() {
  local file="$1"
  local key="$2"
  awk -v key="$key" '
    /^---[[:space:]]*$/ {
      fence++
      if (fence == 2) exit
      next
    }
    fence == 1 && index($0, key ":") == 1 {
      value = $0
      sub("^[^:]+:[[:space:]]*", "", value)
      print value
      exit
    }
  ' "$file"
}

pass_count() {
  local file="$1"
  local role="$2"
  awk -v role="$role" '
    $0 == "## Pass History" { in_history = 1; next }
    in_history && /^## / { exit }
    in_history && $0 ~ "^### " role " Pass [0-9]+" { count++ }
    END { print count + 0 }
  ' "$file"
}

latest_pass_entry() {
  local file="$1"
  local role_pattern="${2:-Developer|QA}"
  awk -v role_pattern="$role_pattern" '
    $0 == "## Pass History" { in_history = 1; next }
    in_history && /^## / { exit }
    in_history && $0 ~ "^### (" role_pattern ") Pass [0-9]+" {
      if (entry != "") latest = entry
      entry = $0 "\n"
      next
    }
    in_history && entry != "" { entry = entry $0 "\n" }
    END {
      if (entry != "") latest = entry
      gsub(/\n[[:space:]]*$/, "", latest)
      print latest
    }
  ' "$file"
}

latest_pass_heading() {
  local file="$1"
  latest_pass_entry "$file" | sed -n '1p'
}

latest_pass_line() {
  local file="$1"
  local role="$2"
  awk -v role="$role" '
    $0 == "## Pass History" { in_history = 1; next }
    in_history && /^## / { exit }
    in_history && $0 ~ "^### " role " Pass [0-9]+" { latest = NR }
    END { print latest + 0 }
  ' "$file"
}

qa_verdict() {
  local file="$1"
  local review
  review="$(section "$file" "QA Review")"
  if [[ -z "$review" ]]; then
    printf '%s\n' "none"
    return 0
  fi

  printf '%s\n' "$review" |
    awk '
      tolower($0) ~ /^[[:space:]-]*verdict[[:space:]]*:/ {
        value = $0
        sub(/^[^:]*:[[:space:]]*/, "", value)
        gsub(/`/, "", value)
        print toupper(value)
        found = 1
        exit
      }
      END { if (!found) print "present" }
    '
}

qa_blockers() {
  local file="$1"
  local review
  review="$(section "$file" "QA Review")"
  if [[ -z "$review" ]]; then
    printf '%s\n' "(no QA Review found)"
    return 0
  fi

  printf '%s\n' "$review" |
    awk '
      tolower($0) ~ /^[[:space:]-]*blockers[[:space:]]*:/ {
        value = $0
        sub(/^[^:]*:[[:space:]]*/, "", value)
        print value
        found = 1
        exit
      }
      END { if (!found) print "(no blockers field found)" }
    '
}

qa_retry_classification() {
  local file="$1"
  local review
  review="$(section "$file" "QA Review")"
  if [[ -z "$review" ]]; then
    echo "none"
    return 0
  fi

  printf '%s\n' "$review" |
    awk '
      BEGIN { found = 0 }
      tolower($0) ~ /^[[:space:]-]*(blocker classification|blocker type|retry safety)[[:space:]]*:/ {
        value = tolower($0)
        sub(/^[^:]*:[[:space:]]*/, "", value)
        if (value ~ /(scope_change|scope change|scope-change)/) {
          print "scope_change"
        } else if (value ~ /(retry_limit_reached|retry limit reached)/) {
          print "retry_limit_reached"
        } else if (value ~ /(requires_decision|requires decision|manual intervention|human decision|requirements ambiguity|ambiguous|unsafe)/) {
          print "requires_decision"
        } else if (value ~ /(fixable|retry-safe|retry safe)/) {
          print "fixable"
        } else {
          print "unclassified"
        }
        found = 1
        exit
      }
      END {
        if (!found) print "unclassified"
      }
    '
}

entry_has_field() {
  local entry="$1"
  local field="$2"
  printf '%s\n' "$entry" | awk -v field="$field" '
    tolower($0) ~ "^[[:space:]-]*" tolower(field) "[[:space:]]*:" {
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      if (value != "" && value != "Pending." && value != "Pending") found = 1
    }
    END { exit found ? 0 : 1 }
  '
}

acceptance_unmarked_count() {
  local content="$1"
  if [[ -z "$content" ]]; then
    echo 0
    return
  fi

  printf '%s\n' "$content" | awk '
    /^[[:space:]]*-/ {
      if ($0 !~ /(^|[[:space:]])(Verified|Blocked|Not Applicable)([[:space:].,;]|$)/) count++
    }
    END { print count + 0 }
  '
}

acceptance_blocked_count() {
  local content="$1"
  if [[ -z "$content" ]]; then
    echo 0
    return
  fi

  printf '%s\n' "$content" | awk '
    /^[[:space:]]*-/ && /(^|[[:space:]])Blocked([[:space:].,;]|$)/ { count++ }
    END { print count + 0 }
  '
}

normalize_acceptance_text() {
  awk '
    {
      line = $0
      sub(/^[[:space:]]*-[[:space:]]*/, "", line)
      sub(/[[:space:]]*:[[:space:]]*(Verified|Blocked|Not Applicable)[[:space:].,;]*$/, "", line)
      sub(/[[:space:]]*(Verified|Blocked|Not Applicable)[[:space:].,;]*$/, "", line)
      gsub(/`/, "", line)
      gsub(/[[:space:]]+/, " ", line)
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
      gsub(/[[:space:].,;]+$/, "", line)
      print tolower(line)
    }
  '
}

acceptance_item_list() {
  local content="$1"
  printf '%s\n' "$content" |
    awk '/^[[:space:]]*-/ { print }' |
    normalize_acceptance_text |
    awk 'length($0) > 0 && !seen[$0]++ { print }'
}

contains_line() {
  local haystack="$1"
  local needle="$2"
  printf '%s\n' "$haystack" | grep -Fxq "$needle"
}

acceptance_match_failures() {
  local criteria="$1"
  local verification="$2"
  local criteria_items verification_items item missing_count extra_count

  criteria_items="$(acceptance_item_list "$criteria")"
  verification_items="$(acceptance_item_list "$verification")"

  if [[ -z "$criteria_items" ]]; then
    echo "Acceptance Criteria are missing or contain no bullet items"
    return
  fi

  if [[ -z "$verification_items" ]]; then
    echo "Acceptance Criteria Verification contains no bullet items"
    return
  fi

  missing_count=0
  while IFS= read -r item; do
    [[ -n "$item" ]] || continue
    if ! contains_line "$verification_items" "$item"; then
      ((missing_count+=1))
    fi
  done <<< "$criteria_items"

  extra_count=0
  while IFS= read -r item; do
    [[ -n "$item" ]] || continue
    if ! contains_line "$criteria_items" "$item"; then
      ((extra_count+=1))
    fi
  done <<< "$verification_items"

  if (( missing_count > 0 )); then
    echo "Acceptance Criteria Verification is missing $missing_count acceptance criterion item(s)"
  fi
  if (( extra_count > 0 )); then
    echo "Acceptance Criteria Verification has $extra_count unmatched extra item(s)"
  fi
}

chunk_requires_test_impact() {
  local file="$1"
  awk '
    /^## (Pass History|QA Review)$/ { exit }
    { print }
  ' "$file" |
    grep -Eiq '(behavior changed|behavior-changing|(^|[^[:alnum:]])ui([^[:alnum:]]|$)|(^|[^[:alnum:]])auth([^[:alnum:]]|$)|backend/API|backend api|API behavior|database|integration|Telegram|workflow tooling|developer/operator|CLI helper|workflow summary|orchestrator handoff|prompt synthesis|runtime smoke|frontend/browser)'
}

test_impact_missing_field_count() {
  local content="$1"
  local missing=0
  local label value
  local labels=(
    "Behavior Changed"
    "Existing Tests Affected"
    "New Tests Required"
    "Regression Risks"
    "Runtime Smoke Needed"
    "Frontend/Browser Coverage Needed"
    "Backend/API Coverage Needed"
    "Scenario/Workflow Coverage Needed"
    "Not-Applicable Rationale"
  )

  for label in "${labels[@]}"; do
    value="$(
      printf '%s\n' "$content" |
        awk -v label="$label" '
          BEGIN { needle = tolower(label) }
          {
            line = tolower($0)
            if (line ~ "^[[:space:]-]*" needle "[[:space:]]*:") {
              value = $0
              sub(/^[^:]*:[[:space:]]*/, "", value)
              gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
              print value
              found = 1
              exit
            }
          }
          END { if (!found) print "" }
        '
    )"
    if [[ -z "$value" || "$value" == "Pending" || "$value" == "Pending." ]]; then
      ((missing+=1))
    fi
  done

  echo "$missing"
}

test_impact_weak_not_applicable() {
  local content="$1"
  local rationale
  if ! printf '%s\n' "$content" | grep -Eiq 'not applicable'; then
    return 1
  fi

  rationale="$(
    printf '%s\n' "$content" |
      awk '
        tolower($0) ~ /^[[:space:]-]*not-applicable rationale[[:space:]]*:/ {
          value = $0
          sub(/^[^:]*:[[:space:]]*/, "", value)
          gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
          print value
          found = 1
          exit
        }
        END { if (!found) print "" }
      '
  )"

  [[ -z "$rationale" || "$rationale" == "Pending" || "$rationale" == "Pending." || "$rationale" == "Not applicable" || "$rationale" == "Not applicable." ]]
}

append_failure() {
  local message="$1"
  failures+=("$message")
}

print_failures() {
  local title="$1"
  shift
  local items=("$@")
  local item
  echo "$title"
  if (( ${#items[@]} == 0 )); then
    echo "  - none"
    return
  fi
  for item in "${items[@]}"; do
    echo "  - $item"
  done
}

mode="${1:-report}"
case "$mode" in
  report | --ready-for-qa | --ready-to-complete)
    ;;
  -h | --help)
    usage
    exit 0
    ;;
  *)
    usage >&2
    exit 2
    ;;
esac

root="$(repo_root)"
shopt -s nullglob
active_chunks=("$root"/ai/chunks/active/chunk-[0-9]*-*.md)
shopt -u nullglob

IFS=$'\n' active_chunks=($(printf '%s\n' "${active_chunks[@]}" | sort))
unset IFS

active_count="${#active_chunks[@]}"
active_chunk=""
active_status=""
if (( active_count == 1 )); then
  active_chunk="${active_chunks[0]}"
  active_status="$(metadata_value "$active_chunk" "Status")"
fi

ready_for_qa_failures=()
ready_to_complete_failures=()
failures=()

phase="manual intervention required"
canonical_state="manual_intervention_required"
latest_pass="(none)"
latest_dev_pass="(none)"
latest_qa_pass="(none)"
qa_status="none"
stale_qa_risk="unknown"
iteration_count="0"
blockers="(not available)"
next_action="manual intervention required"
completion_readiness="not ready"
completion_gate="blocked"

git_dirty="$(git -C "$root" status --short --untracked-files=all)"

if (( active_count == 0 )); then
  append_failure "no active chunk exists"
  if [[ -n "$git_dirty" ]]; then
    phase="commit ready"
    canonical_state="commit_ready"
    next_action="review git status and commit approved changes"
  else
    phase="manual intervention required"
    canonical_state="manual_intervention_required"
    next_action="create or activate a chunk from approved requirements"
  fi
elif (( active_count > 1 )); then
  append_failure "multiple active chunks exist"
  canonical_state="manual_intervention_required"
elif [[ "$active_status" == "Ready for Human Review" ]]; then
  phase="Ready for Human Review"
  canonical_state="ready_for_human_review"
  latest_pass="$(latest_pass_heading "$active_chunk")"
  [[ -n "$latest_pass" ]] || latest_pass="(none)"
  qa_status="$(qa_verdict "$active_chunk")"
  blockers="$(qa_blockers "$active_chunk")"
  stale_qa_risk="no"
  next_action="await close/commit approval or run final human review"
  completion_readiness="ready"
  completion_gate="passed"
else
  execution_notes="$(section "$active_chunk" "Execution Notes")"
  acceptance_verification="$(section "$active_chunk" "Acceptance Criteria Verification")"
  acceptance_criteria="$(section "$active_chunk" "Acceptance Criteria")"
  test_impact="$(section "$active_chunk" "Test Impact")"
  qa_review="$(section "$active_chunk" "QA Review")"
  pass_history="$(section "$active_chunk" "Pass History")"
  latest_pass="$(latest_pass_heading "$active_chunk")"
  latest_dev_pass="$(latest_pass_entry "$active_chunk" "Developer")"
  latest_qa_pass="$(latest_pass_entry "$active_chunk" "QA")"
  latest_dev_line="$(latest_pass_line "$active_chunk" "Developer")"
  latest_qa_line="$(latest_pass_line "$active_chunk" "QA")"
  qa_status="$(qa_verdict "$active_chunk")"
  qa_retry_classification_value="$(qa_retry_classification "$active_chunk")"
  iteration_count="$(pass_count "$active_chunk" "Developer")"
  blockers="$(qa_blockers "$active_chunk")"

  [[ -n "$latest_pass" ]] || latest_pass="(none)"

  if [[ "$qa_status" == PASS* ]] && (( latest_qa_line >= latest_dev_line )) && (( latest_qa_line > 0 )); then
    phase="QA passed"
    canonical_state="qa_passed"
    next_action="check completion readiness"
  elif (( latest_dev_line > latest_qa_line )); then
    phase="ready for QA"
    canonical_state="ready_for_qa"
    next_action="run QA review"
  elif [[ "$qa_status" == BLOCKED* ]]; then
    if (( iteration_count >= 3 )); then
      phase="retry limit reached"
      canonical_state="retry_limit_reached"
      next_action="stop and request human intervention before another Developer retry"
    else
      case "$qa_retry_classification_value" in
        fixable)
          phase="QA blocked - fixable"
          canonical_state="qa_blocked_fixable"
          next_action="send a focused Developer fix prompt for retry-safe QA blockers"
          ;;
        scope_change)
          phase="QA blocked - scope change"
          canonical_state="qa_blocked_scope_change"
          next_action="stop and request human approval or a new chunk for the scope change"
          ;;
        requires_decision)
          phase="QA blocked - requires decision"
          canonical_state="qa_blocked_requires_decision"
          next_action="stop and request human or requirements clarification"
          ;;
        retry_limit_reached)
          phase="retry limit reached"
          canonical_state="retry_limit_reached"
          next_action="stop and request human intervention before another Developer retry"
          ;;
        *)
          phase="QA blocked - unclassified"
          canonical_state="qa_blocked_requires_decision"
          next_action="stop and request QA blocker classification before retry"
          ;;
      esac
    fi
  else
    phase="implementation or manual review needed"
    canonical_state="developer_pass"
    next_action="inspect chunk notes and decide whether Developer or QA should run next"
  fi

  case "$canonical_state" in
    ready_for_qa)
      stale_qa_risk="no - QA pending for latest Developer pass"
      blockers="None for QA readiness"
      ;;
    qa_blocked_fixable | qa_blocked_requires_decision | qa_blocked_scope_change | retry_limit_reached)
      stale_qa_risk="no"
      ;;
    qa_passed | ready_to_complete)
      stale_qa_risk="no"
      blockers="None."
      ;;
    *)
      if (( latest_dev_line > latest_qa_line )) && (( latest_qa_line > 0 )); then
        stale_qa_risk="yes - latest Developer pass is newer than latest QA pass"
      else
        stale_qa_risk="no"
      fi
      ;;
  esac

  if [[ -z "$execution_notes" ]]; then
    ready_for_qa_failures+=("Execution Notes are missing")
    ready_to_complete_failures+=("Execution Notes are missing")
  fi

  if [[ -z "$acceptance_verification" ]]; then
    ready_for_qa_failures+=("Acceptance Criteria Verification is missing")
    ready_to_complete_failures+=("Acceptance Criteria Verification is missing")
  else
    acceptance_unmarked="$(acceptance_unmarked_count "$acceptance_verification")"
    acceptance_blocked="$(acceptance_blocked_count "$acceptance_verification")"
    if (( acceptance_unmarked > 0 )); then
      ready_for_qa_failures+=("Acceptance Criteria Verification has $acceptance_unmarked unmarked item(s)")
      ready_to_complete_failures+=("Acceptance Criteria Verification has $acceptance_unmarked unmarked item(s)")
    fi
    if (( acceptance_blocked > 0 )); then
      ready_to_complete_failures+=("Acceptance Criteria Verification contains $acceptance_blocked Blocked item(s)")
    fi
    while IFS= read -r acceptance_failure; do
      [[ -n "$acceptance_failure" ]] || continue
      ready_for_qa_failures+=("$acceptance_failure")
      ready_to_complete_failures+=("$acceptance_failure")
    done <<< "$(acceptance_match_failures "$acceptance_criteria" "$acceptance_verification")"
  fi

  if [[ -z "$test_impact" ]]; then
    if chunk_requires_test_impact "$active_chunk"; then
      ready_for_qa_failures+=("Test Impact is missing for behavior/tooling change")
      ready_to_complete_failures+=("Test Impact is missing for behavior/tooling change")
    fi
  else
    test_impact_missing="$(test_impact_missing_field_count "$test_impact")"
    if (( test_impact_missing > 0 )); then
      ready_for_qa_failures+=("Test Impact has $test_impact_missing missing or pending field(s)")
      ready_to_complete_failures+=("Test Impact has $test_impact_missing missing or pending field(s)")
    fi
    if test_impact_weak_not_applicable "$test_impact"; then
      ready_for_qa_failures+=("Test Impact marks tests not applicable without a concrete rationale")
      ready_to_complete_failures+=("Test Impact marks tests not applicable without a concrete rationale")
    fi
  fi

  if [[ -z "$pass_history" ]]; then
    ready_to_complete_failures+=("Pass History is missing")
  fi

  if (( latest_dev_line == 0 )); then
    ready_for_qa_failures+=("latest Developer pass is missing")
    ready_to_complete_failures+=("latest Developer pass is missing")
  fi

  if (( latest_dev_line <= latest_qa_line )); then
    ready_for_qa_failures+=("latest pass is not Developer")
  fi

  if [[ -n "$latest_dev_pass" && "$latest_dev_pass" != "(none)" ]]; then
    entry_has_field "$latest_dev_pass" "Validation" || ready_for_qa_failures+=("latest Developer pass is missing validation")
    entry_has_field "$latest_dev_pass" "Cleanup" || ready_for_qa_failures+=("latest Developer pass is missing cleanup")
    entry_has_field "$latest_dev_pass" "Recommended Next Action" || ready_to_complete_failures+=("latest Developer pass is missing recommended next action")
    entry_has_field "$latest_dev_pass" "Validation" || ready_to_complete_failures+=("latest Developer pass is missing validation")
    entry_has_field "$latest_dev_pass" "Cleanup" || ready_to_complete_failures+=("latest Developer pass is missing cleanup")
  fi

  if [[ -z "$qa_review" ]]; then
    ready_to_complete_failures+=("current QA Review is missing")
  fi

  if [[ "$qa_status" != PASS* ]]; then
    ready_to_complete_failures+=("QA verdict is not PASS")
  fi

  if (( latest_qa_line == 0 )); then
    ready_to_complete_failures+=("latest QA pass is missing")
  elif (( latest_dev_line > latest_qa_line )); then
    ready_to_complete_failures+=("latest pass is Developer after latest QA pass")
  fi

  if [[ -n "$latest_qa_pass" && "$latest_qa_pass" != "(none)" ]]; then
    entry_has_field "$latest_qa_pass" "Validation" || ready_to_complete_failures+=("latest QA pass is missing validation")
    entry_has_field "$latest_qa_pass" "Cleanup" || ready_to_complete_failures+=("latest QA pass is missing cleanup")
    entry_has_field "$latest_qa_pass" "Recommended Next Action" || ready_to_complete_failures+=("latest QA pass is missing recommended next action")
  fi
fi

if (( active_count != 1 )); then
  ready_for_qa_failures+=("expected exactly one active chunk; found $active_count")
  ready_to_complete_failures+=("expected exactly one active chunk; found $active_count")
fi

if [[ "$canonical_state" == "ready_for_qa" ]] && (( ${#ready_for_qa_failures[@]} > 0 )); then
  canonical_state="developer_pass"
  phase="Developer pass incomplete"
  next_action="complete Developer pass validation and cleanup before QA"
fi

if (( ${#ready_to_complete_failures[@]} == 0 )); then
  completion_readiness="ready"
  completion_gate="passed"
  canonical_state="ready_to_complete"
  phase="completion readiness passed"
  next_action="complete/archive the chunk, then commit approved changes"
fi

print_report() {
  local status_output diff_output chunk_label status_value completed_value depends_value validation_value
  status_output="$(git -C "$root" status --short --untracked-files=all)"
  diff_output="$(git -C "$root" diff --stat)"

  echo "Workflow State"
  echo
  echo "Active chunk count: $active_count"
  if [[ -n "$active_chunk" ]]; then
    chunk_label="ai/chunks/active/$(basename "$active_chunk")"
    status_value="$(metadata_value "$active_chunk" "Status")"
    completed_value="$(metadata_value "$active_chunk" "Completed")"
    depends_value="$(metadata_value "$active_chunk" "Depends On")"
    validation_value="$(metadata_value "$active_chunk" "Validation")"
    echo "Active chunk: $chunk_label"
    echo "Metadata:"
    echo "  Status: ${status_value:-"(missing)"}"
    echo "  Completed: ${completed_value:-"(empty)"}"
    echo "  Depends On: ${depends_value:-"(empty)"}"
    echo "  Validation: ${validation_value:-"(empty)"}"
  else
    echo "Active chunk: (none or ambiguous)"
  fi
  echo
  echo "Canonical state: $canonical_state"
  echo "Current phase: $phase"
  echo "Latest pass: $latest_pass"
  echo "QA verdict: $qa_status"
  echo "Stale QA risk: $stale_qa_risk"
  echo "Developer iteration count: $iteration_count"
  echo "Blockers: $blockers"
  echo "Recommended next action: $next_action"
  echo "Completion readiness: $completion_readiness"
  echo "Completion gate: $completion_gate"
  echo
  print_failures "Ready-for-QA blockers:" "${ready_for_qa_failures[@]}"
  echo
  print_failures "Ready-to-complete blockers:" "${ready_to_complete_failures[@]}"
  echo
  echo "Git status:"
  if [[ -n "$status_output" ]]; then
    printf '%s\n' "$status_output" | sed 's/^/  /'
  else
    echo "  (clean)"
  fi
  echo
  echo "Diff stat:"
  if [[ -n "$diff_output" ]]; then
    printf '%s\n' "$diff_output" | sed 's/^/  /'
  else
    echo "  (no diff)"
  fi
}

case "$mode" in
  report)
    print_report
    ;;
  --ready-for-qa)
    print_report
    (( ${#ready_for_qa_failures[@]} == 0 ))
    ;;
  --ready-to-complete)
    print_report
    (( ${#ready_to_complete_failures[@]} == 0 ))
    ;;
esac
