# shellcheck shell=bash

trim_whitespace() {
  local value="${1-}"
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

is_interactive_session() {
  [[ -t 0 && -t 1 ]]
}

split_task_key() {
  local raw_key=""
  raw_key="$(trim_whitespace "${TASK_KEY_INPUT-}")"

  if [[ -z "$raw_key" ]]; then
    return 0
  fi

  if [[ "$raw_key" != *:*:* ]]; then
    printf 'invalid --task-key value: %s (expected <project-id>:<task-group>:<task-id>)\n' "$raw_key" >&2
    exit 1
  fi

  local key_proj="${raw_key%%:*}"
  local key_rest="${raw_key#*:}"
  local key_group="${key_rest%%:*}"
  local key_id="${key_rest#*:}"

  if [[ -z "$key_proj" || -z "$key_group" || -z "$key_id" || "$key_id" == *:* ]]; then
    printf 'invalid --task-key value: %s (expected <project-id>:<task-group>:<task-id>)\n' "$raw_key" >&2
    exit 1
  fi

  if [[ -n "$PROJECT_ID" && "$PROJECT_ID" != "$key_proj" ]]; then
    printf '%s\n' "--task-key project-id ($key_proj) conflicts with --project-id ($PROJECT_ID)" >&2
    exit 1
  fi
  if [[ -n "$TASK_GROUP" && "$TASK_GROUP" != "$key_group" ]]; then
    printf '%s\n' "--task-key task-group ($key_group) conflicts with --task-group ($TASK_GROUP)" >&2
    exit 1
  fi
  if [[ -n "$TASK_ID" && "$TASK_ID" != "$key_id" ]]; then
    printf '%s\n' "--task-key task-id ($key_id) conflicts with --task-id ($TASK_ID)" >&2
    exit 1
  fi

  PROJECT_ID="$key_proj"
  TASK_GROUP="$key_group"
  TASK_ID="$key_id"
}

resolve_task_root_for_shortcut() {
  local project_root="$1"
  local project_id="$2"
  local task_group="$3"
  local task_id="$4"

  local resolved=""
  resolved="$(python3 - "$OKSTRA_PYTHONPATH" "$project_root" "$project_id" "$task_group" "$task_id" <<'PY'
import json, os, sys
from pathlib import Path

# task root 의 slug 경로 구성은 okstra_ctl.paths.task_dir(SSOT) 에 위임한다.
# 과거 이 heredoc 은 slugify 와 `.okstra/tasks/<slug>/<slug>` 구조를 자체
# 재구현해 규칙 변경 시 silent drift 위험이 있었다. project-resolver.sh 와
# 동일하게 $OKSTRA_PYTHONPATH 를 sys.path 에 올려 패키지를 import 한다.
sys.path.insert(0, sys.argv[1])
from okstra_ctl.paths import task_dir

project_root = Path(sys.argv[2])
project_id = sys.argv[3]
task_group = sys.argv[4]
task_id = sys.argv[5]

requested_key = f"{project_id}:{task_group}:{task_id}"
requested_key_ci = requested_key.lower()

candidates = []

catalog_path = project_root / ".okstra" / "discovery" / "task-catalog.json"
catalog_keys = []
if catalog_path.is_file():
    try:
        catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
    except Exception as exc:
        sys.stderr.write(f"resolve_task_root: failed to parse {catalog_path}: {exc}\n")
        catalog = None
    if isinstance(catalog, dict):
        catalog_project_id = catalog.get("projectId")
        tasks = catalog.get("tasks") or []
        for entry in tasks:
            if not isinstance(entry, dict):
                continue
            entry_key = entry.get("taskKey") or ""
            if not isinstance(entry_key, str) or not entry_key:
                continue
            entry_belongs_to_project = (
                catalog_project_id == project_id
                or entry.get("projectId") == project_id
                or entry_key.startswith(f"{project_id}:")
            )
            if entry_belongs_to_project:
                catalog_keys.append(entry_key)
            if entry_key.lower() == requested_key_ci:
                rel = entry.get("taskRootPath") or entry.get("taskRoot")
                if isinstance(rel, str) and rel:
                    abs_path = (project_root / rel).resolve() if not os.path.isabs(rel) else Path(rel)
                    if abs_path.is_dir():
                        print(f"OK\t{abs_path}")
                        sys.exit(0)
                    candidates.append(str(abs_path))

slug_path = task_dir(project_root, task_group, task_id)
if slug_path.is_dir():
    print(f"OK\t{slug_path}")
    sys.exit(0)
candidates.append(str(slug_path))

# Failure path: emit diagnostics so the wrapper can show them.
print("MISSING")
print(f"REQUESTED\t{requested_key}")
for cand in candidates:
    print(f"TRIED\t{cand}")
for key in sorted(set(catalog_keys)):
    print(f"AVAILABLE\t{key}")
PY
)"

  local status=""
  status="$(printf '%s' "$resolved" | head -n 1 | awk -F '\t' '{print $1}')"

  if [[ "$status" == "OK" ]]; then
    printf '%s' "$resolved" | head -n 1 | awk -F '\t' '{print $2}'
    return 0
  fi

  printf 'task root not found for %s:%s:%s\n' "$project_id" "$task_group" "$task_id" >&2
  printf '%s\n' "$resolved" | awk -F '\t' '
    $1 == "TRIED"    { printf "  tried: %s\n", $2 }
    $1 == "AVAILABLE"{ avail = avail "    " $2 "\n" }
    END {
      if (avail != "") {
        printf "  available task-keys for this project (case- and slug-sensitive on disk):\n%s", avail
      }
    }' >&2
  printf '  hint: task-key lookup is case-insensitive against task-catalog.json. Group / id segments on disk are slugified (lowercased, non-alphanumeric runs collapsed to "-").\n' >&2
  return 1
}

autofill_from_manifest() {
  if [[ -z "$PROJECT_ID" ]]; then
    return 0
  fi
  if [[ -n "$BRIEF_PATH" && -n "$TASK_TYPE" ]]; then
    return 0
  fi
  if [[ -z "$TASK_GROUP" || -z "$TASK_ID" ]]; then
    return 0
  fi

  # 신규 모델: PROJECT_ROOT 는 okstra.sh 가 시작 단계에서 best-effort 해석해 둔다.
  # 미해석(빈 문자열) 상태이거나 실제 디렉토리가 아니면 autofill 을 skip 한다 — 과거
  # "conf 파일 부재" 분기와 동일한 의미.
  if [[ -z "${PROJECT_ROOT:-}" || ! -d "${PROJECT_ROOT:-}" ]]; then
    return 0
  fi
  local manifest_project_root="$PROJECT_ROOT"

  local manifest_task_root=""
  manifest_task_root="$(resolve_task_root_for_shortcut "$manifest_project_root" "$PROJECT_ID" "$TASK_GROUP" "$TASK_ID" 2>/dev/null)" || return 0
  local manifest_path="$manifest_task_root/task-manifest.json"
  if [[ ! -f "$manifest_path" ]]; then
    return 0
  fi

  PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - "$manifest_project_root" "$manifest_task_root" <<'PY' || true
import sys
from pathlib import Path

from okstra_ctl.implementation_outcome import reconcile_implementation_outcome

try:
    reconcile_implementation_outcome(Path(sys.argv[1]), Path(sys.argv[2]))
except Exception:
    pass
PY

  local need_brief_val need_type_val
  need_brief_val="$([[ -z "$BRIEF_PATH" ]] && printf '1' || printf '0')"
  need_type_val="$([[ -z "$TASK_TYPE" ]] && printf '1' || printf '0')"
  local autofill_output=""
  # autofill 은 편의 기능이라 실패해도 run 을 끊으면 안 된다. heredoc 이
  # okstra_ctl 을 import 하므로 설치가 깨지면 non-zero 로 끝나는데, okstra.sh 는
  # `set -euo pipefail` 이라 이 평범한 대입 하나가 run 을 통째로 중단시킨다.
  # 위 reconcile 호출(:189)이 같은 이유로 `|| true` 를 달고 있다.
  autofill_output="$(PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - "$manifest_path" "$need_brief_val" "$need_type_val" <<'PY'
import json, sys

from okstra_ctl import next_phase

path = sys.argv[1]
need_brief = sys.argv[2] == "1"
need_type = sys.argv[3] == "1"
try:
    data = json.loads(open(path, "r", encoding="utf-8").read())
except Exception as exc:
    sys.stderr.write(f"autofill_from_manifest: failed to parse {path}: {exc}\n")
    sys.exit(0)

brief = ""
task_type = ""
if need_brief:
    brief = (data.get("taskBriefPath") or "").strip()
if need_type:
    # 포인터 판정은 Python SSOT 가 갖는다 — `ready` 가 아니면 빈 문자열이라
    # 셸이 센티널 목록을 따로 들고 있을 이유가 없다.
    task_type = next_phase.autofill_task_type(data)

print(f"BRIEF={brief}")
print(f"TYPE={task_type}")
PY
)" || true

  local manifest_brief=""
  local manifest_type=""
  while IFS= read -r line; do
    case "$line" in
      BRIEF=*) manifest_brief="${line#BRIEF=}" ;;
      TYPE=*)   manifest_type="${line#TYPE=}" ;;
    esac
  done <<<"$autofill_output"

  if [[ -z "$TASK_TYPE" && -n "$manifest_type" ]]; then
    TASK_TYPE="$manifest_type"
    printf 'autofill: task-type from manifest workflow.nextRecommendedPhase: %s\n' "$TASK_TYPE" >&2
  fi
  # release-handoff 는 brief 입력이 없다 (prepare 가 검증 보고서 인용 input 을
  # 생성) — task-type autofill 이후에 판정해야 하므로 순서가 위와 바뀌면 안 된다.
  if [[ -z "$BRIEF_PATH" && -n "$manifest_brief" && "$TASK_TYPE" != "release-handoff" ]]; then
    BRIEF_PATH="$manifest_brief"
    printf 'autofill: brief-path from task-manifest.json: %s\n' "$BRIEF_PATH" >&2
  fi
}

missing_required_arguments_summary() {
  local missing=()

  [[ -z "$PROJECT_ID" ]] && missing+=("<project-id>")
  [[ -z "$TASK_GROUP" ]] && missing+=("<task-group>")
  [[ -z "$TASK_ID" ]] && missing+=("<task-id>")
  [[ -z "$TASK_TYPE" ]] && missing+=("<task-type>")
  # release-handoff 는 brief 입력이 없다 — prepare 가 input 문서를 생성한다.
  [[ -z "$BRIEF_PATH" && "$TASK_TYPE" != "release-handoff" ]] && missing+=("<brief-path>")

  if (( ${#missing[@]} == 0 )); then
    printf '%s' ""
    return 0
  fi

  printf '%s' "${missing[*]}"
}

prompt_for_required_argument() {
  local variable_name="$1"
  local prompt_label="$2"
  local current_value="${!variable_name}"

  current_value="$(trim_whitespace "$current_value")"

  while [[ -z "$current_value" ]]; do
    printf '%s: ' "$prompt_label" >&2
    if ! IFS= read -r current_value; then
      printf 'input cancelled while reading %s\n' "$prompt_label" >&2
      exit 1
    fi
    current_value="$(trim_whitespace "$current_value")"
  done

  printf -v "$variable_name" '%s' "$current_value"
}

find_latest_final_report() {
  # Args: project_root task_group task_id task_type_filter(optional)
  # Echoes: <abs path to latest final-report-*.md>\t<task-type>
  # Returns: 1 if not found.
  #
  # 선택 규칙(mtime 최신, 동률이면 basename)과 산출물 위치 지식은
  # okstra_ctl.paths.RunRef 가 정본이다. 과거 이 함수는 그 규칙을 bash 로
  # 재구현해 python 쪽과 손으로 동기화해야 했다.
  local project_root="$1"
  local task_group="$2"
  local task_id="$3"
  local task_type_filter="$4"

  # task-key 단축키 해소와 오타 진단(실제 task-key 목록 출력)은 이 resolver 가
  # 소유한다. 최신 보고서 선택만 python 정본에 위임한다.
  local task_root=""
  task_root="$(resolve_task_root_for_shortcut "$project_root" "$PROJECT_ID" "$task_group" "$task_id")" || {
    printf 'resume-clarification: unable to resolve task root for the given task-key. See diagnostics above.\n' >&2
    return 1
  }

  local found=""
  found="$(PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - \
             "$task_root" "$task_type_filter" <<'PY'
import sys
from okstra_ctl.paths import RunRef

task_root, task_type_filter = sys.argv[1:3]
types = ((task_type_filter,) if task_type_filter
         else ("error-analysis", "requirements-discovery"))
ref = RunRef.latest_under(task_root, types)
if ref is not None:
    sys.stdout.write(f"{ref.report}\t{ref.task_type}")
PY
)" || return 1

  if [[ -z "$found" ]]; then
    printf 'resume-clarification: no final-report found for task %s:%s under %s/runs\n' \
      "$task_group" "$task_id" "$task_root" >&2
    return 1
  fi
  printf '%s\n' "$found"
}

run_resume_clarification() {
  if [[ "$RENDER_ONLY" == "true" ]]; then
    printf '%s\n' '--resume-clarification is incompatible with --render-only' >&2
    exit 1
  fi
  if [[ -n "${CLARIFICATION_RESPONSE_PATH-}" ]]; then
    printf '%s\n' '--resume-clarification is incompatible with --clarification-response (the resume mode supplies it automatically)' >&2
    exit 1
  fi
  if [[ -n "${APPROVED_PLAN_PATH-}" ]]; then
    printf '%s\n' '--resume-clarification is incompatible with --approved-plan' >&2
    exit 1
  fi

  if [[ -z "$PROJECT_ID" || -z "$TASK_GROUP" || -z "$TASK_ID" ]]; then
    printf 'resume-clarification: --project-id, --task-group, --task-id (or --task-key) are required\n' >&2
    exit 1
  fi

  # 신규 모델: PROJECT_ROOT 는 okstra.sh 진입 시점에 resolve_project_root_strict
  # 로 이미 확정되어 export 되어 있다.
  if [[ -z "${PROJECT_ROOT:-}" || ! -d "${PROJECT_ROOT:-}" ]]; then
    printf 'resume-clarification: PROJECT_ROOT not resolved\n' >&2
    exit 1
  fi
  local resume_project_root="$PROJECT_ROOT"

  local lookup_result=""
  if ! lookup_result="$(find_latest_final_report \
        "$resume_project_root" "$TASK_GROUP" "$TASK_ID" "${TASK_TYPE-}")"; then
    exit 1
  fi
  local report_path="${lookup_result%$'\t'*}"
  local resolved_type="${lookup_result##*$'\t'}"

  # implementation-planning 도 §1 Clarification Items(plan-body 결정: 예 C-205
  # tx-배관 step 누락)를 가지므로, 사용자가 결정란을 채운 뒤 같은 phase 를
  # clarification-response 자동공급으로 재실행해 plan 을 재생성·gate 재판정할 수
  # 있다. 자동검색(task_type_filter 없음)은 phase 디렉터리 전반에서 mtime 최신
  # 리포트를 고르므로 어느 phase 가 뽑힐지 보장되지 않는다. 따라서 이 경로는
  # --task-type implementation-planning 을 명시했을 때만 결정적으로 해소된다.
  case "$resolved_type" in
    requirements-discovery|error-analysis|implementation-planning) ;;
    *)
      printf '%s (resolved task-type: %s)\n' '--resume-clarification only applies to requirements-discovery, error-analysis, or implementation-planning runs' "$resolved_type" >&2
      exit 1
      ;;
  esac

  local resume_task_root=""
  resume_task_root="$(resolve_task_root_for_shortcut "$resume_project_root" "$PROJECT_ID" "$TASK_GROUP" "$TASK_ID")" || {
    printf 'resume-clarification: unable to resolve task root for the given task-key. See diagnostics above.\n' >&2
    exit 1
  }
  local manifest_file="$resume_task_root/task-manifest.json"
  if [[ ! -f "$manifest_file" ]]; then
    printf 'resume-clarification: task-manifest.json not found: %s\n' "$manifest_file" >&2
    exit 1
  fi
  local resume_brief_rel=""
  resume_brief_rel="$(python3 - "$manifest_file" <<'PY'
import json, sys
data = json.loads(open(sys.argv[1], "r", encoding="utf-8").read())
print((data.get("taskBriefPath") or "").strip())
PY
)"
  if [[ -z "$resume_brief_rel" ]]; then
    printf 'resume-clarification: manifest missing taskBriefPath: %s\n' "$manifest_file" >&2
    exit 1
  fi

  # 직전 run 의 워커/모델/directive/related-tasks 를 그대로 이어 실행한다 — resume 가
  # 이 설정들을 안 넘기면 매 단계 재입력이 아니라 Python phase 기본값으로 조용히
  # 되돌아가 버린다. run-inputs 의 위치/구조 지식은 okstra_ctl.run_context 가 SSOT.
  local resume_extra_args=()
  while IFS= read -r -d '' _arg; do
    resume_extra_args+=("$_arg")
  done < <(PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - \
             "$PROJECT_ROOT" "$TASK_GROUP" "$TASK_ID" "$resolved_type" <<'PY'
import sys
from okstra_ctl.run_context import latest_run_inputs
from okstra_ctl.ids import slugify_task_segment

project_root, task_group, task_id, task_type = sys.argv[1:5]
inputs = latest_run_inputs(
    project_root, task_group, task_id,
    phase_segment=slugify_task_segment(task_type))


def emit(flag, value):
    if value:
        sys.stdout.write(flag + "\0" + value + "\0")


def model(value):
    # 직전 run 의 모델 display 값. "default"/빈 값은 phase 기본값을 뜻하므로 넘기지
    # 않는다(명시 플래그로 고정하면 안 됨).
    return value if isinstance(value, str) and value not in ("", "default") else ""


workers = inputs.get("workers")
if isinstance(workers, list):
    workers = ",".join(w for w in workers if isinstance(w, str))
elif not isinstance(workers, str):
    workers = ""
emit("--workers", workers)
emit("--lead-model", model(inputs.get("leadModel")))
emit("--claude-model", model(inputs.get("claudeModel")))
emit("--codex-model", model(inputs.get("codexModel")))
emit("--antigravity-model", model(inputs.get("antigravityModel")))
emit("--report-writer-model", model(inputs.get("reportWriterModel")))
directive = inputs.get("directive")
emit("--directive", directive if isinstance(directive, str) else "")
related = inputs.get("relatedTasks")
emit("--related-tasks", related if isinstance(related, str) else "")
PY
)

  if [[ "${OKSTRA_RESUME_CLARIFICATION_DRY_RUN:-false}" == "true" ]]; then
    printf 'resume-clarification (dry-run):\n' >&2
    printf '  report: %s\n' "$report_path" >&2
    printf '  re-exec: bash %s --task-type %s --project-id %s --task-group %s --task-id %s --task-brief %s --clarification-response %s' \
      "$0" "$resolved_type" "$PROJECT_ID" "$TASK_GROUP" "$TASK_ID" "$resume_brief_rel" "$report_path" >&2
    if (( ${#resume_extra_args[@]} > 0 )); then
      printf ' %s' "${resume_extra_args[@]}" >&2
    fi
    printf '\n' >&2
    return 0
  fi

  local editor_cmd="${EDITOR:-vi}"
  printf 'resume-clarification: opening %s with %s\n' "$report_path" "$editor_cmd" >&2
  if ! "$editor_cmd" "$report_path"; then
    printf 'resume-clarification: editor exited non-zero; aborting\n' >&2
    exit 1
  fi

  printf 'resume-clarification: re-running okstra with --clarification-response\n' >&2
  exec bash "$0" \
    --task-type "$resolved_type" \
    --project-id "$PROJECT_ID" \
    --task-group "$TASK_GROUP" \
    --task-id "$TASK_ID" \
    --task-brief "$resume_brief_rel" \
    --clarification-response "$report_path" \
    ${resume_extra_args[@]+"${resume_extra_args[@]}"}
}
