#!/bin/bash
set -euo pipefail

die_json() { jq -n --arg d "$1" '{"ok":false,"detail":$d}' >&2; exit 2; }

# resolve-seat.sh - pure function seat resolution per spec
# Usage: resolve-seat.sh --plan <jsonl> --runconfig <json> --node <id>
# Exit 0: { "seat": "coder", "tier": "medium", "wrapper": "...", ... } on stdout
# Exit 2: { "ok": false, "detail": "..." } on stderr (fail-closed)

# Parse command line arguments
PLAN_FILE=""
RUNCONFIG_FILE=""
NODE_ID=""

while [[ $# -gt 0 ]]; do
  case $1 in
    --plan)
      if [[ $# -lt 2 ]]; then
        die_json "Flag --plan requires a value"
      fi
      PLAN_FILE="$2"
      shift 2
      ;;
    --runconfig)
      if [[ $# -lt 2 ]]; then
        die_json "Flag --runconfig requires a value"
      fi
      RUNCONFIG_FILE="$2"
      shift 2
      ;;
    --node)
      if [[ $# -lt 2 ]]; then
        die_json "Flag --node requires a value"
      fi
      NODE_ID="$2"
      shift 2
      ;;
    *)
      die_json "Unknown argument: $1"
      ;;
  esac
done

# Validate inputs
if [[ -z "$PLAN_FILE" || -z "$RUNCONFIG_FILE" || -z "$NODE_ID" ]]; then
  die_json "Missing required arguments"
fi

# Load and parse plan JSONL
if ! PLAN_NODE_ALL=$(jq -c --arg node_id "$NODE_ID" 'select(.id == $node_id)' "$PLAN_FILE" 2>/dev/null); then
  die_json "Failed to parse plan file"
fi
PLAN_NODE_COUNT=$(echo "$PLAN_NODE_ALL" | grep -c '^{' || true)
if [[ "$PLAN_NODE_COUNT" -gt 1 ]]; then
  die_json "Duplicate node ID in plan: $NODE_ID"
fi
PLAN_NODE="$PLAN_NODE_ALL"
if [[ -z "$PLAN_NODE" ]]; then
  die_json "Node not found in plan"
fi

# Extract seat and tier from plan node
if ! SEAT=$(echo "$PLAN_NODE" | jq -r '.seat' 2>/dev/null); then
  die_json "Failed to extract seat from plan node"
fi
if ! TIER=$(echo "$PLAN_NODE" | jq -r '.tier // "medium"' 2>/dev/null); then
  die_json "Failed to extract tier from plan node"
fi

# Deterministic gates are NOT seats - fail closed
if [[ "$SEAT" == "gate0" || "$SEAT" == "risk" ]]; then
  die_json "Deterministic gates are not resolvable seats"
fi

# Load and parse runconfig
if ! RUNCONFIG=$(cat "$RUNCONFIG_FILE" 2>/dev/null); then
  die_json "Failed to read runconfig"
fi
if ! PRESET_NAME=$(echo "$RUNCONFIG" | jq -r '.preset' 2>/dev/null); then
  die_json "Failed to parse runconfig JSON"
fi

RC_VERSION=$(echo "$RUNCONFIG" | jq -r '.version // "missing"')
if [[ "$RC_VERSION" != "runconfig/v1" ]]; then
  die_json "Runconfig version must be runconfig/v1, got: ${RC_VERSION}"
fi

OVERRIDES_TYPE=$(echo "$RUNCONFIG" | jq -r 'if has("overrides") then (.overrides | type) else "absent" end')
if [[ "$OVERRIDES_TYPE" != "absent" && "$OVERRIDES_TYPE" != "object" ]]; then
  die_json "Runconfig overrides must be an object, got type: ${OVERRIDES_TYPE}"
fi
if ! OVERRIDES=$(echo "$RUNCONFIG" | jq -c '.overrides // {}' 2>/dev/null); then
  die_json "Failed to parse runconfig overrides"
fi

# Validate no unknown top-level runconfig fields (schema additionalProperties:false)
UNKNOWN_RC_KEYS=$(echo "$RUNCONFIG" | jq -r '[keys[] | select(. != "version" and . != "preset" and . != "profile" and . != "overrides")] | join(", ")' 2>/dev/null || true)
if [[ -n "$UNKNOWN_RC_KEYS" ]]; then
  die_json "Runconfig has unknown fields (schema additionalProperties:false): ${UNKNOWN_RC_KEYS}"
fi
PROFILE_TYPE=$(echo "$RUNCONFIG" | jq -r 'if has("profile") then (.profile | type) else "absent" end')
PROFILE=$(echo "$RUNCONFIG" | jq -r '.profile // empty')
if [[ "$PROFILE_TYPE" != "absent" && ( "$PROFILE_TYPE" != "string" || ! "$PROFILE" =~ ^(dynamic|[a-z0-9][a-z0-9-]*)(,(dynamic|[a-z0-9][a-z0-9-]*))*$ ) ]]; then
  die_json "Runconfig profile must be a comma-separated account chain"
fi

# Validate override keys match schema propertyNames pattern
if [[ "$OVERRIDES" != "{}" ]]; then
  INVALID_KEY=$(echo "$OVERRIDES" | jq -r 'keys[] | select(test("^[a-z0-9][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)?$") | not)' 2>/dev/null | head -1)
  if [[ -n "$INVALID_KEY" ]]; then
    die_json "Runconfig overrides key '${INVALID_KEY}' does not match required pattern ^[a-z0-9][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)?$"
  fi
fi

# Validate override keys do not target deterministic gates (gate0, risk are NOT overridable)
if [[ "$OVERRIDES" != "{}" ]]; then
  GATE_OVERRIDE_KEY=$(echo "$OVERRIDES" | jq -r '
    keys[] |
    (split(".")[0]) |
    select(. == "gate0" or . == "risk")
  ' 2>/dev/null | head -1)
  if [[ -n "$GATE_OVERRIDE_KEY" ]]; then
    die_json "Runconfig override targets non-overridable gate seat '${GATE_OVERRIDE_KEY}' (gate0/risk are deterministic, not model-bound)"
  fi
fi

# Validate override values have no unknown fields (binding additionalProperties:false)
# Allowed binding fields per presets.schema.json#/$defs/binding: wrapper, model, timeout, flags, fallback
if [[ "$OVERRIDES" != "{}" ]]; then
  INVALID_BINDING_KEY=$(echo "$OVERRIDES" | jq -r '
    to_entries[] |
    .key as $k |
    .value |
    if type != "object" then $k
    else (
      keys[] | select(. != "wrapper" and . != "model" and . != "timeout" and . != "flags" and . != "fallback")
      | $k
    )
    end
  ' 2>/dev/null | head -1)
  if [[ -n "$INVALID_BINDING_KEY" ]]; then
    die_json "Runconfig override for key '${INVALID_BINDING_KEY}' contains unknown binding field(s) (schema additionalProperties:false)"
  fi
fi

if [[ "$OVERRIDES" != "{}" ]]; then
  INVALID_KEY=$(echo "$OVERRIDES" | jq -r '
    to_entries[] |
    .key as $k |
    .value |
    if type == "object" and has("timeout") then
      (.timeout | if (type != "number" or . < 1 or floor != .) then $k else empty end)
    else empty end
  ' 2>/dev/null | head -1)
  if [[ -n "$INVALID_KEY" ]]; then
    die_json "Runconfig override for key '${INVALID_KEY}' has invalid timeout (must be integer >= 1)"
  fi
fi

if [[ "$OVERRIDES" != "{}" ]]; then
  INVALID_KEY=$(echo "$OVERRIDES" | jq -r '
    to_entries[] |
    .key as $k |
    .value |
    if type == "object" and has("flags") then
      (.flags | if type != "array" then $k
       elif ([ .[] | select(type != "string") ] | length) > 0 then $k
       else empty end)
    else empty end
  ' 2>/dev/null | head -1)
  if [[ -n "$INVALID_KEY" ]]; then
    die_json "Runconfig override for key '${INVALID_KEY}' has invalid flags (must be array of strings)"
  fi
fi

if [[ "$OVERRIDES" != "{}" ]]; then
  INVALID_KEY=$(echo "$OVERRIDES" | jq -r '
    to_entries[] |
    .key as $k |
    .value |
    if type == "object" and has("fallback") then
      (.fallback |
        if type == "string" then empty
        elif type == "object" then (if (has("wrapper") and (.wrapper|type=="string") and (.wrapper|length>0) and has("model") and (.model|type=="string") and (.model|length>0)) then empty else $k end)
        elif type == "array" then
          ([ .[] | select(type == "string" or (type == "object" and (has("wrapper") and (.wrapper|type=="string") and (.wrapper|length>0) and has("model") and (.model|type=="string") and (.model|length>0)))) ] | length) as $valid |
          if $valid == (. | length) then empty else $k end
        else $k end)
    else empty end
  ' 2>/dev/null | head -1)
  if [[ -n "$INVALID_KEY" ]]; then
    die_json "Runconfig override for key '${INVALID_KEY}' has invalid fallback (must be string, binding object with non-empty wrapper+model, or array of those)"
  fi
fi

BINDING_VALID_PRED='type == "object" and has("wrapper") and (.wrapper | type == "string") and (.wrapper | length > 0) and has("model") and (.model | type == "string") and (.model | length > 0)'

# Validate all override bindings upfront (not only the selected one)
if [[ "$OVERRIDES" != "{}" ]]; then
  INVALID_BINDING_VAL_KEY=$(echo "$OVERRIDES" | jq -r "
    to_entries[] |
    .key as \$k |
    .value |
    if ($BINDING_VALID_PRED) then empty
    else \$k
    end
  " 2>/dev/null | head -1)
  if [[ -n "$INVALID_BINDING_VAL_KEY" ]]; then
    die_json "Runconfig override for key '${INVALID_BINDING_VAL_KEY}' is not a valid binding (must be object with non-empty wrapper and model strings)"
  fi
fi

if [[ "$PRESET_NAME" == "null" ]]; then
  die_json "Runconfig missing preset field"
fi

if [[ ! "$PRESET_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  die_json "Invalid preset name: must match ^[a-z0-9][a-z0-9-]*$"
fi

# Load preset
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"

PRESET_PATH=""

for location in "$PARENT_DIR/presets/" "$PARENT_DIR/spec/presets/" "$PARENT_DIR/workspace/presets/" "$PARENT_DIR/workspace/spec/presets/"; do
  if [[ -f "$location${PRESET_NAME}.json" ]]; then
    PRESET_PATH="$location${PRESET_NAME}.json"
    break
  fi
done

if [[ -z "$PRESET_PATH" ]]; then
  die_json "Preset not found: ${PRESET_NAME}"
fi

if ! PRESET=$(cat "$PRESET_PATH" 2>/dev/null); then
  die_json "Failed to read preset"
fi

# Resolution ladder per spec/runconfig.schema.json (stop at first rung that holds):
# 1. overrides["seat.tier"] present → use it (most specific)
# 2. overrides["seat"] present → use it (bare seat override)
# 3. preset.seats[seat][tier] present (tiered) → use it
# 4. preset.seats[seat] is flat binding → use it
# 5. none → BLOCK (fail-closed)
if echo "$OVERRIDES" | jq -e --arg key "${SEAT}.${TIER}" 'has($key)' > /dev/null 2>&1; then
  RESOLVED_BINDING=$(echo "$OVERRIDES" | jq -c --arg key "${SEAT}.${TIER}" '.[$key]')
  RESOLUTION_RUNG=1
elif echo "$OVERRIDES" | jq -e --arg key "${SEAT}" 'has($key)' > /dev/null 2>&1; then
  RESOLVED_BINDING=$(echo "$OVERRIDES" | jq -c --arg key "${SEAT}" '.[$key]')
  RESOLUTION_RUNG=2
elif echo "$PRESET" | jq -e --arg s "$SEAT" --arg t "$TIER" '.seats[$s] | (has("wrapper") | not) and has($t)' > /dev/null 2>&1; then
  RESOLVED_BINDING=$(echo "$PRESET" | jq -c --arg s "$SEAT" --arg t "$TIER" '.seats[$s][$t]')
  RESOLUTION_RUNG=3
elif echo "$PRESET" | jq -e --arg s "$SEAT" '.seats[$s].wrapper' > /dev/null 2>&1; then
  RESOLVED_BINDING=$(echo "$PRESET" | jq -c --arg s "$SEAT" '.seats[$s]')
  RESOLUTION_RUNG=4
else
  AVAILABLE=$(echo "$PRESET" | jq -r '[.seats | to_entries[] | .key as $s | (if (.value | has("wrapper")) then $s else (.value | keys[] | "\($s)/\(.)") end)] | join(", ")' 2>/dev/null)
  die_json "Unknown seat/tier: ${SEAT}/${TIER} — preset ${PRESET_NAME} provides: ${AVAILABLE}"
fi

# Validate binding has required 'wrapper' and 'model' keys
if ! echo "$RESOLVED_BINDING" | jq -e "$BINDING_VALID_PRED" > /dev/null 2>&1; then
  die_json "Resolved binding missing required wrapper and model keys"
fi

# Add seat, tier, and resolution rung to binding for output
echo "$RESOLVED_BINDING" | jq --arg seat "$SEAT" --arg tier "$TIER" --argjson rung "$RESOLUTION_RUNG" '. + {seat: $seat, tier: $tier, rung: $rung}'
