#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RUNTIME_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

PROJECT_ROOT="${OPSX_PROJECT_ROOT:-}"
ARGS=()

while [ $# -gt 0 ]; do
  case "$1" in
    -p|--project)
      if [ $# -lt 2 ]; then
        echo "错误: $1 需要项目路径" >&2
        exit 1
      fi
      PROJECT_ROOT="$2"
      shift 2
      ;;
    *)
      ARGS+=("$1")
      shift
      ;;
  esac
done

if [ "${#ARGS[@]}" -gt 0 ]; then
  set -- "${ARGS[@]}"
else
  set --
fi

usage() {
  cat <<'EOF'
用法:
  opsx changes [-p <project>] list
  opsx changes [-p <project>] status
  opsx changes [-p <project>] init <change-name> [schema] [--profile fast|formal|auto] [--source-type lite|bugfix|feature|refactor|docs]
  opsx changes [-p <project>] resolve <change-name>
EOF
}

abs_existing_dir() {
  local input="$1"
  if [ ! -e "$input" ]; then
    echo "错误: 项目路径不存在: $input" >&2
    exit 1
  fi

  if [ -f "$input" ]; then
    input="$(dirname "$input")"
  fi

  (cd "$input" && pwd -P)
}

find_project_root() {
  local dir="$1"
  while true; do
    if [ -f "$dir/openspec/config.yaml" ] || [ -d "$dir/openspec/changes" ]; then
      printf "%s\n" "$dir"
      return 0
    fi

    local parent
    parent="$(dirname "$dir")"
    if [ "$parent" = "$dir" ]; then
      printf "%s\n" "$1"
      return 0
    fi
    dir="$parent"
  done
}

if [ -z "$PROJECT_ROOT" ]; then
  PROJECT_ROOT="$PWD"
fi

PROJECT_ROOT="$(find_project_root "$(abs_existing_dir "$PROJECT_ROOT")")"
CHANGES_DIR="$PROJECT_ROOT/openspec/changes"
SCHEMAS_DIR="$RUNTIME_DIR/schemas"

if [ ! -d "$SCHEMAS_DIR" ] && [ -d "$PROJECT_ROOT/.claude/opsx/schemas" ]; then
  SCHEMAS_DIR="$PROJECT_ROOT/.claude/opsx/schemas"
fi

if [ ! -d "$SCHEMAS_DIR" ] && [ -d "$HOME/.claude/opsx/schemas" ]; then
  SCHEMAS_DIR="$HOME/.claude/opsx/schemas"
fi

ensure_changes_dir() {
  mkdir -p "$CHANGES_DIR"
}

schema_exists() {
  local schema="$1"
  [ -f "$SCHEMAS_DIR/$schema/schema.yaml" ]
}

is_safe_component() {
  local value="$1"
  [ -n "$value" ] || return 1
  [[ "$value" != "." ]] || return 1
  [[ "$value" != ".." ]] || return 1
  [[ "$value" != .* ]] || return 1
  [[ "$value" != */* ]] || return 1
  [[ "$value" != *".."* ]] || return 1
}

validate_component() {
  local kind="$1"
  local value="$2"
  if ! is_safe_component "$value"; then
    echo "错误: $kind 名称不合法: $value" >&2
    exit 1
  fi
  if [ "$value" = "archive" ]; then
    echo "错误: archive 是保留目录名，不能用作 $kind 名称" >&2
    exit 1
  fi
}

valid_profile() {
  case "$1" in
    fast|formal|lean-change|strict-change|auto) return 0 ;;
    *) return 1 ;;
  esac
}

valid_source_type() {
  case "$1" in
    lite|bugfix|feature|refactor|docs) return 0 ;;
    *) return 1 ;;
  esac
}

parse_init_options() {
  PARSED_SCHEMA="spec-driven"
  PARSED_PROFILE="formal"
  PARSED_SOURCE_TYPE="feature"

  if [ $# -gt 0 ] && [[ "${1:-}" != --* ]]; then
    PARSED_SCHEMA="$1"
    shift
  fi

  while [ $# -gt 0 ]; do
    case "$1" in
      --profile)
        if [ $# -lt 2 ]; then
          echo "错误: --profile 需要值" >&2
          exit 1
        fi
        PARSED_PROFILE="$2"
        shift 2
        ;;
      --source-type)
        if [ $# -lt 2 ]; then
          echo "错误: --source-type 需要值" >&2
          exit 1
        fi
        PARSED_SOURCE_TYPE="$2"
        shift 2
        ;;
      *)
        echo "错误: 未知 init 参数 '$1'" >&2
        exit 1
        ;;
    esac
  done

  if ! valid_profile "$PARSED_PROFILE"; then
    echo "错误: profile 仅支持 fast|formal|auto" >&2
    exit 1
  fi
  if [ "$PARSED_PROFILE" = "auto" ]; then
    PARSED_PROFILE="formal"
  fi
  if [ "$PARSED_PROFILE" = "lean-change" ] || [ "$PARSED_PROFILE" = "strict-change" ]; then
    PARSED_PROFILE="formal"
  fi
  if ! valid_source_type "$PARSED_SOURCE_TYPE"; then
    echo "错误: source_type 仅支持 lite|bugfix|feature|refactor|docs" >&2
    exit 1
  fi
}

resolve_target_dir() {
  local target="$1"
  local candidate=""

  if [[ "$target" == *".."* ]] || [[ "$target" == */* ]] || [[ "$target" == /* ]]; then
    echo "错误: change 标识不合法: $target" >&2
    return 1
  fi

  candidate="$CHANGES_DIR/$target"
  if [ -f "$candidate/.openspec.yaml" ]; then
    (cd "$candidate" && pwd -P)
    return 0
  fi

  echo "错误: 找不到 change '$target'" >&2
  return 1
}

change_stage_summary() {
  local dir="$1"
  local has_spec=false has_tasks=false
  [ -f "$dir/spec.md" ] && has_spec=true
  [ -f "$dir/tasks.md" ] && has_tasks=true

  local stage
  if $has_tasks; then
    stage="tasks"
  elif $has_spec; then
    stage="spec"
  else
    stage="empty"
  fi

  local progress=""
  if $has_tasks; then
    local total done
    total=$({ grep -E '^\s*- \[[ x]\] [0-9]+[.][0-9]+' "$dir/tasks.md" 2>/dev/null || true; } | wc -l | tr -d ' ')
    done=$({ grep -E '^\s*- \[x\] [0-9]+[.][0-9]+' "$dir/tasks.md" 2>/dev/null || true; } | wc -l | tr -d ' ')
    if [ "$total" -gt 0 ]; then
      progress=" ($done/$total)"
    fi
  fi

  printf "%s%s\n" "$stage" "$progress"
}

change_stage_name() {
  local dir="$1"
  if [ -f "$dir/tasks.md" ]; then
    echo "tasks"
  elif [ -f "$dir/spec.md" ]; then
    echo "spec"
  else
    echo "empty"
  fi
}

task_progress() {
  local dir="$1"
  if [ ! -f "$dir/tasks.md" ]; then
    echo "-"
    return 0
  fi

  local total done
  total=$({ grep -E '^\s*- \[[ x]\] [0-9]+[.][0-9]+' "$dir/tasks.md" 2>/dev/null || true; } | wc -l | tr -d ' ')
  done=$({ grep -E '^\s*- \[x\] [0-9]+[.][0-9]+' "$dir/tasks.md" 2>/dev/null || true; } | wc -l | tr -d ' ')
  if [ "$total" -gt 0 ]; then
    printf "%s/%s\n" "$done" "$total"
  else
    echo "-"
  fi
}

gate_value() {
  local dir="$1"
  local key="$2"
  local file="$dir/.openspec.yaml"
  if [ ! -f "$file" ]; then
    echo "missing"
    return 0
  fi

  local value
  value="$(awk -v key="$key" '
    function trim(value) {
      sub(/^[[:space:]]*/, "", value)
      sub(/[[:space:]]*$/, "", value)
      gsub(/^"/, "", value)
      gsub(/"$/, "", value)
      return value
    }
    function emit(value) {
      print value
      emitted = 1
      exit
    }
    {
      raw = $0
      if (raw ~ /^[^[:space:]][^:]*:/) {
        top = raw
        sub(/:.*/, "", top)
        in_gates = (top == "gates")
        in_key = 0
        candidate = ""
        next
      }

      if (!in_gates) {
        next
      }

      if (in_key && raw ~ /^[[:space:]][[:space:]][^[:space:]][^:]*:/) {
        if (candidate != "") {
          emit(candidate)
        }
        in_key = 0
        candidate = ""
      }

      line = raw
      sub(/^[[:space:]]*/, "", line)
      split(line, parts, ":")
      field = parts[1]

      if (field == key) {
        rest = line
        sub(/^[^:]+:[[:space:]]*/, "", rest)
        rest = trim(rest)
        if (rest != "") {
          emit(rest)
        }
        in_key = 1
        candidate = ""
        next
      }

      if (in_key && (field == "status" || field == "at")) {
        rest = line
        sub(/^[^:]+:[[:space:]]*/, "", rest)
        rest = trim(rest)
        if (rest != "") {
          if (field == "status") {
            emit(rest)
          }
          if (candidate == "") {
            candidate = rest
          }
        }
      }
    }
    END {
      if (!emitted && candidate != "") {
        print candidate
      }
    }
  ' "$file")"

  if [ -n "$value" ]; then
    printf "%s\n" "$value"
  else
    echo "missing"
  fi
}

gate_status() {
  local dir="$1"
  local key="$2"
  local value
  value="$(gate_value "$dir" "$key")"

  case "$value" in
    missing|""|stale|needs_rerun|fail|failed|blocked|skip|skipped)
      echo "missing"
      ;;
    pass|pass_with_warnings)
      echo "pass"
      ;;
    [0-9][0-9][0-9][0-9]-*)
      echo "pass"
      ;;
    *)
      echo "missing"
      ;;
  esac
}

yaml_value() {
  local dir="$1"
  local key="$2"
  local file="$dir/.openspec.yaml"
  if [ ! -f "$file" ]; then
    echo "missing"
    return 0
  fi

  local value
  value="$(awk -v key="$key" '
    {
      line = $0
      sub(/^[[:space:]]*/, "", line)
      split(line, parts, ":")
      if (parts[1] == key) {
        sub(/^[^:]+:[[:space:]]*/, "", line)
        gsub(/^"/, "", line)
        gsub(/"$/, "", line)
        print line
        exit
      }
    }
  ' "$file")"

  if [ -n "$value" ]; then
    printf "%s\n" "$value"
  else
    echo "missing"
  fi
}

copy_template_if_missing() {
  local template="$1"
  local destination="$2"

  if [ -f "$destination" ]; then
    return 0
  fi
  if [ ! -f "$template" ]; then
    echo "错误: 模板不存在: $template" >&2
    exit 1
  fi

  local content
  content="$(cat "$template")"
  content="${content//<id>/$TEMPLATE_ID}"
  content="${content//<profile>/$TEMPLATE_PROFILE}"
  content="${content//<source_type>/$TEMPLATE_SOURCE_TYPE}"
  content="${content//<created>/$TEMPLATE_CREATED}"
  printf "%s\n" "$content" > "$destination"
}

materialize_change_artifacts() {
  local change_dir="$1"
  local id="$2"
  local profile="$3"
  local source_type="$4"
  local created="$5"
  local templates_dir="$SCHEMAS_DIR/spec-driven/templates"

  TEMPLATE_ID="$id"
  TEMPLATE_PROFILE="$profile"
  TEMPLATE_SOURCE_TYPE="$source_type"
  TEMPLATE_CREATED="$created"
  copy_template_if_missing "$templates_dir/spec.md" "$change_dir/spec.md"
  copy_template_if_missing "$templates_dir/tasks.md" "$change_dir/tasks.md"
  copy_template_if_missing "$templates_dir/evidence.md" "$change_dir/evidence.md"
}

has_specs() {
  local dir="$1"
  [ -f "$dir/spec.md" ] || { [ -d "$dir/specs" ] && find "$dir/specs" -type f -name "*.md" -print -quit | grep -q .; }
}

tasks_complete() {
  local dir="$1"
  if [ ! -f "$dir/tasks.md" ]; then
    return 1
  fi

  local total done
  total=$({ grep -E '^\s*- \[[ x]\] [0-9]+[.][0-9]+' "$dir/tasks.md" 2>/dev/null || true; } | wc -l | tr -d ' ')
  done=$({ grep -E '^\s*- \[x\] [0-9]+[.][0-9]+' "$dir/tasks.md" 2>/dev/null || true; } | wc -l | tr -d ' ')
  [ "$total" -gt 0 ] && [ "$done" -eq "$total" ]
}

plan_gate_status() {
  local dir="$1"
  local status
  status="$(gate_status "$dir" "plan")"
  if [ "$status" = "pass" ]; then
    echo "pass"
  else
    gate_status "$dir" "readiness"
  fi
}

yes_no_file() {
  local file="$1"
  if [ -f "$file" ]; then
    echo "yes"
  else
    echo "no"
  fi
}

next_step() {
  local dir="$1"
  if ! has_specs "$dir" || [ ! -f "$dir/tasks.md" ] || [ ! -f "$dir/evidence.md" ]; then
    echo "opsx-plan"
  elif [ "$(plan_gate_status "$dir")" != "pass" ]; then
    echo "opsx-plan"
  elif ! tasks_complete "$dir"; then
    echo "opsx-implement"
  elif [ "$(gate_status "$dir" "verify")" != "pass" ]; then
    echo "opsx-verify"
  else
    echo "opsx-archive"
  fi
}

print_change() {
  local dir="$1"
  local name
  name="$(basename "$dir")"
  printf "  %-40s  [%s profile=%s next=%s]\n" "$name" "$(change_stage_summary "$dir")" "$(yaml_value "$dir" "profile")" "$(next_step "$dir")"
}

list_changes() {
  local active=()
  if [ -d "$CHANGES_DIR" ]; then
    while IFS= read -r -d '' d; do
      [[ "$(basename "$d")" == "archive" ]] && continue
      [ -f "$d/.openspec.yaml" ] || continue
      active+=("$d")
    done < <(find "$CHANGES_DIR" -mindepth 1 -maxdepth 1 -type d -not -name "archive" -print0 | sort -z)
  fi

  if [ ${#active[@]} -eq 0 ]; then
    echo "无活动变更"
    return 0
  fi

  if [ ${#active[@]} -gt 0 ]; then
    echo "活动变更 (${#active[@]}):"
    for d in "${active[@]}"; do
      print_change "$d"
    done
  fi

}

print_status_change() {
  local dir="$1"
  local indent="${2:-}"
  local name
  name="$(basename "$dir")"

  printf "%sChange: %s\n" "$indent" "$name"
  printf "%s  Profile: %s\n" "$indent" "$(yaml_value "$dir" "profile")"
  printf "%s  Source type: %s\n" "$indent" "$(yaml_value "$dir" "source_type")"
  printf "%s  Stage: %s\n" "$indent" "$(change_stage_name "$dir")"
  printf "%s  Tasks: %s\n" "$indent" "$(task_progress "$dir")"
  printf "%s  Gates:\n" "$indent"
  printf "%s    plan:        %s\n" "$indent" "$(gate_value "$dir" "plan")"
  printf "%s    verify:       %s\n" "$indent" "$(gate_value "$dir" "verify")"
  printf "%s  Artifacts:\n" "$indent"
  printf "%s    spec.md:                 %s\n" "$indent" "$(if has_specs "$dir"; then echo yes; else echo no; fi)"
  printf "%s    tasks.md:                %s\n" "$indent" "$(yes_no_file "$dir/tasks.md")"
  printf "%s    evidence.md:             %s\n" "$indent" "$(yes_no_file "$dir/evidence.md")"
  printf "%s  Next: %s\n" "$indent" "$(next_step "$dir")"
}

status_changes() {
  echo "Project: $PROJECT_ROOT"

  local active=()
  if [ -d "$CHANGES_DIR" ]; then
    while IFS= read -r -d '' d; do
      [[ "$(basename "$d")" == "archive" ]] && continue
      [ -f "$d/.openspec.yaml" ] || continue
      active+=("$d")
    done < <(find "$CHANGES_DIR" -mindepth 1 -maxdepth 1 -type d -not -name "archive" -print0 | sort -z)
  fi

  echo
  echo "Active changes: ${#active[@]}"

  local d
  if [ ${#active[@]} -gt 0 ]; then
    for d in "${active[@]}"; do
      echo
      print_status_change "$d"
    done
  fi

}

init_change() {
  local name="${1:-}"
  shift || true
  parse_init_options "$@"
  local schema="$PARSED_SCHEMA"
  local profile="$PARSED_PROFILE"
  local source_type="$PARSED_SOURCE_TYPE"

  validate_component "change" "$name"
  if ! schema_exists "$schema"; then
    echo "错误: 找不到 schema '$schema' ($SCHEMAS_DIR/$schema/schema.yaml)" >&2
    exit 1
  fi

  ensure_changes_dir
  local change_dir="$CHANGES_DIR/$name"
  local meta_file="$change_dir/.openspec.yaml"
  local created
  created="$(date +%F)"
  mkdir -p "$change_dir"

  if [ ! -f "$meta_file" ]; then
    {
      printf "schema: %s\n" "$schema"
      echo "kind: change"
      printf "profile: %s\n" "$profile"
      printf "source_type: %s\n" "$source_type"
      printf "created: %s\n" "$created"
      echo "status: in_progress"
      echo "gates:"
      echo "  plan:"
      echo "    status:"
      echo "    at:"
      echo "  verify:"
      echo "    status:"
      echo "    at:"
      echo "  archive:"
      echo "    status:"
      echo "    at:"
    } > "$meta_file"
    materialize_change_artifacts "$change_dir" "$name" "$profile" "$source_type" "$created"
    echo "已初始化变更: openspec/changes/$name"
  else
    echo "变更已存在: openspec/changes/$name"
  fi
  echo "schema: $schema"
  echo "profile: $profile"
  echo "source_type: $source_type"
}

command="${1:-list}"

case "$command" in
  list)
    list_changes
    ;;
  status)
    status_changes
    ;;
  init)
    shift
    name="${1:-}"
    shift || true
    init_change "$name" "$@"
    ;;
  resolve)
    shift
    resolve_target_dir "${1:-}"
    ;;
  -h|--help|help)
    usage
    ;;
  *)
    echo "错误: 未知命令 '$command'" >&2
    usage >&2
    exit 1
    ;;
esac
