#!/usr/bin/env bash
# Global factory entrypoint: run a vendored ADW against the current git repo.
set -euo pipefail

SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
FACTORY_ROOT="${FACTORY_ROOT:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
DEFAULT_CONFIG="${FACTORY_ROOT}/sssf.config.yaml"
# Survives the exec into `uv run python`, which leaves no ancestor named "factory" for the
# session ledger to attribute an agent launch to.
export FACTORY_ADW=1

usage() {
  cat <<'EOF'
Usage: factory [--repo PATH] [--config PATH] [--preset NAME] [--account SLUG] <ADW-or-command> [args...]
       factory presets
       factory kubernetes [--repo PATH] <ADW> [args...]
       factory stop <adw_id> [--repo PATH] [--config PATH]
       factory reconcile [--repo PATH] [--config PATH]
       factory decisions [--adw ID] [--json] [--repo PATH] [--config PATH]
       factory answer <decision_id> (--choice VALUE | --text TEXT) [--repo PATH] [--config PATH]
       factory watch [ADW_ID] [--repo PATH] [--config PATH] [--follow|--no-follow] [--since-start] [--json] [--poll-ms N]

Runs a vendored SSSF ADW against a git repository. Resolves the target repo from
the current directory (or --repo), selects config from --config, <repo>/.factory/
sssf.config.yaml when present, or the overdeck default config. --preset selects a named
seat arrangement; --account selects an isolated pi account (and overrides a preset account).

Control commands use the same config resolution and global trace database.

Examples:
  factory presets
  factory --preset fast prompt "summarize this repo"
  factory --account multideal prompt "summarize this repo"
  factory prompt "summarize this repo"
  factory scout "where is auth handled?"
  factory --repo /path/to/repo plan "add a health endpoint"
  factory stop a1b2c3d4
  factory reconcile
  factory watch --since-start
EOF
}

die() {
  echo "factory: $1" >&2
  exit "${2:-2}"
}

canonical_path() {
  local target="$1"
  if [[ ! -e "$target" && ! -L "$target" ]]; then
    die "no such path: $target"
  fi
  readlink -f "$target"
}

resolve_repo_root() {
  local start="$1" root=""
  if ! root="$(git -C "$start" rev-parse --show-toplevel 2>/dev/null)"; then
    die "not a git repository: $start
hint: run from inside a git work tree or pass --repo PATH to an initialized repo"
  fi
  canonical_path "$root"
}

resolve_config_path() {
  local repo_root="$1" explicit="$2" candidate=""
  if [[ -n "$explicit" ]]; then
    candidate="$(canonical_path "$explicit")"
    [[ -f "$candidate" ]] || die "config not found: $candidate"
    printf '%s\n' "$candidate"
    return 0
  fi

  candidate="${repo_root}/.factory/sssf.config.yaml"
  if [[ -f "$candidate" ]]; then
    canonical_path "$candidate"
    return 0
  fi

  candidate="$DEFAULT_CONFIG"
  [[ -f "$candidate" ]] || die "default config missing: $candidate"
  canonical_path "$candidate"
}

config_escapes_allowed_roots() {
  local config_path="$1" repo_root="$2"
  local default_root repo_factory
  default_root="$(canonical_path "$FACTORY_ROOT")"
  if [[ "$config_path" == "$default_root/"* || "$config_path" == "$default_root" ]]; then
    return 0
  fi
  repo_factory="${repo_root}/.factory"
  [[ -e "$repo_factory" || -L "$repo_factory" ]] || return 1
  repo_factory="$(canonical_path "$repo_factory")"
  [[ "$config_path" == "$repo_factory/"* || "$config_path" == "$repo_factory" ]]
}

validate_config_path() {
  local config_path="$1" repo_root="$2" explicit="$3"
  [[ -f "$config_path" ]] || die "config not found: $config_path"
  if [[ -z "$explicit" ]] && ! config_escapes_allowed_roots "$config_path" "$repo_root"; then
    die "config resolves outside allowed roots: $config_path
hint: project config must live under ${repo_root}/.factory/; defaults under ${FACTORY_ROOT}/"
  fi
}

adw_accepts_config() {
  local adw_path="$1"
  grep -Eq 'add_argument\([[:space:]]*["'"'"']--config["'"'"']' "$adw_path"
}

canonical_project_factory_root() {
  local repo_root="$1" repo_factory="${repo_root}/.factory"
  [[ -e "$repo_factory" || -L "$repo_factory" ]] || return 1
  canonical_path "$repo_factory"
}

validate_project_adw_path() {
  local adw_path="$1" repo_root="$2" repo_factory=""
  repo_factory="$(canonical_project_factory_root "$repo_root")" \
    || die "project ADW requires ${repo_root}/.factory/"
  adw_path="$(canonical_path "$adw_path")"
  if [[ "$adw_path" != "$repo_factory/"* ]]; then
    die "project ADW resolves outside ${repo_root}/.factory/: $adw_path
hint: project ADWs must live under .factory/ and cannot escape via symlinks"
  fi
  [[ -f "$adw_path" ]] || die "ADW not found: $adw_path"
  printf '%s\n' "$adw_path"
}

project_adw_basename() {
  local token="$1"
  case "$token" in
    adw_*.py) printf '%s\n' "$token" ;;
    adw_*) printf '%s.py\n' "$token" ;;
    *.py) return 1 ;;
    *) printf 'adw_%s.py\n' "$token" ;;
  esac
}

try_resolve_project_adw_path() {
  local repo_root="$1" token="$2" basename="" candidate=""
  basename="$(project_adw_basename "$token")" || return 1
  candidate="${repo_root}/.factory/${basename}"
  [[ -f "$candidate" ]] || return 1
  validate_project_adw_path "$candidate" "$repo_root"
}

resolve_core_adw_path() {
  local token="$1" candidate=""
  case "$token" in
    prompt) candidate="${FACTORY_ROOT}/adw_prompt.py" ;;
    scout) candidate="${FACTORY_ROOT}/adw_scout.py" ;;
    plan) candidate="${FACTORY_ROOT}/adw_plan.py" ;;
    plan-build|plan_build) candidate="${FACTORY_ROOT}/adw_plan_build.py" ;;
    sdlc) candidate="${FACTORY_ROOT}/adw_plan_build_test.py" ;;
    simple-sdlc|simple_sdlc) candidate="${FACTORY_ROOT}/adw_simple_sdlc.py" ;;
    build) candidate="${FACTORY_ROOT}/adw_build.py" ;;
    build-review|build_review) candidate="${FACTORY_ROOT}/adw_build_review.py" ;;
    build-test|build_test) candidate="${FACTORY_ROOT}/adw_build_test.py" ;;
    quality) candidate="${FACTORY_ROOT}/adw_quality.py" ;;
    document|documenter) candidate="${FACTORY_ROOT}/adw_document.py" ;;
    plan-build-test|plan_build_test) candidate="${FACTORY_ROOT}/adw_plan_build_test.py" ;;
    plan-build-test-quality|plan_build_test_quality) candidate="${FACTORY_ROOT}/adw_plan_build_test_quality.py" ;;
    adw_*.py)
      candidate="${FACTORY_ROOT}/${token}"
      ;;
    *.py)
      if [[ -f "${FACTORY_ROOT}/${token}" ]]; then
        candidate="${FACTORY_ROOT}/${token}"
      elif [[ -f "$token" ]]; then
        candidate="$(canonical_path "$token")"
      else
        candidate="$(canonical_path "$token")"
      fi
      ;;
    adw_*)
      candidate="${FACTORY_ROOT}/${token}.py"
      ;;
    *)
      if [[ -f "${FACTORY_ROOT}/adw_${token}.py" ]]; then
        candidate="${FACTORY_ROOT}/adw_${token}.py"
      elif [[ -f "${FACTORY_ROOT}/${token}.py" ]]; then
        candidate="${FACTORY_ROOT}/${token}.py"
      else
        die "unknown ADW or command: $token
hint: try prompt, scout, plan, sdlc, or an adw_*.py name"
      fi
      ;;
  esac

  [[ -f "$candidate" ]] || die "ADW not found: $candidate"
  printf '%s\n' "$(canonical_path "$candidate")"
}

resolve_adw_path() {
  local repo_root="$1" token="$2" candidate=""
  if try_resolve_project_adw_path "$repo_root" "$token"; then
    return 0
  fi

  case "$token" in
    *.py)
      candidate=""
      if [[ -f "${repo_root}/.factory/${token}" ]]; then
        candidate="$(validate_project_adw_path "${repo_root}/.factory/${token}" "$repo_root")"
        printf '%s\n' "$candidate"
        return 0
      fi
      if [[ -f "$token" ]]; then
        local resolved=""
        resolved="$(canonical_path "$token")"
        if [[ "$resolved" == "${repo_root}/.factory/"* ]]; then
          candidate="$(validate_project_adw_path "$resolved" "$repo_root")"
          printf '%s\n' "$candidate"
          return 0
        fi
      fi
      ;;
  esac

  resolve_core_adw_path "$token"
}

run_control() {
  local subcmd="$1"
  shift

  local repo_flag="" config_flag="" preset_flag="" adw_id="" decision_id=""
  local -a extra_argv=()

  if [[ "$subcmd" == "stop" ]]; then
    [[ $# -gt 0 ]] || die "stop requires adw_id"
    adw_id="$1"
    shift
  elif [[ "$subcmd" == "answer" ]]; then
    [[ $# -gt 0 ]] || die "answer requires decision_id"
    decision_id="$1"
    shift
  elif [[ "$subcmd" == "watch" && $# -gt 0 && "$1" != -* ]]; then
    adw_id="$1"
    shift
  fi

  while [[ $# -gt 0 ]]; do
    case "$1" in
      -h|--help)
        if [[ "$subcmd" == "watch" ]]; then
          extra_argv+=("$1")
          shift
          continue
        fi
        usage
        exit 0
        ;;
      --repo)
        [[ $# -ge 2 ]] || die "--repo requires a path"
        repo_flag="$2"
        shift 2
        ;;
      --repo=*)
        repo_flag="${1#--repo=}"
        [[ -n "$repo_flag" ]] || die "--repo requires a path"
        shift
        ;;
      --config)
        [[ $# -ge 2 ]] || die "--config requires a path"
        config_flag="$2"
        shift 2
        ;;
      --config=*)
        config_flag="${1#--config=}"
        [[ -n "$config_flag" ]] || die "--config requires a path"
        shift
        ;;
      --preset)
        [[ $# -ge 2 ]] || die "--preset requires a name"
        preset_flag="$2"; shift 2 ;;
      --preset=*)
        preset_flag="${1#--preset=}"; [[ -n "$preset_flag" ]] || die "--preset requires a name"; shift ;;
      --adw)
        [[ $# -ge 2 ]] || die "--adw requires a value"
        extra_argv+=("$1" "$2")
        shift 2
        ;;
      --adw=*)
        extra_argv+=("$1")
        shift
        ;;
      --json)
        extra_argv+=("$1")
        shift
        ;;
      --follow|--no-follow|--since-start)
        extra_argv+=("$1")
        shift
        ;;
      --poll-ms)
        [[ $# -ge 2 ]] || die "--poll-ms requires a value"
        extra_argv+=("$1" "$2")
        shift 2
        ;;
      --poll-ms=*)
        extra_argv+=("$1")
        shift
        ;;
      --choice)
        [[ $# -ge 2 ]] || die "--choice requires a value"
        extra_argv+=("$1" "$2")
        shift 2
        ;;
      --choice=*)
        extra_argv+=("$1")
        shift
        ;;
      --text)
        [[ $# -ge 2 ]] || die "--text requires a value"
        extra_argv+=("$1" "$2")
        shift 2
        ;;
      --text=*)
        extra_argv+=("$1")
        shift
        ;;
      --)
        shift
        break
        ;;
      -*)
        die "unknown option: $1"
        ;;
      *)
        die "unexpected argument for factory $subcmd: $1"
        ;;
    esac
  done

  local repo_start="${repo_flag:-$PWD}"
  local repo_root config_path
  repo_root="$(resolve_repo_root "$repo_start")"
  config_path="$(resolve_config_path "$repo_root" "$config_flag")"
  validate_config_path "$config_path" "$repo_root" "$config_flag"

  if ! command -v uv >/dev/null 2>&1; then
    die "uv not found on PATH — install uv to run factory control commands"
  fi

  cd "$repo_root"
  local -a py_argv=("$subcmd")
  if [[ -n "$adw_id" ]]; then
    py_argv+=("$adw_id")
  fi
  if [[ -n "$decision_id" ]]; then
    py_argv+=("$decision_id")
  fi
  if ((${#extra_argv[@]})); then
    py_argv+=("${extra_argv[@]}")
  fi
  py_argv+=(--config "$config_path")
  if [[ -n "$preset_flag" ]]; then py_argv+=(--preset "$preset_flag"); fi

  local module="control"
  [[ "$subcmd" == "watch" ]] && module="watch"
  PYTHONPATH="${FACTORY_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" FACTORY_CONTROL_MODULE="$module" \
    exec uv run --project "$FACTORY_ROOT" python -c 'import importlib, os, sys; main = importlib.import_module("adw_modules." + os.environ["FACTORY_CONTROL_MODULE"]).main; raise SystemExit(main(sys.argv[2:] if sys.argv[1] == "watch" else sys.argv[1:]))' \
    "${py_argv[@]}"
}

if [[ $# -gt 0 && "$1" == "presets" ]]; then
  PYTHONPATH="${FACTORY_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" exec uv run --project "$FACTORY_ROOT" python -c 'from adw_modules.agents import preset_descriptions; print(*preset_descriptions(), sep="\n")'
fi

if [[ $# -gt 0 ]]; then
  case "$1" in
    -h|--help)
      usage
      exit 0
      ;;
    kubernetes)
      shift
      command -v uv >/dev/null 2>&1 || die "uv not found on PATH"
      PYTHONPATH="${FACTORY_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" exec uv run --project "$FACTORY_ROOT" python -m adw_modules.kubernetes_job "$@"
      ;;
    stop|reconcile|decisions|answer|watch)
      run_control "$@"
      ;;
  esac
fi

repo_flag=""
config_flag=""
preset_flag=""
account_flag=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      usage
      exit 0
      ;;
    --repo)
      [[ $# -ge 2 ]] || die "--repo requires a path"
      repo_flag="$2"
      shift 2
      ;;
    --repo=*)
      repo_flag="${1#--repo=}"
      [[ -n "$repo_flag" ]] || die "--repo requires a path"
      shift
      ;;
    --config)
      [[ $# -ge 2 ]] || die "--config requires a path"
      config_flag="$2"
      shift 2
      ;;
    --config=*)
      config_flag="${1#--config=}"
      [[ -n "$config_flag" ]] || die "--config requires a path"
      shift
      ;;
    --preset)
      [[ $# -ge 2 ]] || die "--preset requires a name"
      preset_flag="$2"; shift 2 ;;
    --preset=*)
      preset_flag="${1#--preset=}"; [[ -n "$preset_flag" ]] || die "--preset requires a name"; shift ;;
    --account)
      [[ $# -ge 2 ]] || die "--account requires a slug"
      account_flag="$2"; shift 2 ;;
    --account=*)
      account_flag="${1#--account=}"; [[ -n "$account_flag" ]] || die "--account requires a slug"; shift ;;
    --)
      shift
      break
      ;;
    -*)
      die "unknown option: $1"
      ;;
    *)
      break
      ;;
  esac
done

[[ $# -gt 0 ]] || { usage >&2; exit 2; }

repo_start="${repo_flag:-$PWD}"
repo_root="$(resolve_repo_root "$repo_start")"
config_path="$(resolve_config_path "$repo_root" "$config_flag")"
validate_config_path "$config_path" "$repo_root" "$config_flag"

adw_token="$1"
shift
adw_path="$(resolve_adw_path "$repo_root" "$adw_token")"

if ! command -v uv >/dev/null 2>&1; then
  die "uv not found on PATH — install uv to run factory ADWs"
fi

if [[ $# -gt 0 ]]; then
  export FACTORY_RUN_SLUG_HINT="$1"
fi

launch=(uv run --project "$FACTORY_ROOT" "$adw_path")
if adw_accepts_config "$adw_path"; then
  launch+=(--config "$config_path")
fi
if [[ -n "$preset_flag" ]]; then launch+=(--preset "$preset_flag"); fi
if [[ -n "$account_flag" ]]; then launch+=(--account "$account_flag"); fi
launch+=("$@")

cd "$repo_root"
PYTHONPATH="${FACTORY_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" \
  exec "${launch[@]}"
