#!/usr/bin/env bash

# okstra.sh — thin wrapper around python `okstra_ctl.run.prepare_task_bundle`.
#
# 책임 분할:
#   - 이 bash 파일: CLI 인자 파싱 / interactive prompt / confirm-execution-plan /
#     resume-clarification 모드 / 최종 `exec claude` 만 담당. 산출물 생성 로직
#     (path 계산, render, manifest, central record_start) 은 일절 보유하지 않는다.
#   - python `okstra_ctl.run.prepare_task_bundle()`: identity / brief / 모델 /
#     워커 / phase 규칙 / 9개 render / record_start 까지의 단일 진입점.
#
# 같은 prepare 함수를 `okstra-run` skill 도 직접 호출하므로 두 caller 가 공유한
# 단일 권위가 보존된다 (rule-of-two).

set -euo pipefail

SOURCE_PATH="${BASH_SOURCE[0]}"
while [[ -L "$SOURCE_PATH" ]]; do
  SOURCE_DIR="$(cd -P "$(dirname "$SOURCE_PATH")" && pwd)"
  SOURCE_PATH="$(readlink "$SOURCE_PATH")"
  [[ "$SOURCE_PATH" != /* ]] && SOURCE_PATH="$SOURCE_DIR/$SOURCE_PATH"
done

SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE_PATH")" && pwd)"
WORKSPACE_ROOT="$(cd -P "$SCRIPT_DIR/.." && pwd)"

# CLI parsing + interactive prompt 만 source. 나머지 lib/okstra/*.sh 는 더 이상
# 직접 호출되지 않는다 (python 모듈이 권위).
# shellcheck disable=SC1090
source "$SCRIPT_DIR/lib/okstra/globals.sh"
# shellcheck disable=SC1090
source "$SCRIPT_DIR/lib/okstra/usage.sh"
# shellcheck disable=SC1090
source "$SCRIPT_DIR/lib/okstra/cli.sh"
# shellcheck disable=SC1090
source "$SCRIPT_DIR/lib/okstra/interactive.sh"
# shellcheck disable=SC1090
source "$SCRIPT_DIR/lib/okstra/project-resolver.sh"

# The python packages live at `scripts/` in the repo and at `lib/python/` once
# installed, and this script runs from both. Assuming the repo layout made the
# installed launcher die on its first import; that went unnoticed because
# in-host runs reach python through the Node CLI instead of through here.
OKSTRA_PYTHONPATH=""
for candidate in "scripts" "python" "lib/python"; do
  if [[ -d "$WORKSPACE_ROOT/$candidate/okstra_project" ]]; then
    OKSTRA_PYTHONPATH="$WORKSPACE_ROOT/$candidate"
    break
  fi
done
if [[ -z "$OKSTRA_PYTHONPATH" ]]; then
  printf 'okstra: python package not found under %s — run '"'"'okstra install'"'"'\n' \
    "$WORKSPACE_ROOT" >&2
  exit 1
fi

okstra_py() {
  PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 "$@"
}

confirm_sandbox_waiver() {
  local sandbox_note="$1"
  local lead_executable="$2"
  local cancel_message="$3"
  local assume_yes_message="$4"
  local sandbox_answer

  [[ -z "$sandbox_note" ]] && return
  printf '\n%s\n' "$sandbox_note" >&2
  if [[ "$ASSUME_YES" == "true" ]]; then
    [[ -n "$assume_yes_message" ]] && printf '%s\n\n' "$assume_yes_message" >&2
    return 0
  fi
  printf 'Start %s without its sandbox? [y/N] ' "$lead_executable" >&2
  read -r sandbox_answer < /dev/tty || sandbox_answer=""
  if [[ "$sandbox_answer" != "y" && "$sandbox_answer" != "Y" ]]; then
    printf '%s\n' "$cancel_message" >&2
    exit 1
  fi
}

execute_launch_plan() {
  local cancel_message="$1"
  local assume_yes_message="$2"
  shift 2
  local launch_plan_file
  local sandbox_note
  local env_count
  local argv_offset
  local lead_executable
  local field
  local -a launch_fields
  local -a launch_env
  local -a launch_argv

  launch_plan_file="$(mktemp "${TMPDIR:-/tmp}/okstra-launch-plan.XXXXXX")"
  if ! okstra_py -m okstra_ctl.entrypoints.hosts "$@" \
    --format null > "$launch_plan_file"; then
    rm -f "$launch_plan_file"
    exit 1
  fi
  launch_fields=()
  while IFS= read -r -d '' field; do
    launch_fields+=("$field")
  done < "$launch_plan_file"
  rm -f "$launch_plan_file"
  if [[ ${#launch_fields[@]} -lt 4 ]] || \
    [[ ! "${launch_fields[2]}" =~ ^[0-9]+$ ]]; then
    printf 'okstra: invalid launch plan\n' >&2
    exit 1
  fi
  sandbox_note="${launch_fields[1]}"
  env_count="${launch_fields[2]}"
  argv_offset=$((env_count + 3))
  if [[ ${#launch_fields[@]} -le $argv_offset ]]; then
    printf 'okstra: invalid launch plan\n' >&2
    exit 1
  fi
  launch_env=("${launch_fields[@]:3:env_count}")
  launch_argv=("${launch_fields[@]:argv_offset}")
  lead_executable="${launch_argv[0]}"
  confirm_sandbox_waiver \
    "$sandbox_note" "$lead_executable" "$cancel_message" "$assume_yes_message"
  cd "${launch_fields[0]}"
  exec env "${launch_env[@]}" "${launch_argv[@]}"
}

parse_cli_arguments "$@"
split_task_key

PROJECT_ROOT="$(resolve_project_root_safe "$PROJECT_ROOT_OVERRIDE")"
if [[ "$RESUME_SESSION_MODE" == "true" ]]; then
  if [[ -z "$PROJECT_ROOT" ]]; then
    PROJECT_ROOT="$(resolve_project_root_strict "$PROJECT_ROOT_OVERRIDE")" || exit 1
  fi
  execute_launch_plan \
    "okstra: resume cancelled" \
    "" \
    resume-plan --host "$LEAD_RUNTIME" --entry-mode spawn-process \
    --project-root "$PROJECT_ROOT" --session-id "$RESUME_SESSION_ID"
fi
if [[ "$RESUME_CLARIFICATION_MODE" == "true" ]]; then
  if [[ -z "$PROJECT_ROOT" ]]; then
    PROJECT_ROOT="$(resolve_project_root_strict "$PROJECT_ROOT_OVERRIDE")" \
      || { printf 'resume-clarification: %s\n' "$PROJECT_ROOT" >&2; exit 1; }
  fi
  run_resume_clarification
  exit 0
fi
autofill_from_project_json

# `okstra run <lead>` — open the host, nothing else. The okstra-run skill inside
# it collects task inputs, which is how the Claude Code path has always worked.
# Preparing a bundle here would demand a task id before the user has a session
# to choose one in.
if [[ "$LAUNCH_ONLY" == "true" ]]; then
  if [[ -z "$PROJECT_ROOT" ]]; then
    PROJECT_ROOT="$(resolve_project_root_strict "$PROJECT_ROOT_OVERRIDE")" || exit 1
  fi
  execute_launch_plan \
    "okstra: cancelled" \
    "" \
    launch-plan --host "$LEAD_RUNTIME" --entry-mode spawn-process \
    --project-root "$PROJECT_ROOT"
fi

autofill_from_manifest
collect_required_arguments

if [[ -z "$PROJECT_ROOT" ]]; then
  if ! PROJECT_ROOT="$(resolve_project_root_strict "$PROJECT_ROOT_OVERRIDE")"; then
    exit 1
  fi
fi

# Interactive 모드의 confirm prompt. 입력값만 보여주고 진행 여부를 묻는다 —
# 산출물 경로 계산은 python 으로 미루므로 이 단계에서 디스크에 쓰지 않는다.
if [[ "$ASSUME_YES" != "true" ]] && [[ -t 0 ]] && [[ -t 1 ]]; then
  cat >&2 <<CONFIRM_EOF
okstra execution summary:
  render only: ${RENDER_ONLY}
  task type: ${TASK_TYPE}
  project id: ${PROJECT_ID}
  project root: ${PROJECT_ROOT}
  task group: ${TASK_GROUP}
  task id: ${TASK_ID}
  task brief: ${BRIEF_PATH}
  directive: ${DIRECTIVE:-None}
  clarification response: ${CLARIFICATION_RESPONSE_PATH:-None}
  selected direction: ${SELECTED_DIRECTION_PATH:-None}
  workers override: ${WORKERS_OVERRIDE:-None}
  executor (implementation only): ${EXECUTOR_OVERRIDE:-default(claude)}
  approved plan: ${APPROVED_PLAN_PATH:-None}
  approve ack (CLI 승인 의사): ${APPROVE_PLAN_ACK}
  implementation option (선택 옵션): ${IMPLEMENTATION_OPTION:-None(fallback to Recommended Option)}
  related tasks: ${RELATED_TASKS_RAW:-None}
CONFIRM_EOF
  printf 'Continue? [y/yes]: ' >&2
  if ! IFS= read -r _confirmation; then
    printf 'confirmation cancelled\n' >&2
    exit 1
  fi
  _confirmation="$(printf '%s' "$_confirmation" | tr '[:upper:]' '[:lower:]')"
  _confirmation="${_confirmation#"${_confirmation%%[![:space:]]*}"}"
  _confirmation="${_confirmation%"${_confirmation##*[![:space:]]}"}"
  if [[ "$_confirmation" != "y" && "$_confirmation" != "yes" ]]; then
    printf 'okstra cancelled before execution\n' >&2
    exit 1
  fi
fi

# Build python argv and invoke prepare_task_bundle.
PY_ARGS=(
  --workspace-root "$WORKSPACE_ROOT"
  --project-root "$PROJECT_ROOT"
  --project-id "$PROJECT_ID"
  --task-group "$TASK_GROUP"
  --task-id "$TASK_ID"
  --task-type "$TASK_TYPE"
  --task-brief "$BRIEF_PATH"
)
[[ -n "${DIRECTIVE-}" ]] && PY_ARGS+=(--directive "$DIRECTIVE")
[[ -n "${FIX_CYCLE-}" ]] && PY_ARGS+=(--fix-cycle "$FIX_CYCLE")
[[ -n "${WORKERS_OVERRIDE-}" ]] && PY_ARGS+=(--workers "$WORKERS_OVERRIDE")
for role_count in "${ROLE_COUNTS[@]-}"; do
  [[ -z "$role_count" ]] && continue
  PY_ARGS+=(--role-count "$role_count")
done
for role_model in "${ROLE_MODELS[@]-}"; do
  [[ -z "$role_model" ]] && continue
  PY_ARGS+=(--role-model "$role_model")
done
[[ -n "${HOST_SESSION_CONTEXT_JSON-}" ]] && \
  PY_ARGS+=(--host-session-context-json "$HOST_SESSION_CONTEXT_JSON")
[[ -n "${LEAD_PROVIDER_OVERRIDE-}" ]] && PY_ARGS+=(--lead-provider "$LEAD_PROVIDER_OVERRIDE")
[[ -n "${LEAD_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--lead-model "$LEAD_MODEL_OVERRIDE")
[[ -n "${CLAUDE_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--claude-model "$CLAUDE_MODEL_OVERRIDE")
[[ -n "${CODEX_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--codex-model "$CODEX_MODEL_OVERRIDE")
[[ -n "${ANTIGRAVITY_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--antigravity-model "$ANTIGRAVITY_MODEL_OVERRIDE")
[[ -n "${WORKER_MODELS_OVERRIDE-}" ]] && PY_ARGS+=(--worker-model "$WORKER_MODELS_OVERRIDE")
[[ -n "${REPORT_WRITER_PROVIDER_OVERRIDE-}" ]] && PY_ARGS+=(--report-writer-provider "$REPORT_WRITER_PROVIDER_OVERRIDE")
[[ -n "${REPORT_WRITER_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--report-writer-model "$REPORT_WRITER_MODEL_OVERRIDE")
[[ -n "${LEAD_RUNTIME-}" ]] && PY_ARGS+=(--lead-runtime "$LEAD_RUNTIME")
[[ -n "${EXECUTOR_OVERRIDE-}" ]] && PY_ARGS+=(--executor "$EXECUTOR_OVERRIDE")
[[ -n "${CRITIC_CHOICE-}" ]] && PY_ARGS+=(--critic "$CRITIC_CHOICE")
[[ -n "${RELATED_TASKS_RAW-}" ]] && PY_ARGS+=(--related-tasks "$RELATED_TASKS_RAW")
[[ -n "${APPROVED_PLAN_PATH-}" ]] && PY_ARGS+=(--approved-plan "$APPROVED_PLAN_PATH")
[[ "$APPROVE_PLAN_ACK" == "true" ]] && PY_ARGS+=(--approve)
[[ -n "${IMPLEMENTATION_OPTION-}" ]] && PY_ARGS+=(--implementation-option "$IMPLEMENTATION_OPTION")
[[ -n "${CLARIFICATION_RESPONSE_PATH-}" ]] && PY_ARGS+=(--clarification-response "$CLARIFICATION_RESPONSE_PATH")
[[ -n "${SELECTED_DIRECTION_PATH-}" ]] && PY_ARGS+=(--selected-direction "$SELECTED_DIRECTION_PATH")
[[ -n "${WORK_CATEGORY-}" ]] && PY_ARGS+=(--work-category "$WORK_CATEGORY")
[[ -n "${BASE_REF-}" ]] && PY_ARGS+=(--base-ref "$BASE_REF")
[[ -n "${STAGE-}" ]] && PY_ARGS+=(--stage "$STAGE")
[[ -n "${STAGES-}" ]] && PY_ARGS+=(--stages "$STAGES")
[[ -n "${QA_WAIVER-}" ]] && PY_ARGS+=(--qa-waiver "$QA_WAIVER")
[[ "$RENDER_ONLY" == "true" ]] && PY_ARGS+=(--render-only)
[[ "$PLAN_VERIFICATION_ENABLED" == "false" ]] && PY_ARGS+=(--no-plan-verification)

if [[ "$RENDER_ONLY" == "true" ]]; then
  okstra_py -m okstra_ctl.run "${PY_ARGS[@]}"
  exit 0
fi

# Non-render-only: capture summary + launch JSON, then exec claude.
# `set -e` aborts on a failing command-substitution assignment, which would
# skip the PY_RC branch below and swallow any stdout the prepare step buffered.
# Guard the assignment so the explicit return-code check can surface it.
PY_RC=0
PREPARE_OUTPUT="$(okstra_py -m okstra_ctl.run "${PY_ARGS[@]}")" || PY_RC=$?
if [[ $PY_RC -ne 0 ]]; then
  printf '%s\n' "$PREPARE_OUTPUT"
  exit $PY_RC
fi

# Display summary lines to user (everything except the machine-readable line).
printf '%s\n' "$PREPARE_OUTPUT" | grep -v '^__OKSTRA_LAUNCH__ '

# Parse launch metadata. Everything provider-specific — which executable, which
# flags, whether a sandbox must be waived — is decided by the model catalog and
# arrives already assembled, so this wrapper stays a thin adapter.
LAUNCH_JSON="$(printf '%s\n' "$PREPARE_OUTPUT" | sed -n 's/^__OKSTRA_LAUNCH__ //p' | tail -n1)"
if [[ -z "$LAUNCH_JSON" ]]; then
  printf 'okstra: prepare_task_bundle did not emit launch metadata\n' >&2
  exit 1
fi

# Note: per-session --settings injection was removed. okstra-ctl prepare
# provisions <PROJECT_ROOT>/.claude/settings.local.json as a symlink to
# ~/.okstra/templates/settings.local.json, which Claude Code auto-loads
# whenever it runs inside that project — no CLI flag required.
execute_launch_plan \
  "okstra: cancelled. The task bundle is prepared; rerun to resume it." \
  "Proceeding without the sandbox (--yes)." \
  launch-plan --host "$LEAD_RUNTIME" --entry-mode spawn-process \
  --prepared-launch-json "$LAUNCH_JSON"
