#!/usr/bin/env bash
# Resolves the general workflow config and always generates the minimal
# `{input}`-only config used by the `exec` command.
#
# General config (`run`/`plan`/`fix` commands), in priority order:
#   1. `config` input set -- resolved to an absolute path and exported as
#      CRUISE_CONFIG.
#   2. `config` unset, and the checkout root already has a
#      cruise.yaml/cruise.yml/.cruise.yaml/.cruise.yml (or a `.cruise/*.yaml`
#      / `*.yml`) -- CRUISE_CONFIG is left unset entirely and cruise's own
#      resolver picks that file up.
#   3. `config` unset and no repo config exists -- this script generates a
#      default config and exports it as CRUISE_CONFIG. It intentionally does
#      NOT set `model`/`plan_model`: cruise's *built-in* default (used when
#      no config is found at all) hardcodes `model: sonnet` / `plan_model:
#      opus`, which under the `sdk: pi` this action always forces are
#      interpreted as literal pi model-registry ids -- and pi has no id
#      named exactly "sonnet"/"opus" (real ids look like
#      "claude-sonnet-4-6"), so the zero-config path would fail to resolve a
#      model. Leaving them unset lets pi auto-select a provider/model from
#      whichever of ANTHROPIC_API_KEY/OPENAI_API_KEY is present. The `model`/
#      `plan_model` inputs (CRUISE_MODEL/CRUISE_PLAN_MODEL env overrides,
#      applied by cruise itself) still take priority over this generated
#      config when set.
#
# exec config (`exec` command only): `cruise exec` binds the whole plan text
# to {input} and never runs a planning step (plan.md stays empty), so this
# config intentionally has a single step referencing {input} rather than
# {plan}. Generated unconditionally, regardless of the `config` input --
# `exec` always uses this file via an explicit `-c`. Like the generated
# default above, it does not set `model`/`plan_model` either.
set -euo pipefail

CONFIG_INPUT="${CONFIG_INPUT:-}"
WORKSPACE="${GITHUB_WORKSPACE:-$(pwd)}"
OUT_DIR="${RUNNER_TEMP:-/tmp}/cruise"
mkdir -p "$OUT_DIR"

# The action's own checkout (where prompts/*.md live), so the generated
# default config can embed the real prompt files instead of a hand-copied
# duplicate. GITHUB_ACTION_PATH is set automatically by GitHub Actions for
# composite actions; fall back to resolving relative to this script for
# local testing.
resolve_action_root() {
  if [ -n "${GITHUB_ACTION_PATH:-}" ]; then
    printf '%s\n' "$GITHUB_ACTION_PATH"
  else
    (cd "$(dirname "$0")/../.." && pwd)
  fi
}
ACTION_ROOT="$(resolve_action_root)"

# Embeds a prompt file as a YAML block-literal ("|") body indented by $2.
# Reads the file verbatim (no escaping needed inside a block literal) so
# blank lines and any YAML-special characters in the prompt survive
# unchanged; blank lines are emitted with no indentation, which YAML block
# literals accept unconditionally.
embed_prompt_file() { # $1=file $2=indent
  local file="$1" indent="$2"
  if [ ! -f "$file" ]; then
    echo "::error::cruise: prompt file not found at '$file' (GITHUB_ACTION_PATH=${GITHUB_ACTION_PATH:-unset}, ACTION_ROOT=$ACTION_ROOT)" >&2
    exit 1
  fi
  while IFS='' read -r line || [ -n "$line" ]; do
    if [ -n "$line" ]; then
      printf '%s%s\n' "$indent" "$line"
    else
      printf '\n'
    fi
  done < "$file"
}

# True if the checkout already has a config cruise's own resolver would pick
# up on its own (the local-file tiers of resolve_config(): the four
# top-level names, or any YAML file directly under .cruise/).
repo_has_own_config() {
  for name in cruise.yaml cruise.yml .cruise.yaml .cruise.yml; do
    [ -f "$WORKSPACE/$name" ] && return 0
  done
  if [ -d "$WORKSPACE/.cruise" ]; then
    for f in "$WORKSPACE"/.cruise/*.yaml "$WORKSPACE"/.cruise/*.yml; do
      [ -f "$f" ] && return 0
    done
  fi
  return 1
}

config_path=""
if [ -n "$CONFIG_INPUT" ]; then
  case "$CONFIG_INPUT" in
    /*) resolved="$CONFIG_INPUT" ;;
    *) resolved="$WORKSPACE/$CONFIG_INPUT" ;;
  esac
  if [ ! -f "$resolved" ]; then
    echo "::error::cruise config not found at '$resolved' (input 'config' = '$CONFIG_INPUT')" >&2
    exit 1
  fi
  echo "cruise: using user-supplied config at $resolved (sets CRUISE_CONFIG for run/plan/fix)"
  config_path="$resolved"
elif repo_has_own_config; then
  echo "cruise: repository already has its own config -- cruise's own resolver will pick it up (CRUISE_CONFIG left unset)"
else
  config_path="$OUT_DIR/default-config.yaml"
  echo "cruise: no 'config' input and no repository config found -- generating a default at $config_path"
  {
    cat <<'YAML'
# Auto-generated by the cruise GitHub Action: no `config` input was given and
# the repository has no cruise.yaml/cruise.yml/.cruise.yaml/.cruise.yml of
# its own. Mirrors cruise's built-in default workflow (write-tests ->
# implement) under `sdk: pi`, deliberately without `model`/`plan_model` so pi
# auto-selects a provider/model instead of failing to resolve the built-in
# default's literal "sonnet"/"opus" as pi model-registry ids.
sdk: pi

steps:
  write-tests:
    prompt: |
YAML
    embed_prompt_file "$ACTION_ROOT/prompts/write-test-first.md" "      "
    cat <<'YAML'

  implement:
    prompt: |
YAML
    embed_prompt_file "$ACTION_ROOT/prompts/implement-after-tests.md" "      "
  } > "$config_path"
fi

exec_config_path="$OUT_DIR/exec-config.yaml"
cat > "$exec_config_path" <<'YAML'
# Auto-generated by the cruise GitHub Action for the `exec` command.
# `cruise exec` binds the whole plan text to {input} and never runs a
# planning step (plan.md stays empty), so this config has a single step
# referencing {input} rather than {plan}. `sdk: pi` is forced regardless via
# CRUISE_SDK, but is declared here too so the file is self-sufficient.
# `model` is deliberately unset too, for the same reason as the generated
# default config above: it lets pi auto-select instead of failing to
# resolve a hardcoded literal as a pi model-registry id.
sdk: pi

steps:
  implement:
    prompt: "{input}"
YAML

# CRUISE_CONFIG must never be *set to an empty string* -- cruise's resolver
# treats an empty CRUISE_CONFIG as "use this (nonexistent) path" and hard
# errors, which is different from leaving it unset entirely. Only export it
# when a real path was resolved.
if [ -n "$config_path" ]; then
  echo "CRUISE_CONFIG=$config_path" >> "$GITHUB_ENV"
fi

{
  echo "config_path=$config_path"
  echo "exec_config_path=$exec_config_path"
} >> "$GITHUB_OUTPUT"
