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

usage() {
  cat <<'USAGE'
Usage:
  plan-tracking-issue.sh --plan <path> [options]

Create or preview a lightweight provider tracking issue for an existing plan bundle.

Required:
  --plan <path>                  Existing plan file, usually docs/plans/<slug>/<slug>-plan.md

Source and provider:
  --source <path-or-url>         Primary source override. If omitted, parse the plan Read First Primary source line.
  --execution-state <path>       Execution-state path override. Defaults from the plan filename when possible.
  --provider <github|gitlab|auto>
                                 Provider to use. Default: auto from git remote.
  --repo <owner/repo|group/repo> Provider repository override.

Metadata:
  --title <text>                 Issue title. Default: Track plan: <plan H1>
  --label <name>                 Repeatable label.
  --assignee <login>             Repeatable assignee.
  --milestone <name>             Milestone title.
  --project <title>              GitHub-only project title. Repeatable.
  --due-date <YYYY-MM-DD>        GitLab-only due date.
  --weight <number>              GitLab-only issue weight.
  --confidential                 GitLab-only confidential issue.

Safety/output:
  --dry-run                      Render and print the provider command without mutating remote state.
  --body-out <path>              Write the rendered issue body to this path.
  --issue-url-out <path>         Write the created/dry-run issue URL marker to this path.
  -h, --help                     Show this help.
USAGE
}

die() {
  echo "error: $*" >&2
  exit 1
}

usage_error() {
  echo "error: $*" >&2
  usage >&2
  exit 2
}

require_cmd() {
  local cmd="${1:-}"
  command -v "$cmd" >/dev/null 2>&1 || die "$cmd is required"
}

print_cmd() {
  local out=''
  local arg=''
  for arg in "$@"; do
    out+=" $(printf '%q' "$arg")"
  done
  printf '%s\n' "${out# }"
}

repo_root() {
  git rev-parse --show-toplevel 2>/dev/null || pwd
}

repo_relative_path() {
  local root="${1:-}"
  local path="${2:-}"
  python3 - "$root" "$path" <<'PY'
from pathlib import Path
import sys

root = Path(sys.argv[1]).resolve()
path = Path(sys.argv[2])
if not path.is_absolute():
    path = root / path
try:
    print(path.resolve().relative_to(root).as_posix())
except ValueError:
    raise SystemExit(1)
PY
}

is_local_source_value() {
  local value="${1:-}"
  case "$value" in
    http://*|https://*|\#*|plan-only\ waiver:*)
      return 1
      ;;
    *)
      return 0
      ;;
  esac
}

verify_committed_file() {
  local label="${1:-file}"
  local path="${2:-}"
  local root=''
  local rel=''

  root="$(repo_root)"
  if ! rel="$(repo_relative_path "$root" "$path")"; then
    die "live issue creation requires committed local files; ${label} is outside the git work tree: $path"
  fi

  if [[ ! -f "$root/$rel" ]]; then
    die "live issue creation requires committed local files; ${label} does not exist: $rel"
  fi

  if ! git -C "$root" cat-file -e "HEAD:$rel" 2>/dev/null; then
    die "live issue creation requires committed local files; ${label} is not committed: $rel. Commit it before creating a live tracking issue, or use --dry-run."
  fi

  if ! git -C "$root" diff --quiet -- "$rel"; then
    die "live issue creation requires committed local files; ${label} has unstaged changes: $rel. Commit it before creating a live tracking issue, or use --dry-run."
  fi

  if ! git -C "$root" diff --cached --quiet -- "$rel"; then
    die "live issue creation requires committed local files; ${label} has staged changes not in HEAD: $rel. Commit it before creating a live tracking issue, or use --dry-run."
  fi
}

provider='auto'
plan_path=''
source_value=''
execution_state_path=''
repo_override=''
title=''
milestone=''
due_date=''
weight=''
dry_run='0'
confidential='0'
body_out=''
issue_url_out=''
declare -a labels=()
declare -a assignees=()
declare -a projects=()

while [[ $# -gt 0 ]]; do
  case "${1:-}" in
    --plan)
      [[ $# -ge 2 ]] || usage_error "--plan requires a path"
      plan_path="${2:-}"
      shift 2
      ;;
    --source)
      [[ $# -ge 2 ]] || usage_error "--source requires a value"
      source_value="${2:-}"
      shift 2
      ;;
    --execution-state)
      [[ $# -ge 2 ]] || usage_error "--execution-state requires a path"
      execution_state_path="${2:-}"
      shift 2
      ;;
    --provider)
      [[ $# -ge 2 ]] || usage_error "--provider requires github, gitlab, or auto"
      provider="${2:-}"
      shift 2
      ;;
    --repo)
      [[ $# -ge 2 ]] || usage_error "--repo requires a value"
      repo_override="${2:-}"
      shift 2
      ;;
    --title)
      [[ $# -ge 2 ]] || usage_error "--title requires text"
      title="${2:-}"
      shift 2
      ;;
    --label)
      [[ $# -ge 2 ]] || usage_error "--label requires a name"
      labels+=("${2:-}")
      shift 2
      ;;
    --assignee)
      [[ $# -ge 2 ]] || usage_error "--assignee requires a login"
      assignees+=("${2:-}")
      shift 2
      ;;
    --milestone)
      [[ $# -ge 2 ]] || usage_error "--milestone requires a value"
      milestone="${2:-}"
      shift 2
      ;;
    --project)
      [[ $# -ge 2 ]] || usage_error "--project requires a title"
      projects+=("${2:-}")
      shift 2
      ;;
    --due-date)
      [[ $# -ge 2 ]] || usage_error "--due-date requires YYYY-MM-DD"
      due_date="${2:-}"
      shift 2
      ;;
    --weight)
      [[ $# -ge 2 ]] || usage_error "--weight requires a number"
      weight="${2:-}"
      shift 2
      ;;
    --confidential)
      confidential='1'
      shift
      ;;
    --dry-run)
      dry_run='1'
      shift
      ;;
    --body-out)
      [[ $# -ge 2 ]] || usage_error "--body-out requires a path"
      body_out="${2:-}"
      shift 2
      ;;
    --issue-url-out)
      [[ $# -ge 2 ]] || usage_error "--issue-url-out requires a path"
      issue_url_out="${2:-}"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      usage_error "unknown argument: ${1:-}"
      ;;
  esac
done

[[ -n "$plan_path" ]] || usage_error "--plan is required"
[[ -f "$plan_path" ]] || die "plan file not found: $plan_path"

case "$provider" in
  github|gitlab|auto)
    ;;
  *)
    usage_error "--provider must be github, gitlab, or auto"
    ;;
esac

if [[ "$provider" == "auto" ]]; then
  remote_url="$(git remote get-url origin 2>/dev/null || true)"
  case "$remote_url" in
    *github.com*|*github:*)
      provider='github'
      ;;
    *gitlab.com*|*gitlab:*)
      provider='gitlab'
      ;;
    *)
      usage_error "cannot infer provider from git remote; pass --provider github or --provider gitlab"
      ;;
  esac
fi

if [[ "$provider" == "github" ]]; then
  [[ -z "$due_date" ]] || usage_error "--due-date is GitLab-only"
  [[ -z "$weight" ]] || usage_error "--weight is GitLab-only"
  [[ "$confidential" == "0" ]] || usage_error "--confidential is GitLab-only"
fi

if [[ "$provider" == "gitlab" ]]; then
  [[ "${#projects[@]}" -eq 0 ]] || usage_error "--project is GitHub-only"
fi

require_cmd python3

render_output="$(
  python3 - "$plan_path" "$source_value" "$execution_state_path" "$title" "$provider" "$repo_override" <<'PY'
from __future__ import annotations

import re
import sys
from pathlib import Path


def die(message: str) -> None:
    print(f"error: {message}", file=sys.stderr)
    raise SystemExit(1)


plan_path = Path(sys.argv[1])
source_arg = sys.argv[2].strip()
execution_state_arg = sys.argv[3].strip()
title_arg = sys.argv[4].strip()
provider = sys.argv[5].strip()
repo_override = sys.argv[6].strip()

text = plan_path.read_text(encoding="utf-8", errors="replace")
lines = text.splitlines()
tick = chr(96)

plan_title = ""
for line in lines:
    if line.startswith("# "):
        plan_title = line[2:].strip().strip(tick)
        break
if plan_title.lower().startswith("plan:"):
    plan_title = plan_title.split(":", 1)[1].strip().strip(tick)
if not plan_title:
    plan_title = plan_path.stem.replace("-plan", "").replace("-", " ").strip().title() or "Implementation Plan"


def extract_source_from_plan() -> str:
    for line in lines:
        if "Primary source:" not in line:
            continue
        value = line.split("Primary source:", 1)[1].strip()
        value = value.lstrip("-").strip()
        if not value:
            continue
        markdown_link = re.search(r"\[[^\]]+\]\(([^)]+)\)", value)
        if markdown_link:
            return markdown_link.group(1).strip()
        tick_start = value.find(tick)
        if tick_start != -1:
            tick_end = value.find(tick, tick_start + 1)
            if tick_end != -1:
                return value[tick_start + 1 : tick_end].strip()
        return value.strip("<>").strip()
    return ""


source_value = source_arg or extract_source_from_plan()
if not source_value:
    die("missing primary source; pass --source or add a Read First Primary source line to the plan")

if not execution_state_arg:
    if plan_path.name.endswith("-plan.md"):
        execution_state = plan_path.with_name(plan_path.name.removesuffix("-plan.md") + "-execution-state.md")
    else:
        execution_state = plan_path.with_name(plan_path.stem + "-execution-state.md")
else:
    execution_state = Path(execution_state_arg)

issue_title = title_arg or f"Track plan: {plan_title}"

execution_state_note = "exists" if execution_state.exists() else "expected"

body_lines = [
    "## Plan Tracking",
    "",
    "This issue is a lightweight tracking surface. The plan bundle remains the source of truth for execution scope, sequencing, and validation.",
    "",
    "## Read First",
    "",
    f"- Primary source: {tick}{source_value}{tick}",
    f"- Plan: {tick}{plan_path.as_posix()}{tick}",
    f"- Execution state: {tick}{execution_state.as_posix()}{tick} ({execution_state_note})",
    "",
    "## Tracking Metadata",
    "",
    f"- Provider: {provider}",
]
if repo_override:
    body_lines.append(f"- Repository override: {tick}{repo_override}{tick}")
body_lines.extend(
    [
        "- Tracking role: follow-up timeline and implementation entrypoint",
        "",
        "## Next Action",
        "",
        "- Review or execute the linked plan bundle through the appropriate plan execution workflow.",
        "",
        "## Guardrails",
        "",
        "- Do not duplicate detailed requirements, findings, or task decomposition here.",
        "- Keep substantial execution updates in the plan execution state or provider comments that link back to it.",
    ]
)

print(f"TITLE::{issue_title}")
print(f"SOURCE::{source_value}")
print("BODY::")
print("\n".join(body_lines))
PY
)"

render_metadata="${render_output%%$'\n'BODY::*}"
issue_body="${render_output#*$'\n'BODY::$'\n'}"
issue_title=''
rendered_source_value=''
while IFS= read -r line; do
  case "$line" in
    TITLE::*)
      issue_title="${line#TITLE::}"
      ;;
    SOURCE::*)
      rendered_source_value="${line#SOURCE::}"
      ;;
  esac
done <<<"$render_metadata"

[[ -n "$issue_title" ]] || die "failed to render issue title"
[[ -n "$rendered_source_value" ]] || die "failed to render primary source"

if [[ -n "$body_out" ]]; then
  mkdir -p "$(dirname "$body_out")"
  printf '%s\n' "$issue_body" >"$body_out"
  body_file="$body_out"
  cleanup_body='0'
else
  body_file="$(mktemp "${TMPDIR:-/tmp}/plan-tracking-issue.XXXXXX.md")"
  printf '%s\n' "$issue_body" >"$body_file"
  cleanup_body='1'
fi

cleanup() {
  if [[ "${cleanup_body:-0}" == "1" && -n "${body_file:-}" ]]; then
    rm -f "$body_file"
  fi
}
trap cleanup EXIT

issue_url=''

if [[ "$dry_run" != "1" ]]; then
  verify_committed_file "plan file" "$plan_path"
  if is_local_source_value "$rendered_source_value"; then
    verify_committed_file "primary source file" "$rendered_source_value"
  fi
fi

if [[ "$provider" == "github" ]]; then
  declare -a cmd=(gh issue create --title "$issue_title" --body-file "$body_file")
  if [[ -n "$repo_override" ]]; then
    cmd+=(-R "$repo_override")
  fi
  if [[ ${#labels[@]} -gt 0 ]]; then
    for label in "${labels[@]}"; do
      cmd+=(--label "$label")
    done
  fi
  if [[ ${#assignees[@]} -gt 0 ]]; then
    for assignee in "${assignees[@]}"; do
      cmd+=(--assignee "$assignee")
    done
  fi
  if [[ ${#projects[@]} -gt 0 ]]; then
    for project in "${projects[@]}"; do
      cmd+=(--project "$project")
    done
  fi
  if [[ -n "$milestone" ]]; then
    cmd+=(--milestone "$milestone")
  fi

  if [[ "$dry_run" == "1" ]]; then
    echo "dry-run: $(print_cmd "${cmd[@]}")" >&2
    issue_url='DRY-RUN-TRACKING-ISSUE-URL'
  else
    require_cmd gh
    gh auth status >/dev/null
    issue_url="$("${cmd[@]}")"
  fi
else
  declare -a cmd=(glab issue create --title "$issue_title" --description "$issue_body" --yes)
  if [[ -n "$repo_override" ]]; then
    cmd+=(--repo "$repo_override")
  fi
  if [[ ${#labels[@]} -gt 0 ]]; then
    for label in "${labels[@]}"; do
      cmd+=(--label "$label")
    done
  fi
  if [[ ${#assignees[@]} -gt 0 ]]; then
    for assignee in "${assignees[@]}"; do
      cmd+=(--assignee "$assignee")
    done
  fi
  if [[ -n "$milestone" ]]; then
    cmd+=(--milestone "$milestone")
  fi
  if [[ -n "$due_date" ]]; then
    cmd+=(--due-date "$due_date")
  fi
  if [[ -n "$weight" ]]; then
    cmd+=(--weight "$weight")
  fi
  if [[ "$confidential" == "1" ]]; then
    cmd+=(--confidential)
  fi

  if [[ "$dry_run" == "1" ]]; then
    echo "dry-run: $(print_cmd "${cmd[@]}")" >&2
    issue_url='DRY-RUN-TRACKING-ISSUE-URL'
  else
    require_cmd glab
    glab auth status >/dev/null
    issue_url="$("${cmd[@]}")"
  fi
fi

if [[ -n "$issue_url_out" ]]; then
  mkdir -p "$(dirname "$issue_url_out")"
  printf '%s\n' "$issue_url" >"$issue_url_out"
fi

printf '%s\n' "$issue_url"
printf 'provider=%s\n' "$provider"
printf 'title=%s\n' "$issue_title"
printf 'body_file=%s\n' "$body_file"
