#!/usr/bin/env bash
# okstra 중앙 컨트롤 센터의 write-side 헬퍼 모음.
# okstra.sh 와 okstra-ctl.sh 양쪽에서 source 하여 사용한다.

# 중앙 디렉터리 위치를 해석한다. OKSTRA_HOME 이 설정돼 있으면 그 값을, 아니면 $HOME/.okstra 를 사용한다.
okstra_central_home() {
  if [[ -n "${OKSTRA_HOME:-}" ]]; then
    printf '%s\n' "$OKSTRA_HOME"
  else
    printf '%s\n' "$HOME/.okstra"
  fi
}

# 중앙 디렉터리와 필수 파일을 생성한다. 멱등성이 보장된다.
okstra_central_bootstrap() {
  local home
  home="$(okstra_central_home)"
  python3 - "$home" <<'PY'
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

home = Path(sys.argv[1])
# OKSTRA_HOME 은 사용자 지정 override 가 가능하며 ($XDG_STATE_HOME/okstra
# 처럼) 부모 디렉터리가 없는 nested 경로일 수 있다. parents=False 로
# mkdir 하면 모든 okstra-ctl 진입점과 record_start hook 이 bootstrap 단계
# 에서 FileNotFoundError 로 abort 하므로, 부모까지 함께 생성한다.
home.mkdir(mode=0o700, parents=True, exist_ok=True)
os.chmod(home, 0o700)
for sub in ("archive", ".locks", "batches", "projects"):
    (home / sub).mkdir(exist_ok=True)
for f in ("active.jsonl", "recent.jsonl"):
    (home / f).touch(exist_ok=True)
state_file = home / "state.json"
if not state_file.exists():
    payload = {
        "schemaVersion": "1",
        "createdAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "backfilledAt": None,
    }
    state_file.write_text(json.dumps(payload, indent=2) + "\n")
    os.chmod(state_file, 0o600)
PY
}

# 인자로 받은 명령을 ~/.okstra/.lock 위에 fcntl LOCK_EX 로 직렬화해 실행한다.
# 사용법: okstra_central_with_lock "<bash command string>"
# 주: macOS 는 util-linux 의 `flock` 바이너리를 제공하지 않아 python fcntl 을 사용한다.
okstra_central_with_lock() {
  local home cmd lockfile
  home="$(okstra_central_home)"
  cmd="$1"
  lockfile="$home/.lock"
  : >> "$lockfile"
  python3 - "$lockfile" "$cmd" <<'PY'
import fcntl, subprocess, sys
lockpath, cmd = sys.argv[1], sys.argv[2]
with open(lockpath, "r+") as lock:
    fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
    rc = subprocess.run(["bash", "-c", cmd]).returncode
sys.exit(rc)
PY
}

# okstra.sh 에서 호출되는 메인 hook. 환경변수에서 메타를 읽어
# active.jsonl, projects/<id>/index.jsonl, invocation, meta.json 을
# 단일 락 트랜잭션으로 갱신한다.
#
# 이전 구현은 OKSTRA_HOME / OKSTRA_CTL_LIB_DIR 값을 OKSTRA_HOME='$home'
# 형태로 bash -c 문자열에 박아 okstra_central_with_lock 에 넘겼는데,
# OKSTRA_HOME 이나 체크아웃 경로에 single quote 가 포함되면 syntax error
# 로 hook 이 silently 실패해 해당 run 이 중앙 인덱스에 남지 않았다.
# 이를 피하기 위해 값은 환경변수로 전달하고, 락은 python 안에서 직접
# fcntl.flock 으로 잡는다(okstra_central_with_lock 와 동일 lockfile).
okstra_central_record_start() {
  local home script_dir payload_json
  home="$(okstra_central_home)"
  script_dir="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  # 빈 default 일 때 JSON 호환 문자열로 미리 정규화한다.
  local _argv_json="${OKSTRA_INVOCATION_ARGV_JSON-}"
  [[ -z "$_argv_json" ]] && _argv_json='[]'
  local _env_json="${OKSTRA_INVOCATION_ENV_JSON-}"
  [[ -z "$_env_json" ]] && _env_json='{}'
  local _initial_status="${OKSTRA_INITIAL_STATUS-}"
  [[ -z "$_initial_status" ]] && _initial_status='running'
  local _run_seq="${OKSTRA_RUN_SEQ-}"
  [[ -z "$_run_seq" ]] && _run_seq='0'
  # Build the entire payload as a JSON string in bash (no env-var passthrough),
  # then hand it to python via argv so this hook is callable inside any
  # claude-session subprocess without leaking shell-globals into the parent
  # process environment.
  payload_json="$(python3 -c '
import json, sys
it = iter(sys.argv[1:])
print(json.dumps({k: v for k, v in zip(it, it)}, ensure_ascii=False))
' \
    OKSTRA_CTL_LIB_DIR "$script_dir" \
    OKSTRA_HOME "$home" \
    PROJECT_ID "${PROJECT_ID-}" \
    PROJECT_ROOT "${PROJECT_ROOT-}" \
    TASK_GROUP "${TASK_GROUP-}" \
    TASK_ID "${TASK_ID-}" \
    TASK_TYPE "${TASK_TYPE-}" \
    OKSTRA_RUN_SEQ "$_run_seq" \
    RUN_TIMESTAMP_ISO "${RUN_TIMESTAMP_ISO-}" \
    RECOMMENDED_ANALYSERS "${RECOMMENDED_ANALYSERS-}" \
    LEAD_MODEL "${LEAD_MODEL-}" \
    RUN_DIR_RELATIVE_PATH "${RUN_DIR_RELATIVE_PATH-}" \
    FINAL_REPORT_RECORD_RELATIVE_PATH "${FINAL_REPORT_RECORD_RELATIVE_PATH-}" \
    FINAL_STATUS_RELATIVE_PATH "${FINAL_STATUS_RELATIVE_PATH-}" \
    OKSTRA_INVOCATION_ARGV_JSON "$_argv_json" \
    OKSTRA_INVOCATION_CWD "${OKSTRA_INVOCATION_CWD-}" \
    OKSTRA_INVOCATION_ENV_JSON "$_env_json" \
    OKSTRA_INITIAL_STATUS "$_initial_status" \
    OKSTRA_INVOCATION_BRIEF_SHA256 "${OKSTRA_INVOCATION_BRIEF_SHA256-}")"
  python3 - "$payload_json" <<'PY'
import fcntl, json, sys
payload = json.loads(sys.argv[1])
sys.path.insert(0, payload["OKSTRA_CTL_LIB_DIR"])
from pathlib import Path
from okstra_ctl import record_start

home = Path(payload["OKSTRA_HOME"])
lockfile = home / ".lock"
home.mkdir(parents=True, exist_ok=True)
lockfile.touch()
with lockfile.open("r+") as lock:
    fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
    record_start(
        home,
        project_id=payload["PROJECT_ID"],
        project_root=payload["PROJECT_ROOT"],
        task_group=payload["TASK_GROUP"],
        task_id=payload["TASK_ID"],
        task_type=payload.get("TASK_TYPE", ""),
        run_seq=int(payload["OKSTRA_RUN_SEQ"]),
        when=payload["RUN_TIMESTAMP_ISO"],
        workers=[w for w in payload.get("RECOMMENDED_ANALYSERS", "").split(",") if w],
        lead_model=payload.get("LEAD_MODEL", ""),
        run_dir_rel=payload.get("RUN_DIR_RELATIVE_PATH", ""),
        final_report_record_rel=payload.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
        final_status_rel=payload.get("FINAL_STATUS_RELATIVE_PATH", ""),
        argv=json.loads(payload.get("OKSTRA_INVOCATION_ARGV_JSON", "[]")),
        cwd=payload.get("OKSTRA_INVOCATION_CWD", ""),
        env_overrides=json.loads(payload.get("OKSTRA_INVOCATION_ENV_JSON", "{}") or "{}"),
        initial_status=payload.get("OKSTRA_INITIAL_STATUS", "running"),
        brief_sha256=payload.get("OKSTRA_INVOCATION_BRIEF_SHA256", ""),
    )
PY
}
