#!/usr/bin/env bash
# sloptrap
if [ -z "${BASH_VERSION:-}" ]; then
  exec bash "$0" "$@"
fi
set -euo pipefail

IS_MAC=false
if [[ $(uname -s 2>/dev/null) == "Darwin" ]]; then
  IS_MAC=true
  mac_gnu_bins=(
    /opt/homebrew/opt/coreutils/libexec/gnubin
    /usr/local/opt/coreutils/libexec/gnubin
    /opt/homebrew/opt/gnu-tar/libexec/gnubin
    /usr/local/opt/gnu-tar/libexec/gnubin
  )
  for bin_dir in "${mac_gnu_bins[@]}"; do
    [[ -d $bin_dir ]] || continue
    case ":$PATH:" in
      *":$bin_dir:"*)
        ;;
      *)
        path_head=${PATH%%:*}
        path_rest=""
        if [[ $PATH == *:* ]]; then
          path_rest=${PATH#*:}
        fi
        case "$path_head" in
          /tmp/*|/var/folders/*|"$HOME"/*)
            if [[ -n $path_rest ]]; then
              PATH="$path_head:$bin_dir:$path_rest"
            else
              PATH="$path_head:$bin_dir"
            fi
            ;;
          *)
            PATH="$bin_dir:$PATH"
            ;;
        esac
        unset -v path_head path_rest
        ;;
    esac
  done
  export PATH
fi

REQUIRED_BREW_PKGS="coreutils gnu-tar jq"

require_cmd() {
  if command -v "$1" >/dev/null 2>&1; then
    return 0
  fi
  if $IS_MAC; then
    error "'$1' is required; install with: brew install $REQUIRED_BREW_PKGS"
  else
    error "'$1' is required"
  fi
}
for c in curl tar sha256sum realpath jq; do require_cmd "$c"; done

COLOR_TEXT=$'\033[38;5;247m'
COLOR_HIGHLIGHT=$'\033[38;5;202m'
COLOR_ERROR=$'\033[38;5;160m'
COLOR_COMMENT=$'\033[38;5;242m'
RESET=$'\033[0m'
BOLD=$'\033[1m'

PREFIX_TEXT=$'\033[38;5;247m░\033[0m '
PREFIX_HIGHLIGHT=$'\033[38;5;202m█\033[0m '
PREFIX_ERROR=$'\033[38;5;160m▒\033[0m '
PREFIX_COMMENT=$'\033[38;5;242m▒\033[0m '

print_styled() {
  local color=$1
  local prefix=$2
  local fmt=$3
  shift 3
  printf '%s' "$prefix"
  printf '%b' "$color"
  if (($# > 0)); then
    local -a colored_args=()
    local arg
    for arg in "$@"; do
      colored_args+=("$COLOR_HIGHLIGHT$arg$color")
    done
    # Format strings are defined within sloptrap, not user input.
    # shellcheck disable=SC2059
    printf "$fmt" "${colored_args[@]}"
  else
    printf '%b' "$fmt"
  fi
  printf '%b' "$RESET"
}

print_styled_err() {
  local color=$1
  local prefix=$2
  local fmt=$3
  shift 3
  printf '%s' "$prefix" >&2
  printf '%b' "$color" >&2
  if (($# > 0)); then
    local -a colored_args=()
    local arg
    for arg in "$@"; do
      colored_args+=("$COLOR_HIGHLIGHT$arg$color")
    done
    # Format strings are defined within sloptrap, not user input.
    # shellcheck disable=SC2059
    printf "$fmt" "${colored_args[@]}" >&2
  else
    printf '%b' "$fmt" >&2
  fi
  printf '%b' "$RESET" >&2
}

info_line() {
  print_styled "$COLOR_TEXT" "$PREFIX_TEXT" "$@"
}

highlight_line() {
  print_styled "$COLOR_HIGHLIGHT" "$PREFIX_TEXT" "$@"
}

status_line() {
  print_styled "$COLOR_COMMENT" "$PREFIX_TEXT" "$@"
}

comment_line() {
  print_styled "$COLOR_COMMENT" "$PREFIX_COMMENT" "$@"
}
warn_line() {
  print_styled_err "$COLOR_HIGHLIGHT" "$PREFIX_HIGHLIGHT" "$@"
}

error_line() {
  print_styled_err "$COLOR_ERROR" "$PREFIX_ERROR" "$@"
}

print_banner() {
  printf '%b' "${COLOR_HIGHLIGHT}${BOLD}"
  cat <<'EOF'
  ██████  ██▓     ▒█████   ██▓███  ▄▄▄█████▓ ██▀███   ▄▄▄       ██▓███  
▒██    ▒ ▓██▒    ▒██▒  ██▒▓██░  ██▒▓  ██▒ ▓▒▓██ ▒ ██▒▒████▄    ▓██░  ██▒
░ ▓██▄   ▒██░    ▒██░  ██▒▓██░ ██▓▒▒ ▓██░ ▒░▓██ ░▄█ ▒▒██  ▀█▄  ▓██░ ██▓▒
  ▒   ██▒▒██░    ▒██   ██░▒██▄█▓▒ ▒░ ▓██▓ ░ ▒██▀▀█▄  ░██▄▄▄▄██ ▒██▄█▓▒ ▒
▒██████▒▒░██████▒░ ████▓▒░▒██▒ ░  ░  ▒██▒ ░ ░██▓ ▒██▒ ▓█   ▓██▒▒██▒ ░  ░
▒ ▒▓▒ ▒ ░░ ▒░▓  ░░ ▒░▒░▒░ ▒▓▒░ ░  ░  ▒ ░░   ░ ▒▓ ░▒▓░ ▒▒   ▓▒█░▒▓▒░ ░  
░ ░▒  ░ ░░ ░ ▒░ https://git.sk4.nz/sk4nz/skz-sloptrap  ▒   ▒▒ ░░▒ ░     
   ░  ░    ░ ░   ░ ░ ░ ▒  ░░         ░        ░░   ░   ░   ▒   ░░       
      ░      ░  ░    ░ ░                       ░           ░  ░         
EOF
  printf '%b' "$RESET"
}

MANIFEST_BASENAME=".sloptrap"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VALID_NAME_REGEX='^[A-Za-z0-9_.-]+$'
DEFAULT_CODEX_ARGS=(--sandbox danger-full-access --ask-for-approval never)
DEFAULT_CODEX_ARGS_DISPLAY=$(printf '%s ' "${DEFAULT_CODEX_ARGS[@]}")
DEFAULT_CODEX_ARGS_DISPLAY=${DEFAULT_CODEX_ARGS_DISPLAY% }
SLOPTRAP_IMAGE_LABEL_KEY="net.sk4nz.sloptrap.managed"
SLOPTRAP_IMAGE_LABEL="${SLOPTRAP_IMAGE_LABEL_KEY}=1"

usage() {
  print_banner
  info_line "Usage: %s [options] <code-directory> [target ...]\n" "$0"
  info_line "Options:\n"
  comment_line "  --dry-run         Show planned container command(s) and exit\n"
  comment_line "  --print-config    Display resolved manifest values\n"
  comment_line "  -h, --help        Show this message\n"
  info_line "\n"
  comment_line "Each project supplies configuration via a %s file in its root.\n" "$MANIFEST_BASENAME"
  info_line "Example manifest entries:\n"
  comment_line "  name=my-project\n"
  comment_line "  packages_extra=kubectl helm\n"
  comment_line "  agent=codex\n"
  info_line "\n"
  info_line "Example targets:\n"
  comment_line "  run               Build if needed, then launch Codex\n"
  comment_line "  resume <id>       Build if needed, then run 'codex resume <id>'\n"
  comment_line "  shell             Drop into an interactive /bin/bash session\n"
  comment_line "  wizard            Create or update %s interactively\n" "$MANIFEST_BASENAME"
  comment_line "  clean             Remove the project container/image cache\n"
  comment_line "  prune             Remove dangling/unused sloptrap images\n"
}

error() {
  error_line "error: %s\n" "$1"
  exit 1
}

warn() {
  warn_line "warning: %s\n" "$1"
}

trim() {
  local value=$1
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

resolve_path_relaxed() {
  local candidate=$1
  local resolved
  if resolved=$(realpath -m "$candidate" 2>/dev/null); then
    printf '%s' "$resolved"
    return 0
  fi
  return 1
}

resolve_path_strict() {
  local candidate=$1
  local resolved
  if resolved=$(resolve_path_relaxed "$candidate"); then
    printf '%s' "$resolved"
    return 0
  fi
  error "failed to resolve path '$candidate'; install GNU coreutils for realpath support"
}

declare -A MANIFEST=()
declare -a SLOPTRAP_IGNORE_ENTRIES=()
declare -a IGNORE_MOUNT_ARGS=()
declare -a CODEX_ARGS_ARRAY=()
declare -a DEFAULT_TARGETS=()
MANIFEST_PRESENT=false

CURRENT_IGNORE_FILE=""
CONTAINER_ENGINE=""
CODEX_ROOT_HOST=""
CODEX_STATE_HOME_HOST=""
CODEX_AUTH_FILE_HOST=""
CODEX_STATE_KEY=""
CODEX_HOME_BOOTSTRAP=false
NEED_LOGIN=false
SLOPTRAP_TOOLS_HOME_CONT="/sloptrap-tools"
SLOPTRAP_TOOLS_BIN_CONT="/sloptrap-tools/bin"
SLOPTRAP_TOOLS_VOLUME=""
IGNORE_STUB_BASE=""
IGNORE_HELPER_ROOT=""
ALLOW_HOST_NETWORK=false
BACKEND="codex"
OPENCODE_SERVER=""
OPENCODE_MODEL=""
OPENCODE_CONTEXT=""
OPENCODE_STATE_HOME_HOST=""
OPENCODE_CONFIG_HOST=""
OPENCODE_CONFIG_CONT=""
SLOPTRAP_HOST_ALIAS=""

declare -a SLOPTRAP_TEMP_PATHS=()

register_temp_path() {
  SLOPTRAP_TEMP_PATHS+=("$1")
}

cleanup_temp_paths() {
  local path
  for path in "${SLOPTRAP_TEMP_PATHS[@]}"; do
    [[ -n ${path:-} ]] || continue
    rm -rf "$path" >/dev/null 2>&1 || true
  done
}

cleanup_ignore_stub_dir() {
  local helper_root=${IGNORE_HELPER_ROOT:-}
  local stub_base=${IGNORE_STUB_BASE:-}
  [[ -n $helper_root && -n $stub_base ]] || return 0
  [[ -d $stub_base ]] || return 0
  local resolved_helper resolved_stub
  if ! resolved_helper=$(resolve_path_relaxed "$helper_root"); then
    warn "failed to resolve helper root '$helper_root' during cleanup"
    return 0
  fi
  if ! resolved_stub=$(resolve_path_relaxed "$stub_base"); then
    warn "failed to resolve helper stub '$stub_base' during cleanup"
    return 0
  fi
  case "$resolved_stub" in
    "$resolved_helper"/session-*|"$resolved_helper"/fallback)
      rm -rf "$resolved_stub"
      ;;
    *)
      warn "refusing to remove unexpected helper path '$resolved_stub'"
      ;;
  esac
}

sloptrap_exit_trap() {
  cleanup_temp_paths
  cleanup_ignore_stub_dir
}

trap sloptrap_exit_trap EXIT INT TERM HUP

create_temp_dir() {
  local label=${1:-tmp}
  local template="${TMPDIR:-/tmp}/sloptrap.${label}.XXXXXXXX"
  local dir
  if ! dir=$(mktemp -d "$template"); then
    error "failed to create temporary directory under ${TMPDIR:-/tmp}"
  fi
  register_temp_path "$dir"
  printf '%s' "$dir"
}

write_embedded_dockerfile() {
  if [[ "$BACKEND" == "opencode" ]]; then
    local opencode_pkg
    case "$(uname -m)" in
      x86_64|amd64)
        opencode_pkg="opencode-desktop-linux-amd64.deb"
        ;;
      arm64|aarch64)
        opencode_pkg="opencode-desktop-linux-arm64.deb"
        ;;
      *)
        error "unsupported architecture for opencode"
        ;;
    esac
    
local dockerfile_content
    dockerfile_content=$(cat <<'DOCKERFILE_EOF'
# Dockerfile.sloptrap
ARG BASE_IMAGE=debian:trixie-slim
FROM ${BASE_IMAGE}

ENV DEBIAN_FRONTEND=noninteractive

ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 wget ripgrep xxd file procps util-linux binutils"
ARG EXTRA_PACKAGES=""
RUN apt-get update && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} && rm -rf /var/lib/apt/lists/*

ARG CODEX_UID=1337
ARG CODEX_GID=1337
RUN groupadd --gid ${CODEX_GID} sloptrap \
 && useradd --create-home --home-dir /home/sloptrap \
              --gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap

ARG OPENCODE_BIN=opencode
ARG OPENCODE_CONF=config/config.toml
COPY ${OPENCODE_BIN} /usr/local/bin/opencode
RUN chmod 0755 /usr/local/bin/opencode \
 && mkdir -p /sloptrap-tools/bin \
 && chmod 0777 /sloptrap-tools /sloptrap-tools/bin \
 && chown -R sloptrap:sloptrap /home/sloptrap

WORKDIR /workspace

ENV SHELL=/bin/bash HOME=/home/sloptrap \
    SLOPTRAP_TOOLS_HOME=/sloptrap-tools \
    PATH=/sloptrap-tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENTRYPOINT ["opencode"]
DOCKERFILE_EOF
)
  # Replace placeholder with actual package name
    dockerfile_content=$(printf '%s' "$dockerfile_content" | sed "s|PLACEHOLDER_OPENCODE_PKG|${opencode_pkg}|g")
    printf '%s' "$dockerfile_content"
  else
    cat <<'EOF'
# Dockerfile.sloptrap
ARG BASE_IMAGE=debian:trixie-slim
FROM ${BASE_IMAGE}

ENV DEBIAN_FRONTEND=noninteractive

ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux"
ARG EXTRA_PACKAGES=""
RUN apt-get update \
 && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \
 && rm -rf /var/lib/apt/lists/*

ARG CODEX_UID=1337
ARG CODEX_GID=1337
RUN groupadd --gid ${CODEX_GID} sloptrap \
 && useradd --create-home --home-dir /home/sloptrap \
             --gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap

ARG CODEX_BIN=codex
ARG CODEX_CONF=config/config.toml
COPY ${CODEX_BIN} /usr/local/bin/codex
RUN chmod 0755 /usr/local/bin/codex \
 && mkdir -p /sloptrap-tools/bin \
 && chmod 0777 /sloptrap-tools /sloptrap-tools/bin \
 && chown -R sloptrap:sloptrap /home/sloptrap

WORKDIR /workspace

ENV SHELL=/bin/bash HOME=/home/sloptrap \
    SLOPTRAP_TOOLS_HOME=/sloptrap-tools \
    PATH=/sloptrap-tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENTRYPOINT ["/usr/local/bin/codex"]
EOF
  fi
}


populate_embedded_helper() {
  local helper=$1
  local destination=$2
  mkdir -p "$(dirname "$destination")"
  write_embedded_helper "$helper" >"$destination"
}

populate_dockerfile() {
  local destination=$1
  mkdir -p "$(dirname "$destination")"
  if [[ -n $SLOPTRAP_DOCKERFILE_SOURCE ]]; then
    if [[ ! -f $SLOPTRAP_DOCKERFILE_SOURCE ]]; then
      error "container recipe '$SLOPTRAP_DOCKERFILE_SOURCE' not found"
    fi
    cp "$SLOPTRAP_DOCKERFILE_SOURCE" "$destination"
  else
    write_embedded_dockerfile >"$destination"
  fi
}

validate_basename() {
  local name=$1
  [[ $name =~ ^[A-Za-z0-9._+-]+$ ]] || error "invalid basename '$name'"
}

sanitize_engine_name() {
  local name=$1
  local lowered=${name,,}
  if [[ $lowered != "$name" ]]; then
    warn "normalizing name '$name' to '$lowered' for container engine compatibility"
  fi
  if [[ ! $lowered =~ ^[a-z0-9_.-]+$ ]]; then
    error "engine name '$name' is invalid after normalization"
  fi
  printf '%s' "$lowered"
}

prepare_build_context() {
  if [[ -n $SLOPTRAP_BUILD_CONTEXT && -d $SLOPTRAP_BUILD_CONTEXT ]]; then
    return 0
  fi
  SLOPTRAP_BUILD_CONTEXT=$(create_temp_dir "context")
  SLOPTRAP_DOCKERFILE_PATH="$SLOPTRAP_BUILD_CONTEXT/Dockerfile.sloptrap"
  populate_dockerfile "$SLOPTRAP_DOCKERFILE_PATH"
  
  if [[ "$BACKEND" == "opencode" ]]; then
    validate_basename "$SLOPTRAP_CODEX_BIN_NAME"
    CODEX_BIN_PATH="$SLOPTRAP_BUILD_CONTEXT/$SLOPTRAP_CODEX_BIN_NAME"
  else
    validate_basename "$SLOPTRAP_CODEX_BIN_NAME"
    CODEX_BIN_PATH="$SLOPTRAP_BUILD_CONTEXT/$SLOPTRAP_CODEX_BIN_NAME"
  fi
}

select_codex_home() {
  local preferred="$HOME/.codex"
  if [[ -n ${CODEX_HOME:-} ]]; then
    if [[ ${HOME:-} == "$CODEX_HOME" ]]; then
      preferred="$CODEX_HOME"
    elif [[ ${SLOPTRAP_PREFER_CODEX_HOME:-0} == "1" ]]; then
      local inherited_runtime_home=false
      local runtime_hint
      for runtime_hint in \
        "${SLOPTRAP_CAPTURE_DIR:-}" \
        "${SLOPTRAP_AUDIT_LOG:-}" \
        "${XDG_CONFIG_HOME:-}" \
        "${XDG_CACHE_HOME:-}" \
        "${XDG_STATE_HOME:-}"; do
        if [[ -n $runtime_hint && ( $runtime_hint == "$CODEX_HOME" || $runtime_hint == "$CODEX_HOME/"* ) ]]; then
          inherited_runtime_home=true
          break
        fi
      done
      if ! $inherited_runtime_home; then
        preferred="$CODEX_HOME"
      fi
    fi
  fi
  if [[ -L $preferred ]]; then
    error "Codex home '$preferred' must not be a symlink"
  fi
  if [[ -e $preferred && ! -d $preferred ]]; then
    error "expected Codex home '$preferred' to be a directory"
  fi

  CODEX_ROOT_HOST="$preferred"
  if [[ -d $CODEX_ROOT_HOST ]]; then
    CODEX_ROOT_HOST="$(cd "$CODEX_ROOT_HOST" && pwd -P)"
    CODEX_HOME_BOOTSTRAP=false
  else
    CODEX_HOME_BOOTSTRAP=true
  fi
  CODEX_STATE_KEY=$(printf '%s' "$CODE_DIR" | sha256sum)
  CODEX_STATE_KEY=${CODEX_STATE_KEY%% *}
  CODEX_STATE_HOME_HOST="$CODEX_ROOT_HOST/sloptrap/state/$CODEX_STATE_KEY"
  CODEX_AUTH_FILE_HOST="$CODEX_ROOT_HOST/auth.json"
  if [[ -L $CODEX_AUTH_FILE_HOST ]]; then
    error "Codex auth file '$CODEX_AUTH_FILE_HOST' must not be a symlink"
  fi
  if [[ -e $CODEX_AUTH_FILE_HOST && ! -f $CODEX_AUTH_FILE_HOST ]]; then
    error "expected Codex auth file '$CODEX_AUTH_FILE_HOST' to be a regular file"
  fi
}

compute_manifest_digest() {
  if [[ -f $MANIFEST_PATH ]]; then
    local digest
    digest=$(sha256sum "$MANIFEST_PATH")
    printf '%s' "${digest%% *}"
    return 0
  fi
  printf 'no-manifest'
}

assert_path_within_code_dir() {
  local candidate=$1
  local resolved
  resolved=$(resolve_path_strict "$candidate")
  if [[ $resolved != "$CODE_DIR" && $resolved != "$CODE_DIR/"* ]]; then
    error "path '$candidate' escapes project root '$CODE_DIR'"
  fi
}

ensure_ignore_helper_root() {
  local helper_root="$CODE_DIR/.sloptrap-ignores"
  if [[ -L $helper_root ]]; then
    error "$helper_root: helper directory may not be a symlink"
  fi
  if [[ -e $helper_root && ! -d $helper_root ]]; then
    error "$helper_root: expected a directory"
  fi
  assert_path_within_code_dir "$helper_root"
  IGNORE_HELPER_ROOT="$helper_root"
}

sanitize_ignore_rel() {
  local rel=$1
  local original=$rel
  local source=${CURRENT_IGNORE_FILE:-$MANIFEST_PATH}
  while [[ $rel == ./* ]]; do
    rel=${rel:2}
  done
  if [[ -z $rel || $rel == "." ]]; then
    error "$source: .sloptrapignore entry '$original' may not target the project root"
  fi
  if [[ ${rel:0:1} == "/" ]]; then
    error "$source: .sloptrapignore entry '$original' must be relative"
  fi
  local IFS='/'
  local -a segments=()
  read -r -a segments <<< "$rel"
  if [[ ${#segments[@]} -eq 0 ]]; then
    error "$source: .sloptrapignore entry '$original' is invalid"
  fi
  local segment
  for segment in "${segments[@]}"; do
    if [[ -z $segment || $segment == "." || $segment == ".." ]]; then
      error "$source: .sloptrapignore entry '$original' uses disallowed path components"
    fi
  done
  if [[ ${rel//$'\n'/} != "$rel" || ${rel//$'\r'/} != "$rel" ]]; then
    error "$source: .sloptrapignore entry '$original' contains control characters"
  fi
  local resolved
  resolved=$(resolve_path_strict "$CODE_DIR/$rel")
  if [[ $resolved != "$CODE_DIR" && $resolved != "$CODE_DIR/"* ]]; then
    error "$source: .sloptrapignore entry '$original' resolves outside the project root"
  fi
  printf '%s' "$rel"
}

resolve_sloptrap_ignore() {
  local root=$1
  local ignore_file="$root/.sloptrapignore"
  SLOPTRAP_IGNORE_ENTRIES=()
  [[ -f $ignore_file ]] || return 0

  CURRENT_IGNORE_FILE="$ignore_file"
  local -a patterns=()
  local line trimmed
  while IFS= read -r line || [[ -n $line ]]; do
    trimmed="$(trim "$line")"
    [[ -z $trimmed ]] && continue
    [[ ${trimmed:0:1} == "#" ]] && continue
    patterns+=("$trimmed")
  done <"$ignore_file"
  [[ ${#patterns[@]} -gt 0 ]] || return 0

  local -A selected=()
  local pattern negate anchored dir_only raw had_match
  local -a matches
  local match rel
  local prev_gs prev_ng prev_dg
  prev_gs=$(shopt -p globstar 2>/dev/null || true)
  prev_ng=$(shopt -p nullglob 2>/dev/null || true)
  prev_dg=$(shopt -p dotglob 2>/dev/null || true)
  shopt -s globstar nullglob dotglob
  for pattern in "${patterns[@]}"; do
    negate=false
    anchored=false
    dir_only=false
    raw="$pattern"
    had_match=false
    if [[ ${raw:0:1} == "!" ]]; then
      negate=true
      raw=${raw:1}
    fi
    if [[ ${raw:0:1} == "/" ]]; then
      anchored=true
      raw=${raw:1}
    fi
    if [[ ${raw: -1} == "/" ]]; then
      dir_only=true
      raw=${raw%/}
    fi
    [[ -n $raw ]] || raw="."
    matches=()
    if $anchored; then
      while IFS= read -r match; do
        matches+=("$root/$match")
      done < <(
        cd "$root" && { compgen -G "$raw" || true; }
      )
    else
      while IFS= read -r match; do
        matches+=("$root/$match")
      done < <(
        cd "$root" && { compgen -G "$raw" || true; }
      )
      while IFS= read -r match; do
        matches+=("$root/$match")
      done < <(
        cd "$root" && { compgen -G "**/$raw" || true; }
      )
    fi
    local -A seen=()
    for match in "${matches[@]}"; do
      [[ -e $match ]] || continue
      if [[ $match != "$root" && $match != $root/* ]]; then
        continue
      fi
      rel=${match#"$root"/}
      [[ -n $rel ]] || rel="."
      if $dir_only && [[ ! -d $match ]]; then
        continue
      fi
      if [[ -n ${seen[$rel]-} ]]; then
        continue
      fi
      seen[$rel]=1
      rel=$(sanitize_ignore_rel "$rel")
      had_match=true
      if $negate; then
        unset 'selected[$rel]'
      else
        if [[ -d $match ]]; then
          selected[$rel]="dir"
        else
          selected[$rel]="file"
        fi
      fi
    done
    if ! $had_match && ! $negate; then
      warn "${CURRENT_IGNORE_FILE:-$MANIFEST_PATH}: .sloptrapignore entry '$pattern' matched no files"
    fi
  done
  eval "$prev_gs"
  eval "$prev_ng"
  eval "$prev_dg"

  if [[ ${#selected[@]} -eq 0 ]]; then
    CURRENT_IGNORE_FILE=""
    return 0
  fi
  SLOPTRAP_IGNORE_ENTRIES=()
  for rel in "${!selected[@]}"; do
    SLOPTRAP_IGNORE_ENTRIES+=("${selected[$rel]}"$'\t'"$rel")
  done
  mapfile -t SLOPTRAP_IGNORE_ENTRIES < <(printf '%s\n' "${SLOPTRAP_IGNORE_ENTRIES[@]}" | sort)
  CURRENT_IGNORE_FILE=""
}

escape_mount_value() {
  local value=$1
  value=${value//\\/\\\\}
  value=${value//,/\\,}
  value=${value//=/\\=}
  printf '%s' "$value"
}

prepare_ignore_mounts() {
  IGNORE_MOUNT_ARGS=()
  [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]] || return 0
  local container_root="${SLOPTRAP_WORKDIR:-/workspace}"
  if [[ $container_root != "/" ]]; then
    container_root="${container_root%/}"
    [[ -n $container_root ]] || container_root="/"
  fi
  local entry type rel target host_path stub_base target_mount host_path_mount
  stub_base="$IGNORE_STUB_BASE"
  assert_path_within_code_dir "$stub_base"
  rm -rf "$stub_base" 2>/dev/null || true
  if ! mkdir -p "$stub_base/files" 2>/dev/null; then
    stub_base="$CODE_DIR/.sloptrap-ignores/fallback"
    assert_path_within_code_dir "$stub_base"
    rm -rf "$stub_base" 2>/dev/null || true
    mkdir -p "$stub_base/files"
  fi
  IGNORE_STUB_BASE="$stub_base"
  for entry in "${SLOPTRAP_IGNORE_ENTRIES[@]}"; do
    type=${entry%%$'\t'*}
    rel=${entry#*$'\t'}
    if [[ $container_root == "/" ]]; then
      target="/$rel"
    else
      target="$container_root/$rel"
    fi
    target_mount=$(escape_mount_value "$target")
    case "$type" in
      dir)
        IGNORE_MOUNT_ARGS+=("--mount" "type=tmpfs,target=$target_mount")
        ;;
      file)
        host_path="$stub_base/files/$rel"
        assert_path_within_code_dir "$host_path"
        mkdir -p "$(dirname "$host_path")"
        : > "$host_path"
        host_path_mount=$(escape_mount_value "$host_path")
        IGNORE_MOUNT_ARGS+=("--mount" "type=bind,source=$host_path_mount,target=$target_mount,readonly")
        ;;
    esac
  done
}

ensure_safe_for_make() {
  local key=$1
  local value=$2
  if [[ $value == *'$'* || $value == *'`'* ]]; then
    error "$MANIFEST_PATH: value for '$key' must not contain \$ or \` characters"
  fi
  if [[ $value == *$'\n'* ]]; then
    error "$MANIFEST_PATH: value for '$key' must not span multiple lines"
  fi
}

validate_package_list() {
  local key=$1
  local raw=$2
  local source=${3:-$MANIFEST_PATH}
  [[ -z $raw ]] && return 0
  local token
  for token in $raw; do
    if [[ ! $token =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*$ ]]; then
      error "$source: invalid package name '$token' in '$key'"
    fi
  done
}

# Common package name aliases for user convenience
expand_package_alias() {
  local pkg=$1
  case "$pkg" in
    rg) printf 'ripgrep' ;;
    git) printf 'git' ;;
    curl) printf 'curl' ;;
    bash) printf 'bash' ;;
    ca-certificates) printf 'ca-certificates' ;;
    libstdc++6) printf 'libstdc++6' ;;
    xxd) printf 'xxd' ;;
    file) printf 'file' ;;
    procps) printf 'procps' ;;
    util-linux) printf 'util-linux' ;;
    *) printf '%s' "$pkg" ;;
  esac
}

detect_container_engine() {
  local override=${SLOPTRAP_CONTAINER_ENGINE-}
  if [[ -n $override ]]; then
    local engine="${override,,}"
    if [[ $engine == */* ]]; then
      [[ -x $engine ]] || error "container engine '$engine' is not executable"
    elif ! command -v "$engine" >/dev/null 2>&1; then
      error "container engine '$engine' not found in PATH"
    fi
    printf '%s' "$engine"
    return 0
  fi
  if command -v podman >/dev/null 2>&1; then
    printf 'podman'
    return 0
  fi
  if command -v docker >/dev/null 2>&1; then
    printf 'docker'
    return 0
  fi
  error "container engine not found in PATH; install podman (preferred) or docker, or set SLOPTRAP_CONTAINER_ENGINE explicitly"
}

parse_manifest() {
  local manifest_path=$1
  local line key value
  while IFS= read -r line || [[ -n $line ]]; do
    line="$(trim "$line")"
    [[ -z $line ]] && continue
    [[ ${line:0:1} == "#" ]] && continue
    if [[ $line != *"="* ]]; then
      error "$manifest_path: expected KEY=VALUE entry (got '$line')"
    fi
    key="$(trim "${line%%=*}")"
    value="$(trim "${line#*=}")"
    if [[ -z $key ]]; then
      error "$manifest_path: blank key in line '$line'"
    fi
    if [[ ( ${value:0:1} == '"' && ${value: -1} == '"' ) || ( ${value:0:1} == "'" && ${value: -1} == "'" ) ]]; then
      value="${value:1:-1}"
    fi
    if [[ $key == make.* ]]; then
      error "$manifest_path: make.* overrides are no longer supported; use packages_extra instead"
    fi
    MANIFEST["$key"]=$value
  done < "$manifest_path"
}

manifest_default_value() {
  local key=$1
  local fallback=$2
  if [[ -v MANIFEST["$key"] ]]; then
    printf '%s' "${MANIFEST[$key]}"
  else
    printf '%s' "$fallback"
  fi
}

prompt_manifest_value() {
   local label=$1
   local default_value=$2
   local input
   # Print prompt to stderr
   printf '%s [%s]: ' "$label" "$default_value" >&2
   # Read input from stdin (works for both interactive and piped input)
   # Don't require interactive terminal - fallback to stdin
   if IFS= read -r input; then
     printf '%s' "$input"
     return 0
   fi
   error "wizard requires input"
}

validate_wizard_name() {
  local value=$1
  [[ -n $value ]] || error "$MANIFEST_PATH: name must not be empty"
  if [[ ! $value =~ $VALID_NAME_REGEX ]]; then
    error "$MANIFEST_PATH: invalid project name '$value' (allowed: letters, digits, ., _, -)"
  fi
}

normalize_wizard_allow_host_network() {
  local value=${1,,}
  case "$value" in
    1|true|yes) printf 'true' ;;
    0|false|no) printf 'false' ;;
    *) error "$MANIFEST_PATH: allow_host_network must be true or false (got '$1')" ;;
  esac
}

parse_opencode_context() {
  local raw=$1
  local upper=${raw^^}
  local number suffix multiplier=1
  [[ -n $upper ]] || error "$MANIFEST_PATH: opencode_context must not be empty"
  if [[ $upper =~ ^([0-9]+)([KMG]?)$ ]]; then
    number=${BASH_REMATCH[1]}
    suffix=${BASH_REMATCH[2]}
  else
    error "$MANIFEST_PATH: opencode_context must be an integer optionally suffixed with K, M, or G (got '$raw')"
  fi
  case "$suffix" in
    "") multiplier=1 ;;
    K) multiplier=1024 ;;
    M) multiplier=$((1024 * 1024)) ;;
    G) multiplier=$((1024 * 1024 * 1024)) ;;
  esac
  printf '%s' $(( number * multiplier ))
}

run_wizard() {
   local manifest_path=$1
   if [[ -L $manifest_path ]]; then
     error "$manifest_path: manifest must not be a symlink"
   fi
   if [[ ! -f $manifest_path ]]; then
     print_banner
   fi
   # Derive CODE_DIR from manifest path
   CODE_DIR="$(dirname "$manifest_path")"

   local default_name
   local default_packages_extra
   local default_agent
   local default_allow_host_network
   local default_opencode_server
   local default_opencode_model
   local default_opencode_context

   default_name=$(manifest_default_value "name" "$(basename "$CODE_DIR")")
   default_packages_extra=$(manifest_default_value "packages_extra" "")
   default_agent=$(manifest_default_value "agent" "codex")
   default_allow_host_network=$(manifest_default_value "allow_host_network" "false")
   default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:8080")
   default_opencode_model=$(manifest_default_value "opencode_model" "unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q3_K_XL")
   default_opencode_context=$(manifest_default_value "opencode_context" "256K")

   local action="Creating"
   if [[ -f $manifest_path ]]; then
     action="Updating"
   fi
   info_line "%s %s interactively.\n" "$action" "$MANIFEST_BASENAME"

   local value
   while true; do
     info_line "name: Labels the project, container, and image names.\n"
     value=$(prompt_manifest_value "name" "$default_name")
     value=$(trim "$value")
     [[ -n $value ]] || value=$default_name
     validate_wizard_name "$value"
     default_name=$value
     break
   done

   while true; do
     info_line "packages_extra: Extra Debian packages to install during image build.\n"
     value=$(prompt_manifest_value "packages_extra" "$default_packages_extra")
     value=$(trim "$value")
     [[ -n $value ]] || value=$default_packages_extra
     if [[ -n $value ]]; then
       validate_package_list "packages_extra" "$value" "$manifest_path"
     fi
     default_packages_extra=$value
     break
   done

   while true; do
      info_line "agent: Select your AI agent backend (codex or opencode).\n"
      value=$(prompt_manifest_value "agent" "$default_agent")
      value=$(trim "$value")
      [[ -n $value ]] || value=$default_agent
      case "${value,,}" in
        codex) value="codex" ;;
        opencode) value="opencode" ;;
        *) error "agent must be 'codex' or 'opencode'" ;;
      esac
      default_agent=$value
      break
    done

   # If opencode, prompt for server and model
   if [[ "$default_agent" == "opencode" ]]; then
     while true; do
       info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:8080).\n"
       value=$(prompt_manifest_value "opencode_server" "$default_opencode_server")
       value=$(trim "$value")
       [[ -n $value ]] || value="http://localhost:8080"
       default_opencode_server=$value
       break
     done

     while true; do
       info_line "opencode_model: Model name on the server (e.g., bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0).\n"
       value=$(prompt_manifest_value "opencode_model" "$default_opencode_model")
       value=$(trim "$value")
       [[ -n $value ]] || value="bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0"
       default_opencode_model=$value
       break
     done

     while true; do
       info_line "opencode_context: Context window for the model (e.g., 256K).\n"
       value=$(prompt_manifest_value "opencode_context" "$default_opencode_context")
       value=$(trim "$value")
       [[ -n $value ]] || value="256K"
       parse_opencode_context "$value" >/dev/null
       default_opencode_context=$value
       break
     done
   fi

   while true; do
     info_line "allow_host_network: Use host networking instead of an isolated bridge.\n"
     value=$(prompt_manifest_value "allow_host_network" "$default_allow_host_network")
     value=$(trim "$value")
     [[ -n $value ]] || value=$default_allow_host_network
     default_allow_host_network=$(normalize_wizard_allow_host_network "$value")
     break
   done

   assert_path_within_code_dir "$manifest_path"
   cat > "$manifest_path" <<EOF
name=$default_name
packages_extra=$default_packages_extra
agent=$default_agent
allow_host_network=$default_allow_host_network
EOF
   if [[ "$default_agent" == "opencode" ]]; then
     cat >> "$manifest_path" <<EOF
opencode_server=$default_opencode_server
opencode_model=$default_opencode_model
opencode_context=$default_opencode_context
EOF
   fi
   info_line "Wrote %s\n" "$manifest_path"
   local ignore_path="$CODE_DIR/.sloptrapignore"
   if [[ ! -f $ignore_path ]]; then
     info_line "Hint: create %s to hide files from the container (e.g., .git/ or secrets/).\n" "$ignore_path"
   fi
   info_line "Hint: run 'sloptrap %s build' or 'sloptrap %s rebuild' to apply changes.\n" "$CODE_DIR" "$CODE_DIR"
}

print_config() {
   local manifest_path=$1
   info_line "manifest_path=%s\n" "$manifest_path"
   info_line "manifest_present=%s\n" "$MANIFEST_PRESENT"
   info_line "project_name=%s\n" "$PROJECT_NAME"
   info_line "project_dir=%s\n" "$CODE_DIR"
   info_line "resolved_targets=%s\n" "${DEFAULT_TARGETS[*]}"
   info_line "container_engine=%s\n" "$CONTAINER_ENGINE"
   info_line "image_name=%s\n" "$SLOPTRAP_IMAGE_NAME"
   info_line "container_name=%s\n" "$SLOPTRAP_CONTAINER_NAME"
   if [[ "$BACKEND" == "opencode" ]]; then
     info_line "backend=%s\n" "opencode"
     info_line "opencode_server=%s\n" "$OPENCODE_SERVER"
     info_line "opencode_model=%s\n" "$OPENCODE_MODEL"
     info_line "opencode_context=%s\n" "$OPENCODE_CONTEXT"
     info_line "opencode_state_home=%s\n" "$OPENCODE_STATE_HOME_HOST"
     info_line "opencode_config=%s\n" "$OPENCODE_CONFIG_HOST"
     info_line "runtime_flags=\n"
   else
     info_line "backend=%s\n" "codex"
     info_line "codex_home=%s\n" "$CODEX_STATE_HOME_HOST"
     info_line "codex_root=%s\n" "$CODEX_ROOT_HOST"
     info_line "codex_state_home=%s\n" "$CODEX_STATE_HOME_HOST"
     info_line "codex_auth_file=%s\n" "$CODEX_AUTH_FILE_HOST"
     info_line "codex_state_key=%s\n" "$CODEX_STATE_KEY"
     info_line "codex_home_bootstrap=%s\n" "$CODEX_HOME_BOOTSTRAP"
     info_line "codex_archive=%s\n" "$SLOPTRAP_CODEX_ARCHIVE"
     info_line "codex_url=%s\n" "$SLOPTRAP_CODEX_URL"
     info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
   fi
   info_line "host_alias=%s\n" "${SLOPTRAP_HOST_ALIAS:-}"
   info_line "tools_volume=%s\n" "$SLOPTRAP_TOOLS_VOLUME"
   info_line "tools_home=%s\n" "$SLOPTRAP_TOOLS_HOME_CONT"
   info_line "tools_bin=%s\n" "$SLOPTRAP_TOOLS_BIN_CONT"
   info_line "needs_login=%s\n" "$NEED_LOGIN"
   info_line "ignore_stub_base=%s\n" "$IGNORE_STUB_BASE"
   if [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]]; then
     local ignore_paths
     ignore_paths=$(printf '%s ' "${SLOPTRAP_IGNORE_ENTRIES[@]}")
     ignore_paths=${ignore_paths% }
     info_line "ignore_paths=%s\n" "$ignore_paths"
   else
     info_line "ignore_paths=\n"
   fi
   if [[ -n $SLOPTRAP_DOCKERFILE_SOURCE ]]; then
     info_line "dockerfile_source=%s\n" "$SLOPTRAP_DOCKERFILE_SOURCE"
   else
     info_line "dockerfile_source=embedded\n"
   fi
   for key in "${!MANIFEST[@]}"; do
     info_line "%s=%s\n" "$key" "${MANIFEST[$key]}"
   done
}

print_manifest_summary() {
   highlight_line "Manifest summary\n"
   comment_line "  manifest_path=%s\n" "$MANIFEST_PATH"
   comment_line "  name=%s\n" "$PROJECT_NAME"
   comment_line "  packages_extra=%s\n" "$PACKAGES_EXTRA"
   if [[ "$BACKEND" == "opencode" ]]; then
     comment_line "  backend=%s\n" "opencode"
     comment_line "  opencode_server=%s\n" "$OPENCODE_SERVER"
     comment_line "  opencode_model=%s\n" "$OPENCODE_MODEL"
     comment_line "  opencode_context=%s\n" "$OPENCODE_CONTEXT"
     comment_line "  opencode_config=%s\n" "$OPENCODE_CONFIG_CONT"
     comment_line "  runtime_flags=\n"
   else
     comment_line "  backend=%s\n" "codex"
     comment_line "  runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
   fi
   if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
     comment_line "  host_alias=%s\n" "$SLOPTRAP_HOST_ALIAS"
   fi
   comment_line "  allow_host_network=%s\n" "$ALLOW_HOST_NETWORK"
}

build_runtime_context_prompt() {
   local prompt network_mode
   network_mode="isolated"
   if [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then
     network_mode="host"
   fi
   local agent_name
   if [[ "$BACKEND" == "opencode" ]]; then
     agent_name="opencode"
   else
     agent_name="Codex"
   fi
   prompt=$(cat <<EOF
You are running inside sloptrap, which confines $agent_name inside a container.
This startup note describes the sloptrap runtime only; it does not replace higher-priority instructions from AGENTS.md or the system.

Current resolved sloptrap state:
- name=$PROJECT_NAME (project/image/container label)
- packages_extra=${PACKAGES_EXTRA:-none} (Debian packages added at build time)
- network_mode=$network_mode (host when host networking is enabled; otherwise isolated)
- tools_home=$SLOPTRAP_TOOLS_HOME_CONT (writable install prefix for third-party tools)
- tools_bin=$SLOPTRAP_TOOLS_BIN_CONT (already on the default PATH)
EOF
)
   if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
     prompt+=$'\n'
     prompt+="- host_alias=$SLOPTRAP_HOST_ALIAS (container-side address for services running on the host)\n"
   fi
   printf '%s' "$prompt"
}

declare -a CONTAINER_SHARED_OPTS=()
declare -a BASE_CONTAINER_CMD=()
SLOPTRAP_IMAGE_NAME=""
SLOPTRAP_CONTAINER_NAME=""
SLOPTRAP_DOCKERFILE_PATH=""
SLOPTRAP_BUILD_CONTEXT=""
SLOPTRAP_DOCKERFILE_SOURCE=""
CODEX_BIN_PATH=""
SLOPTRAP_SHARED_DIR_ABS=""
SLOPTRAP_PACKAGES_BASE=""
SLOPTRAP_PACKAGES_EXTRA_RESOLVED=""
SLOPTRAP_CODEX_BIN_NAME=""
SLOPTRAP_CODEX_URL=""
SLOPTRAP_CODEX_ARCHIVE=""
SLOPTRAP_CODEX_HOME_CONT=""
SLOPTRAP_RUNTIME_PATH=""
SLOPTRAP_VOLUME_LABEL=""
SLOPTRAP_WORKDIR=${SLOPTRAP_WORKDIR-}
SLOPTRAP_NETWORK_NAME=""
SLOPTRAP_LIMITS_PID=""
SLOPTRAP_LIMITS_RAM=""
SLOPTRAP_LIMITS_SWP=""
SLOPTRAP_LIMITS_SHM=""
SLOPTRAP_LIMITS_CPU=""
SLOPTRAP_TMPFS_PATHS=""
SLOPTRAP_RUN_AS_ROOT=false
get_env_default() {
  local var=$1
  local default=$2
  local value
  if value=$(printenv "$var" 2>/dev/null); then
    printf '%s' "$value"
  else
    printf '%s' "$default"
  fi
}

validate_codex_archive_name() {
  local name=$1
  [[ $name =~ ^(codex|opencode)-[A-Za-z0-9_.-]+$ ]] || error "invalid binary name '$name'"
}

detect_codex_archive_name() {
  local os arch codex_os codex_arch
  os=$(uname -s 2>/dev/null || true)
  arch=$(uname -m 2>/dev/null || true)
  [[ -n $os ]] || error "failed to detect host OS for Codex download"
  [[ -n $arch ]] || error "failed to detect host architecture for Codex download"
  case "$os" in
    Linux|Darwin) codex_os="unknown-linux-musl" ;; # Codex runs inside a Debian-based image
    *) error "unsupported host OS '$os' for Codex download" ;;
  esac
  case "$arch" in
    x86_64|amd64) codex_arch="x86_64" ;;
    arm64|aarch64) codex_arch="aarch64" ;;
    *) error "unsupported host architecture '$arch' for Codex download" ;;
  esac
  printf 'codex-%s-%s' "$codex_arch" "$codex_os"
}

resolve_container_workdir() {
  if [[ -z ${SLOPTRAP_WORKDIR:-} ]]; then
    SLOPTRAP_WORKDIR=$(get_env_default "SLOPTRAP_WORKDIR" "/workspace")
  fi
  [[ -n $SLOPTRAP_WORKDIR ]] || SLOPTRAP_WORKDIR="/workspace"
  if [[ $SLOPTRAP_WORKDIR == "/" ]]; then
    error "SLOPTRAP_WORKDIR may not be '/'; use a subdirectory like /workspace"
  fi
}

print_command() {
  local rendered=""
  if [[ $# -gt 0 ]]; then
    rendered=$(printf '%q ' "$@")
    rendered=${rendered% }
  fi
  comment_line "%s\n" "$rendered"
}

run_or_print() {
  if $DRY_RUN; then
    print_command "$@"
    return 0
  fi
  "$@"
}

append_auth_mount_arg() {
  local writable=$1
  local -n out=$2
  local suffix=""
  if [[ $CONTAINER_ENGINE == "podman" ]]; then
    suffix=":Z"
    if [[ $writable != true ]]; then
      suffix=":Z,ro"
    fi
  elif [[ $writable != true ]]; then
    suffix=":ro"
  fi
  out+=(-v "$CODEX_AUTH_FILE_HOST:$SLOPTRAP_CODEX_HOME_CONT/auth.json$suffix")
}

run_runtime_container_cmd() {
  local -a cmd=("$@")
  local status=0
  if run_or_print "${cmd[@]}"; then
    status=0
  else
    status=$?
  fi
  return "$status"
}

ensure_codex_directory() {
  local path=$1
  local label=$2
  if [[ -L $path ]]; then
    error "$label '$path' must not be a symlink"
  fi
  if [[ -e $path && ! -d $path ]]; then
    error "expected $label '$path' to be a directory"
  fi
  if [[ -d $path ]]; then
    return 0
  fi
  if $DRY_RUN; then
    print_command mkdir -p "$path"
    return 0
  fi
  mkdir -p "$path"
}

ensure_codex_storage_paths() {
  local state_root="$CODEX_ROOT_HOST/sloptrap"
  local state_bucket="$state_root/state"
  ensure_codex_directory "$CODEX_ROOT_HOST" "Codex home"
  ensure_codex_directory "$state_root" "sloptrap Codex namespace"
  ensure_codex_directory "$state_bucket" "sloptrap Codex state root"
  ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Codex state"
  if [[ -L $CODEX_AUTH_FILE_HOST ]]; then
    error "Codex auth file '$CODEX_AUTH_FILE_HOST' must not be a symlink"
  fi
  if [[ -e $CODEX_AUTH_FILE_HOST && ! -f $CODEX_AUTH_FILE_HOST ]]; then
    error "expected Codex auth file '$CODEX_AUTH_FILE_HOST' to be a regular file"
  fi
  if [[ -f $CODEX_AUTH_FILE_HOST ]]; then
    return 0
  fi
  if $DRY_RUN; then
    print_command touch "$CODEX_AUTH_FILE_HOST"
    return 0
  fi
  : > "$CODEX_AUTH_FILE_HOST"
}

ensure_opencode_storage_paths() {
  local state_root="$CODEX_ROOT_HOST/sloptrap"
  local state_bucket="$state_root/state"
  ensure_codex_directory "$CODEX_ROOT_HOST" "Opencode home"
  ensure_codex_directory "$state_root" "sloptrap Opencode namespace"
  ensure_codex_directory "$state_bucket" "sloptrap Opencode state root"
  ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Opencode state"
  ensure_codex_directory "$OPENCODE_STATE_HOME_HOST" "project Opencode runtime state"
}

ensure_opencode_config() {
  local provider_id="llama.cpp"
  local provider_name="llama-server (local)"
  local base_url="$OPENCODE_SERVER"
  local context_limit
  context_limit=$(parse_opencode_context "$OPENCODE_CONTEXT")
  local model_key="${OPENCODE_MODEL} - ${context_limit}"
  local model_ref="${provider_id}/${model_key}"
  local config_dir
  case "$base_url" in
    */v1|*/v1/|*/v1\?*|*/v1\#*)
      ;;
    *)
      base_url="${base_url%/}/v1"
      ;;
  esac
  config_dir=$(dirname "$OPENCODE_CONFIG_HOST")
  if $DRY_RUN; then
    print_command mkdir -p "$config_dir"
    print_command env OPENCODE_CONFIG="$OPENCODE_CONFIG_CONT" opencode
    return 0
  fi
  ensure_codex_directory "$config_dir" "project Opencode config"
  if [[ -L $OPENCODE_CONFIG_HOST ]]; then
    error "Opencode config '$OPENCODE_CONFIG_HOST' must not be a symlink"
  fi
  if [[ -e $OPENCODE_CONFIG_HOST && ! -f $OPENCODE_CONFIG_HOST ]]; then
    error "expected Opencode config '$OPENCODE_CONFIG_HOST' to be a regular file"
  fi
  if ! jq -n \
    --arg schema "https://opencode.ai/config.json" \
    --arg provider_id "$provider_id" \
    --arg provider_name "$provider_name" \
    --arg base_url "$base_url" \
    --arg model_id "$OPENCODE_MODEL" \
    --arg model_key "$model_key" \
    --arg model_ref "$model_ref" \
    --argjson context_limit "$context_limit" \
    '{
      "$schema": $schema,
      enabled_providers: [$provider_id],
      share: "disabled",
      autoupdate: false,
      lsp: true,
      provider: {
        ($provider_id): {
          npm: "@ai-sdk/openai-compatible",
          name: $provider_name,
          options: {
            baseURL: $base_url,
            timeout: 1800000,
            chunkTimeout: 180000
          },
          models: {
            ($model_key): {
              name: $model_id,
              limit: {
                context: $context_limit,
                output: 16384
              },
              cost: {
                input: 0.0221,
                output: 0.1684,
                cache_read: 0.0001,
                cache_write: 0.0001
              },
              tool_call: true,
              reasoning: true
            }
          }
        }
      },
      model: $model_ref,
      compaction: {
        threshold: 0.95,
        strategy: "summarize",
        auto: true,
        prune: true
      },
      watcher: {
        ignore: [
          ".git/**",
          "node_modules/**",
          "dist/**",
          ".run/**"
        ]
      },
      permission: {
        read: "allow",
        edit: "allow",
        glob: "allow",
        grep: "allow",
        list: "allow",
        bash: "allow",
        task: "deny",
        question: "allow",
        webfetch: "allow",
        websearch: "allow",
        codesearch: "allow",
        external_directory: "allow",
        doom_loop: "deny"
      },
      "instructions": [
        "AGENTS.md"
      ]
    }' >"$OPENCODE_CONFIG_HOST"; then
    error "failed to write opencode config '$OPENCODE_CONFIG_HOST'"
  fi
}

fetch_latest_codex_digest() {
  local api_url="https://api.github.com/repos/openai/codex/releases/latest"
  local target_asset="${SLOPTRAP_CODEX_ARCHIVE}.tar.gz"
  [[ -n $SLOPTRAP_CODEX_ARCHIVE ]] || error "Codex archive name is not set"
  if ! command -v jq >/dev/null 2>&1; then
    error "jq is required to verify the Codex binary digest"
  fi
  local response
  if ! response=$(curl -fsSL "$api_url"); then
    error "failed to download Codex release metadata from GitHub"
  fi
  local digest_line
  digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
  if [[ -z $digest_line || $digest_line == "null" ]]; then
    error "failed to resolve Codex digest from GitHub response"
  fi
  digest_line=${digest_line#sha256:}
  printf '%s' "$digest_line"
}

detect_opencode_asset_name() {
    local arch
    arch=$(uname -m 2>/dev/null || true)
    [[ -n $arch ]] || error "failed to detect host architecture for opencode download"

    case "$arch" in
      x86_64|amd64)
        printf 'opencode-linux-x64.tar.gz'
        ;;
      arm64|aarch64)
        printf 'opencode-linux-arm64.tar.gz'
        ;;
      *)
        error "unsupported host architecture '$arch' for opencode download"
        ;;
    esac
}

fetch_latest_opencode_digest() {
    local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest"
    local target_asset
    target_asset=$(detect_opencode_asset_name)
    [[ -n $target_asset ]] || error "opencode binary name is not set"
    if ! command -v jq >/dev/null 2>&1; then
      error "jq is required to verify the opencode binary digest"
    fi
    local response
    if ! response=$(curl -fsSL "$api_url"); then
      error "failed to download opencode release metadata from GitHub"
    fi
    local digest_line
    digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
    if [[ -z $digest_line || $digest_line == "null" ]]; then
      error "failed to resolve opencode digest from GitHub response (looking for $target_asset)"
    fi
    digest_line=${digest_line#sha256:}
    printf '%s' "$digest_line"
}

ensure_codex_binary() {
   prepare_build_context
   if [[ -x $CODEX_BIN_PATH ]]; then
     return 0
   fi
   local tar_transform="s/${SLOPTRAP_CODEX_ARCHIVE}/${SLOPTRAP_CODEX_BIN_NAME}/"
   local download_dir
   download_dir=$(create_temp_dir "codex")
   local tmp_archive="$download_dir/codex.tar.gz"
   if $DRY_RUN; then
     print_command curl -Lso "$tmp_archive" "$SLOPTRAP_CODEX_URL"
     print_command sha256sum -c -
     print_command tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"
     print_command chmod 0755 "$CODEX_BIN_PATH"
     return 0
   fi
   if ! curl -Lso "$tmp_archive" "$SLOPTRAP_CODEX_URL"; then
     rm -rf "$download_dir"
     error "failed to download Codex binary from '$SLOPTRAP_CODEX_URL'"
   fi
   local expected_digest
   expected_digest=$(fetch_latest_codex_digest)
   if ! printf "%s  %s\n" "$expected_digest" "$tmp_archive" | sha256sum -c - >/dev/null 2>&1; then
     rm -rf "$download_dir" "$CODEX_BIN_PATH"
     error "checksum verification failed for $SLOPTRAP_CODEX_BIN_NAME"
   fi
   if ! tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"; then
     rm -rf "$download_dir"
     error "failed to extract Codex binary"
   fi
   rm -rf "$download_dir"
   chmod 0755 "$CODEX_BIN_PATH"
}

ensure_opencode_binary() {
   prepare_build_context
   if [[ -x $CODEX_BIN_PATH ]]; then
     return 0
   fi
   local target_asset
   target_asset=$(detect_opencode_asset_name)
   local download_dir
   download_dir=$(create_temp_dir "opencode")
   local tmp_archive="$download_dir/$target_asset"
   local opencode_url="https://github.com/anomalyco/opencode/releases/latest/download/${target_asset}"
   if $DRY_RUN; then
     print_command curl -Lso "$tmp_archive" "$opencode_url"
     print_command sha256sum -c -
     print_command tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"
     print_command chmod 0755 "$CODEX_BIN_PATH"
     return 0
   fi
   if ! curl -Lso "$tmp_archive" "$opencode_url"; then
     rm -rf "$download_dir"
     error "failed to download opencode binary from '$opencode_url'"
   fi
   local expected_digest
   expected_digest=$(fetch_latest_opencode_digest)
   if ! printf "%s  %s\n" "$expected_digest" "$tmp_archive" | sha256sum -c - >/dev/null 2>&1; then
     rm -rf "$download_dir" "$CODEX_BIN_PATH"
     error "checksum verification failed for $SLOPTRAP_CODEX_BIN_NAME"
   fi
   if ! tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"; then
     rm -rf "$download_dir"
     error "failed to extract opencode binary"
   fi
   rm -rf "$download_dir"
   chmod 0755 "$CODEX_BIN_PATH"
}

ensure_safe_sandbox() {
  local -a args=("$@")
  local sandbox_mode=""
  local i=0
  while [[ $i -lt ${#args[@]} ]]; do
    if [[ ${args[$i]} == "--sandbox" ]]; then
      if (( i + 1 >= ${#args[@]} )); then
        error "runtime '--sandbox' flag requires a mode (workspace-write, workspace-read-only, or danger-full-access)"
      fi
      sandbox_mode="${args[$((i + 1))]}"
    fi
    ((i+=1))
  done
  if [[ -z $sandbox_mode ]]; then
    error "runtime flags must include '--sandbox <mode>' (workspace-write, workspace-read-only, or danger-full-access)"
  fi
  case "$sandbox_mode" in
    workspace-write|workspace-read-only|danger-full-access)
      ;;
    *)
      error "sandbox mode '$sandbox_mode' is not allowed (expected workspace-write, workspace-read-only, or danger-full-access)"
      ;;
  esac
}

normalize_package_list() {
  local raw=$1
  raw="$(trim "$raw")"
  [[ -z $raw ]] && return 0
  local -a tokens=()
  read -r -a tokens <<< "$raw"
  local -a normalized=()
  local token
  for token in "${tokens[@]}"; do
    # Expand package aliases
    normalized+=("$(expand_package_alias "$token")")
  done
  printf '%s' "${normalized[*]}"
}

reject_removed_env_override() {
  local var=$1
  local reason=$2
  if printenv "$var" >/dev/null 2>&1; then
    error "environment override '$var' has been removed for security reasons (${reason})"
  fi
}

targets_need_build() {
  local -a targets=("$@")
  if [[ ${#targets[@]} -eq 0 ]]; then
    return 0
  fi
  local target
  for target in "${targets[@]}"; do
    case "$target" in
      build|rebuild|build-if-missing|run|login|shell|resume)
        return 0
        ;;
    esac
  done
  return 1
}

normalize_local_server_url() {
  local url=$1
  local replacement_host=$2
  if [[ $url =~ ^([A-Za-z][A-Za-z0-9+.-]*://)(localhost|127\.0\.0\.1|\[::1\])([:/?#].*)?$ ]]; then
    printf '%s%s%s' "${BASH_REMATCH[1]}" "$replacement_host" "${BASH_REMATCH[3]}"
    return 0
  fi
  printf '%s' "$url"
}

ensure_host_loopback_network_access() {
  if [[ $SLOPTRAP_NETWORK_NAME != slirp4netns* ]]; then
    return 0
  fi
  if [[ $SLOPTRAP_NETWORK_NAME == *allow_host_loopback=* ]]; then
    return 0
  fi
  if [[ $SLOPTRAP_NETWORK_NAME == "slirp4netns" ]]; then
    SLOPTRAP_NETWORK_NAME="slirp4netns:allow_host_loopback=true"
  else
    SLOPTRAP_NETWORK_NAME="${SLOPTRAP_NETWORK_NAME},allow_host_loopback=true"
  fi
}

prepare_container_runtime() {
  resolve_container_workdir
  SLOPTRAP_PACKAGES_EXTRA_RESOLVED=$PACKAGES_EXTRA
  SLOPTRAP_RUN_AS_ROOT=false
  SLOPTRAP_SHARED_DIR_ABS="$CODE_DIR"
  if [[ ! -d $SLOPTRAP_SHARED_DIR_ABS ]]; then
    error "shared directory '$SLOPTRAP_SHARED_DIR_ABS' does not exist"
  fi
  SLOPTRAP_SHARED_DIR_ABS="$(cd "$SLOPTRAP_SHARED_DIR_ABS" && pwd -P)"

  local dockerfile_override
  dockerfile_override=$(get_env_default "SLOPTRAP_DOCKERFILE_PATH" "")
  if [[ -n $dockerfile_override ]]; then
    if [[ $dockerfile_override != /* ]]; then
      dockerfile_override="$SCRIPT_DIR/$dockerfile_override"
    fi
    if [[ ! -f $dockerfile_override ]]; then
      error "container recipe '$dockerfile_override' not found"
    fi
    SLOPTRAP_DOCKERFILE_SOURCE="$dockerfile_override"
  elif [[ -f "$SCRIPT_DIR/Dockerfile.sloptrap" ]]; then
    SLOPTRAP_DOCKERFILE_SOURCE="$SCRIPT_DIR/Dockerfile.sloptrap"
  else
    SLOPTRAP_DOCKERFILE_SOURCE=""
  fi

  SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 wget git ripgrep xxd file procps util-linux")
  validate_package_list "SLOPTRAP_PACKAGES" "$SLOPTRAP_PACKAGES_BASE" "SLOPTRAP_PACKAGES"
  local default_codex_archive
  default_codex_archive=$(detect_codex_archive_name)
  local env_codex_archive
  env_codex_archive=$(printenv "SLOPTRAP_CODEX_ARCHIVE" 2>/dev/null || true)
  if [[ -n $env_codex_archive ]]; then
    SLOPTRAP_CODEX_ARCHIVE=$env_codex_archive
  else
    SLOPTRAP_CODEX_ARCHIVE=$default_codex_archive
  fi
  validate_codex_archive_name "$SLOPTRAP_CODEX_ARCHIVE"
  local default_codex_url="https://github.com/openai/codex/releases/latest/download/${SLOPTRAP_CODEX_ARCHIVE}.tar.gz"
  SLOPTRAP_CODEX_URL=$(get_env_default "SLOPTRAP_CODEX_URL" "$default_codex_url")
  if [[ -z $env_codex_archive ]]; then
    local inferred_archive
    inferred_archive=$(basename "${SLOPTRAP_CODEX_URL%%\?*}")
    inferred_archive=${inferred_archive%.tar.gz}
    if [[ $inferred_archive == codex-* ]]; then
      validate_codex_archive_name "$inferred_archive"
      SLOPTRAP_CODEX_ARCHIVE=$inferred_archive
    fi
  fi
  if [[ "$BACKEND" == "opencode" ]]; then
    SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "opencode")
  else
    SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex")
  fi
  SLOPTRAP_CODEX_HOME_CONT=$(get_env_default "SLOPTRAP_CODEX_HOME_CONT" "/codex")
  if [[ "$BACKEND" == "opencode" ]]; then
    OPENCODE_CONFIG_CONT="$SLOPTRAP_CODEX_HOME_CONT/config/opencode/opencode.json"
  fi
  SLOPTRAP_TOOLS_HOME_CONT="/sloptrap-tools"
  SLOPTRAP_TOOLS_BIN_CONT="$SLOPTRAP_TOOLS_HOME_CONT/bin"
  SLOPTRAP_RUNTIME_PATH="$SLOPTRAP_TOOLS_BIN_CONT:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  SLOPTRAP_TOOLS_VOLUME=$(sanitize_engine_name "${PROJECT_NAME}-sloptrap-tools-${CODEX_STATE_KEY:0:12}")
  SLOPTRAP_CODEX_UID=$(get_env_default "SLOPTRAP_CODEX_UID" "1337")
  SLOPTRAP_CODEX_GID=$(get_env_default "SLOPTRAP_CODEX_GID" "1337")
  local default_network="bridge"
  if [[ $CONTAINER_ENGINE == "podman" ]]; then
    if command -v slirp4netns >/dev/null 2>&1; then
      default_network="slirp4netns"
    else
      warn "podman detected but 'slirp4netns' is missing; falling back to 'bridge' networking"
    fi
  fi
  SLOPTRAP_NETWORK_NAME="$default_network"
  if $ALLOW_HOST_NETWORK; then
    SLOPTRAP_NETWORK_NAME="host"
  fi
  if [[ "$BACKEND" == "opencode" && $ALLOW_HOST_NETWORK == false ]]; then
    ensure_host_loopback_network_access
  fi
  SLOPTRAP_LIMITS_PID=$(get_env_default "SLOPTRAP_LIMITS_PID" "4096")
  SLOPTRAP_LIMITS_RAM=$(get_env_default "SLOPTRAP_LIMITS_RAM" "16384m")
  SLOPTRAP_LIMITS_SWP=$(get_env_default "SLOPTRAP_LIMITS_SWP" "16384m")
  SLOPTRAP_LIMITS_SHM=$(get_env_default "SLOPTRAP_LIMITS_SHM" "4096m")
  SLOPTRAP_LIMITS_CPU=$(get_env_default "SLOPTRAP_LIMITS_CPU" "8")
  SLOPTRAP_TMPFS_PATHS=$(get_env_default "SLOPTRAP_TMPFS_PATHS" "/tmp:exec /run /run/lock")
  SLOPTRAP_IMAGE_NAME=$(get_env_default "SLOPTRAP_IMAGE_NAME" "${PROJECT_NAME}-sloptrap-image")
  SLOPTRAP_CONTAINER_NAME=$(get_env_default "SLOPTRAP_CONTAINER_NAME" "${PROJECT_NAME}-sloptrap-container")
  SLOPTRAP_IMAGE_NAME=$(sanitize_engine_name "$SLOPTRAP_IMAGE_NAME")
  SLOPTRAP_CONTAINER_NAME=$(sanitize_engine_name "$SLOPTRAP_CONTAINER_NAME")

  # Setup opencode state paths
  if [[ "$BACKEND" == "opencode" ]]; then
    ensure_opencode_storage_paths
  fi

  local -a resource_opts=(
    --pids-limit "$SLOPTRAP_LIMITS_PID"
    --memory "$SLOPTRAP_LIMITS_RAM"
    --memory-swap "$SLOPTRAP_LIMITS_SWP"
    --shm-size "$SLOPTRAP_LIMITS_SHM"
    --cpus "$SLOPTRAP_LIMITS_CPU"
  )
  local -a tmpfs_opts=()
  if [[ -n $SLOPTRAP_TMPFS_PATHS ]]; then
    local -a tmpfs_paths=()
    read -r -a tmpfs_paths <<< "$SLOPTRAP_TMPFS_PATHS"
    local path
    for path in "${tmpfs_paths[@]}"; do
      tmpfs_opts+=(--tmpfs "$path")
    done
  fi

  security_opts+=(--cap-drop ALL --security-opt no-new-privileges)
  local -a rootfs_flag=(--read-only)

  if [[ $CONTAINER_ENGINE == "podman" ]]; then
    SLOPTRAP_VOLUME_LABEL=":Z"
  else
    SLOPTRAP_VOLUME_LABEL=""
  fi

  local -a volume_opts=(
    -v "$SLOPTRAP_SHARED_DIR_ABS:$SLOPTRAP_WORKDIR$SLOPTRAP_VOLUME_LABEL"
    -v "$CODEX_STATE_HOME_HOST:$SLOPTRAP_CODEX_HOME_CONT$SLOPTRAP_VOLUME_LABEL"
  )
  local -a mount_opts=(
    --mount "type=volume,source=$SLOPTRAP_TOOLS_VOLUME,target=$SLOPTRAP_TOOLS_HOME_CONT"
  )
  
  # Add opencode state mount if using opencode backend
  if [[ "$BACKEND" == "opencode" ]]; then
    volume_opts+=(-v "$OPENCODE_STATE_HOME_HOST:$SLOPTRAP_CODEX_HOME_CONT/state/opencode$SLOPTRAP_VOLUME_LABEL")
  fi

  local -a env_args=(
    -e "HOME=$SLOPTRAP_CODEX_HOME_CONT"
    -e "XDG_CONFIG_HOME=$SLOPTRAP_CODEX_HOME_CONT/config"
    -e "XDG_CACHE_HOME=$SLOPTRAP_CODEX_HOME_CONT/cache"
    -e "XDG_STATE_HOME=$SLOPTRAP_CODEX_HOME_CONT/state"
    -e "CODEX_HOME=$SLOPTRAP_CODEX_HOME_CONT"
    -e "PATH=$SLOPTRAP_RUNTIME_PATH"
    -e "SLOPTRAP_TOOLS_HOME=$SLOPTRAP_TOOLS_HOME_CONT"
    -e "SLOPTRAP_WORKDIR=$SLOPTRAP_WORKDIR"
    -e "SLOPTRAP_HELPER_DIR=/tmp/sloptrap-helper"
  )
  
  # Add opencode-specific environment variables
  if [[ "$BACKEND" == "opencode" ]]; then
    env_args+=(
      -e "OPENCODE_HOME=$SLOPTRAP_CODEX_HOME_CONT"
      -e "OPENCODE_CONFIG=$OPENCODE_CONFIG_CONT"
      -e "OPENCODE_SERVER=$OPENCODE_SERVER"
      -e "OPENCODE_MODEL=$OPENCODE_MODEL"
    )
  fi

  local uid gid user
  uid=$(id -u)
  gid=$(id -g)
  user=$(id -un 2>/dev/null || true)
  env_args+=(
    -e "SLOPTRAP_HOST_UID=$uid"
    -e "SLOPTRAP_HOST_GID=$gid"
  )
  if [[ -n $user ]]; then
    env_args+=(-e "SLOPTRAP_HOST_USER=$user")
  fi
  local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME")
  if [[ $ALLOW_HOST_NETWORK == false ]]; then
    SLOPTRAP_HOST_ALIAS="sloptrap.host"
    network_opts+=(--add-host "$SLOPTRAP_HOST_ALIAS:host-gateway")
    env_args+=(-e "SLOPTRAP_HOST_ALIAS=$SLOPTRAP_HOST_ALIAS")
    if [[ "$BACKEND" == "opencode" ]]; then
      OPENCODE_SERVER=$(normalize_local_server_url "$OPENCODE_SERVER" "$SLOPTRAP_HOST_ALIAS")
    fi
  else
    SLOPTRAP_HOST_ALIAS=""
  fi
  local -a user_opts=("--user" "$uid:$gid")
  if [[ $CONTAINER_ENGINE == "podman" ]]; then
    user_opts=(--userns="keep-id:uid=$uid,gid=$gid" "${user_opts[@]}")
  fi
  if $SLOPTRAP_RUN_AS_ROOT; then
    user_opts=()
    if [[ $CONTAINER_ENGINE == "podman" ]]; then
      user_opts=(--userns="keep-id:uid=$uid,gid=$gid")
    fi
  fi


  CONTAINER_SHARED_OPTS=(
    "${network_opts[@]}"
    "${security_opts[@]}"
    "${resource_opts[@]}"
    "${rootfs_flag[@]}"
    "${tmpfs_opts[@]}"
    "${mount_opts[@]}"
    "${volume_opts[@]}"
    "${env_args[@]}"
    "${IGNORE_MOUNT_ARGS[@]}"
    "${user_opts[@]}"
    -w "$SLOPTRAP_WORKDIR"
  )

  BASE_CONTAINER_CMD=(
    "$CONTAINER_ENGINE" run --rm -it
    --name "$SLOPTRAP_CONTAINER_NAME"
    "${CONTAINER_SHARED_OPTS[@]}"
  )
}

build_image() {
    prepare_build_context
    if [[ "$BACKEND" == "opencode" ]]; then
      ensure_opencode_binary
    else
      ensure_codex_binary
    fi
    if [[ $SKIP_BUILD_BANNER != true ]]; then
      print_banner
    fi
    print_manifest_summary
    local extra_packages_arg
    extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED")
    if ! $DRY_RUN; then
      status_line "Building %s\n" "$SLOPTRAP_IMAGE_NAME"
    fi
    local binary_build_arg_name="CODEX_BIN"
    if [[ "$BACKEND" == "opencode" ]]; then
      binary_build_arg_name="OPENCODE_BIN"
    fi
    local -a cmd=(
      "$CONTAINER_ENGINE" build --quiet
      -t "$SLOPTRAP_IMAGE_NAME"
      -f "$SLOPTRAP_DOCKERFILE_PATH"
      --network "$SLOPTRAP_NETWORK_NAME"
      --label "$SLOPTRAP_IMAGE_LABEL"
      --build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE"
      --build-arg "$binary_build_arg_name=$SLOPTRAP_CODEX_BIN_NAME"
      --build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID"
      --build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID"
    )
    if [[ -n $extra_packages_arg ]]; then
      cmd+=(--build-arg "EXTRA_PACKAGES=$extra_packages_arg")
    fi
    cmd+=("$SLOPTRAP_BUILD_CONTEXT")
    if $DRY_RUN; then
      run_or_print "${cmd[@]}"
      return
    fi

    local build_output
    if ! build_output=$("${cmd[@]}"); then
      return 1
    fi
    build_output=$(trim "$build_output")
    if [[ -n $build_output ]]; then
      comment_line "Image %s\n" "$build_output"
    fi
}

rebuild_image() {
    prepare_build_context
    if [[ "$BACKEND" == "opencode" ]]; then
      ensure_opencode_binary
    else
      ensure_codex_binary
   fi
   if [[ $SKIP_BUILD_BANNER != true ]]; then
    print_banner
  fi
  print_manifest_summary
  if ! $DRY_RUN; then
    status_line "Rebuilding %s (no cache)\n" "$SLOPTRAP_IMAGE_NAME"
  fi
  local extra_packages_arg
  extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED")
  local binary_build_arg_name="CODEX_BIN"
  if [[ "$BACKEND" == "opencode" ]]; then
    binary_build_arg_name="OPENCODE_BIN"
  fi
  local -a cmd=(
    "$CONTAINER_ENGINE" build --no-cache --quiet
    -t "$SLOPTRAP_IMAGE_NAME"
    -f "$SLOPTRAP_DOCKERFILE_PATH"
    --network "$SLOPTRAP_NETWORK_NAME"
    --label "$SLOPTRAP_IMAGE_LABEL"
    --build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE"
    --build-arg "$binary_build_arg_name=$SLOPTRAP_CODEX_BIN_NAME"
    --build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID"
    --build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID"
  )
  if [[ -n $extra_packages_arg ]]; then
    cmd+=(--build-arg "EXTRA_PACKAGES=$extra_packages_arg")
  fi
  cmd+=("$SLOPTRAP_BUILD_CONTEXT")
  if $DRY_RUN; then
    run_or_print "${cmd[@]}"
    return
  fi

  local build_output
  if ! build_output=$("${cmd[@]}"); then
    return 1
  fi
  build_output=$(trim "$build_output")
  if [[ -n $build_output ]]; then
    comment_line "Image %s\n" "$build_output"
  fi
}

build_if_missing() {
  if $DRY_RUN; then
    print_command "$CONTAINER_ENGINE" image inspect "$SLOPTRAP_IMAGE_NAME"
    return 0
  fi
  if "$CONTAINER_ENGINE" image inspect "$SLOPTRAP_IMAGE_NAME" >/dev/null 2>&1; then
    local created
    if created=$("$CONTAINER_ENGINE" image inspect --format '{{.Created}}' "$SLOPTRAP_IMAGE_NAME" 2>/dev/null); then
      local created_epoch
      created_epoch=$(date -d "$created" +%s 2>/dev/null || true)
      if [[ -n $created_epoch ]]; then
        local now_epoch
        now_epoch=$(date +%s)
        local age_days=$(( (now_epoch - created_epoch) / 86400 ))
        if (( age_days > 30 )); then
          warn "image '$SLOPTRAP_IMAGE_NAME' is ${age_days} days old; rebuilding"
          rebuild_image
        fi
      fi
    fi
    return 0
  fi
  build_image
}

stop_container() {
  if $DRY_RUN; then
    print_command "$CONTAINER_ENGINE" stop "$SLOPTRAP_CONTAINER_NAME"
    return 0
  fi
  "$CONTAINER_ENGINE" stop "$SLOPTRAP_CONTAINER_NAME" >/dev/null 2>&1 || true
}

clean_environment() {
  if ! $DRY_RUN; then
    status_line "Cleaning %s\n" "$PROJECT_NAME"
  fi
  local helper_root="$CODE_DIR/.sloptrap-ignores"
  if [[ -L $helper_root ]]; then
    error "$helper_root: helper directory may not be a symlink"
  fi
  assert_path_within_code_dir "$helper_root"
  stop_container
  if $DRY_RUN; then
    print_command "$CONTAINER_ENGINE" rm -f "$SLOPTRAP_CONTAINER_NAME"
    print_command "$CONTAINER_ENGINE" rmi "$SLOPTRAP_IMAGE_NAME"
    print_command "$CONTAINER_ENGINE" volume rm -f "$SLOPTRAP_TOOLS_VOLUME"
    print_command rm -rf "$helper_root"
    return 0
  fi
  "$CONTAINER_ENGINE" rm -f "$SLOPTRAP_CONTAINER_NAME" >/dev/null 2>&1 || true
  "$CONTAINER_ENGINE" rmi "$SLOPTRAP_IMAGE_NAME" >/dev/null 2>&1 || true
  "$CONTAINER_ENGINE" volume rm -f "$SLOPTRAP_TOOLS_VOLUME" >/dev/null 2>&1 || true
  rm -rf "$helper_root"
}

prune_sloptrap_images() {
  if ! $DRY_RUN; then
    status_line "Pruning unused sloptrap images\n"
  fi
  local -a cmd=(
    "$CONTAINER_ENGINE" image prune --force --all
    --filter "label=$SLOPTRAP_IMAGE_LABEL"
  )
  run_or_print "${cmd[@]}"
}

run_codex_command() {
    local -a extra_args=("$@")
    local -a source_args=("$SLOPTRAP_IMAGE_NAME")
    local -a auth_mount=()
    if [[ "$BACKEND" == "opencode" ]]; then
      ensure_opencode_storage_paths
      ensure_opencode_config
    else
      ensure_codex_storage_paths
      append_auth_mount_arg false auth_mount
    fi
    local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}")
    if [[ "$BACKEND" == "opencode" ]]; then
      true
    else
      if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then
        cmd+=("${CODEX_ARGS_ARRAY[@]}")
      fi
    fi
    if [[ ${#extra_args[@]} -gt 0 ]]; then
      cmd+=("${extra_args[@]}")
    fi
    run_runtime_container_cmd "${cmd[@]}"
}

run_codex() {
  if ! $DRY_RUN; then
    status_line "Running %s\n" "$SLOPTRAP_IMAGE_NAME"
  fi
  local runtime_prompt
  runtime_prompt=$(build_runtime_context_prompt)
  if [[ "$BACKEND" == "opencode" ]]; then
    run_codex_command --prompt "$runtime_prompt"
  else
    run_codex_command "$runtime_prompt"
  fi
}

run_login_target() {
  if [[ "$BACKEND" == "opencode" ]]; then
    error "target 'login' is only supported for the codex backend"
  fi
  ensure_codex_storage_paths
  local -a source_args=("$SLOPTRAP_IMAGE_NAME")
  local -a auth_mount=()
  if ! $DRY_RUN; then
    status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME"
  fi
  append_auth_mount_arg true auth_mount
  local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" login)
  run_runtime_container_cmd "${cmd[@]}"
}

run_shell_target() {
  if [[ "$BACKEND" == "opencode" ]]; then
    ensure_opencode_storage_paths
  else
    ensure_codex_storage_paths
  fi
  local -a source_args=("$SLOPTRAP_IMAGE_NAME")
  local -a auth_mount=()
  if ! $DRY_RUN; then
    status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME"
  fi
  if [[ "$BACKEND" != "opencode" ]]; then
    append_auth_mount_arg false auth_mount
  fi
  local -a cmd=("${BASE_CONTAINER_CMD[@]}" --entrypoint /bin/bash "${auth_mount[@]}" "${source_args[@]}")
  run_runtime_container_cmd "${cmd[@]}"
}

run_resume_target() {
  local session_id=$1
  if ! $DRY_RUN; then
    status_line "Resume %s (%s)\n" "$SLOPTRAP_IMAGE_NAME" "$session_id"
  fi
  if [[ "$BACKEND" == "opencode" ]]; then
    run_codex_command --session "$session_id"
  else
    run_codex_command resume "$session_id"
  fi
}

process_resume_target() {
  local session_id=$1
  if [[ -z $session_id ]]; then
    error "target 'resume' requires a session identifier"
  fi
  build_if_missing
  run_resume_target "$session_id"
}

dispatch_target() {
  local target=$1
  case "$target" in
    build)
      build_image
      ;;
    rebuild)
      rebuild_image
      ;;
    build-if-missing)
      build_if_missing
      ;;
    run)
      build_if_missing
      run_codex
      ;;
    login)
      build_if_missing
      run_login_target
      ;;
    shell)
      build_if_missing
      run_shell_target
      ;;
    wizard)
      run_wizard "$MANIFEST_PATH"
      exit 0
      ;;
    stop)
      stop_container
      ;;
    clean)
      clean_environment
      ;;
    prune)
      prune_sloptrap_images
      ;;
    *)
      error "unknown target '$target'"
      ;;
  esac
}

DRY_RUN=false
PRINT_CONFIG=false
SKIP_BUILD_BANNER=false

while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run)
      DRY_RUN=true
      shift
      ;;
    --print-config)
      PRINT_CONFIG=true
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    --)
      shift
      break
      ;;
    -*)
      usage >&2
      error "unknown flag '$1'"
      ;;
    *)
      break
      ;;
  esac
done

if [[ $# -lt 1 ]]; then
  usage >&2
  exit 1
fi

CODE_DIR_INPUT=$1
shift

if [[ ! -d $CODE_DIR_INPUT ]]; then
  error "code directory '$CODE_DIR_INPUT' does not exist"
fi

CODE_DIR="$(cd "$CODE_DIR_INPUT" && pwd -P)"
if [[ $CODE_DIR == "/" ]]; then
  error "project root may not be '/'"
fi
MANIFEST_PATH="$CODE_DIR/$MANIFEST_BASENAME"
TARGETS_INPUT=("$@")

if [[ -f $MANIFEST_PATH ]]; then
  MANIFEST_PRESENT=true
  parse_manifest "$MANIFEST_PATH"
fi

if [[ ${#TARGETS_INPUT[@]} -gt 0 ]]; then
  target_index=0
  while (( target_index < ${#TARGETS_INPUT[@]} )); do
    if [[ ${TARGETS_INPUT[$target_index]} == "wizard" ]]; then
      if (( ${#TARGETS_INPUT[@]} > 1 )); then
        warn "wizard runs standalone; ignoring other targets"
      fi
      run_wizard "$MANIFEST_PATH"
      exit 0
    fi
    ((target_index+=1))
  done
fi

if [[ ! -f $MANIFEST_PATH ]]; then
  if targets_need_build "${TARGETS_INPUT[@]}"; then
    if [[ -t 0 ]]; then
      run_wizard "$MANIFEST_PATH"
      SKIP_BUILD_BANNER=true
      MANIFEST=()
      MANIFEST_PRESENT=true
      parse_manifest "$MANIFEST_PATH"
    else
      warn "missing $MANIFEST_BASENAME; proceeding with defaults (run '$0 $CODE_DIR wizard' to create one)"
    fi
  fi
fi

PROJECT_NAME=${MANIFEST[name]-$(basename "$CODE_DIR")}
if [[ -z $PROJECT_NAME ]]; then
  error "$MANIFEST_PATH: project name resolved to empty string"
fi
if [[ ! $PROJECT_NAME =~ $VALID_NAME_REGEX ]]; then
  error "$MANIFEST_PATH: invalid project name '$PROJECT_NAME' (allowed: letters, digits, ., _, -)"
fi

select_codex_home "$CODE_DIR"
ensure_ignore_helper_root
IGNORE_STUB_BASE="$IGNORE_HELPER_ROOT/session-${BASHPID:-$$}"
resolve_sloptrap_ignore "$CODE_DIR"
resolve_container_workdir

TARGETS=("${TARGETS_INPUT[@]}")
if [[ ${#TARGETS[@]} -eq 0 ]]; then
  TARGETS=("run")
fi

DEFAULT_TARGETS=("${TARGETS[@]}")

PACKAGES_EXTRA=$(normalize_package_list "${MANIFEST[packages_extra]:-}")
if [[ -n ${MANIFEST[allow_host_network]-} ]]; then
  case "${MANIFEST[allow_host_network],,}" in
    1|true|yes)
      ALLOW_HOST_NETWORK=true
      ;;
    0|false|no)
      ALLOW_HOST_NETWORK=false
      ;;
    *)
      error "$MANIFEST_PATH: allow_host_network must be true or false (got '${MANIFEST[allow_host_network]}')"
      ;;
  esac
fi

forbidden_keys=(codex_args container_opts_extra security_opts_extra env_extra env_passthrough default_targets default_target)
for forbidden_key in "${forbidden_keys[@]}"; do
  if [[ -n ${MANIFEST[$forbidden_key]-} ]]; then
    if [[ $forbidden_key == "codex_args" ]]; then
      error "$MANIFEST_PATH: key 'codex_args' has been deprecated; sloptrap now always uses '$DEFAULT_CODEX_ARGS_DISPLAY'"
    fi
    error "$MANIFEST_PATH: key '$forbidden_key' has been removed for security reasons"
  fi
done

if [[ -n $PACKAGES_EXTRA ]]; then
  ensure_safe_for_make "packages_extra" "$PACKAGES_EXTRA"
  validate_package_list "packages_extra" "$PACKAGES_EXTRA"
fi

select_backend() {
  local manifest_agent="${MANIFEST[agent]:-codex}"
  local env_agent="${SLOPTRAP_AGENT:-}"
  
  [[ -n $env_agent ]] && manifest_agent="$env_agent"
  
  case "${manifest_agent,,}" in
    opencode) BACKEND="opencode" ;;
    codex) BACKEND="codex" ;;
    *) error "$MANIFEST_PATH: agent must be 'codex' or 'opencode' (got '$manifest_agent')" ;;
  esac
}

select_backend

if [[ "$BACKEND" == "opencode" ]]; then
  OPENCODE_SERVER=$(get_env_default "SLOPTRAP_OPENCODE_SERVER" "${MANIFEST[opencode_server]:-http://localhost:8080}")
  OPENCODE_MODEL=$(get_env_default "SLOPTRAP_OPENCODE_MODEL" "${MANIFEST[opencode_model]:-bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0}")
  OPENCODE_CONTEXT=$(get_env_default "SLOPTRAP_OPENCODE_CONTEXT" "${MANIFEST[opencode_context]:-256K}")
  parse_opencode_context "$OPENCODE_CONTEXT" >/dev/null
  OPENCODE_STATE_HOME_HOST="$CODEX_STATE_HOME_HOST/opencode"
  OPENCODE_CONFIG_HOST="$CODEX_STATE_HOME_HOST/config/opencode/opencode.json"
  OPENCODE_CONFIG_CONT=""
else
  OPENCODE_SERVER=""
  OPENCODE_MODEL=""
  OPENCODE_CONTEXT=""
  OPENCODE_STATE_HOME_HOST=""
  OPENCODE_CONFIG_HOST=""
  OPENCODE_CONFIG_CONT=""
fi

NEED_LOGIN=false
if [[ "$BACKEND" != "opencode" && ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
  NEED_LOGIN=true
fi

CONTAINER_ENGINE="$(detect_container_engine)"
reject_removed_env_override "SLOPTRAP_SECURITY_OPTS_EXTRA" "arbitrary runtime security flags must not bypass launcher hardening"
reject_removed_env_override "SLOPTRAP_ROOTFS_READONLY" "the runtime root filesystem is always mounted read-only"
reject_removed_env_override "SLOPTRAP_NETWORK_NAME" "network selection is derived by the launcher and manifest only"
reject_removed_env_override "SLOPTRAP_DOCKERFILE_PATH" "custom image recipes bypass the launcher hardening model"
reject_removed_env_override "SLOPTRAP_CODEX_URL" "backend download URLs are fixed to the verified release source"
reject_removed_env_override "SLOPTRAP_CODEX_ARCHIVE" "backend archive selection is fixed by the launcher"
reject_removed_env_override "SLOPTRAP_CODEX_BIN" "backend binary naming is fixed by the launcher"
CODEX_ARGS_ARRAY=("${DEFAULT_CODEX_ARGS[@]}")
ensure_safe_sandbox "${CODEX_ARGS_ARRAY[@]}"
CODEX_ARGS_DISPLAY=$DEFAULT_CODEX_ARGS_DISPLAY

prepare_ignore_mounts "$CODE_DIR"
prepare_container_runtime

if $PRINT_CONFIG; then
  print_config "$MANIFEST_PATH"
  exit 0
fi

AUTO_LOGIN=false
if [[ $NEED_LOGIN == true ]]; then
  AUTO_LOGIN=true
  for tgt in "${TARGETS[@]}"; do
    if [[ $tgt == "login" ]]; then
      AUTO_LOGIN=false
      break
    fi
  done
fi

if $AUTO_LOGIN; then
  if ! $DRY_RUN; then
    status_line "Codex login required (%s)\n" "$CODEX_AUTH_FILE_HOST"
  fi
  dispatch_target login
fi

target_index=0
while (( target_index < ${#TARGETS[@]} )); do
  current_target="${TARGETS[$target_index]}"
  if [[ $current_target == "resume" ]]; then
    if (( target_index + 1 >= ${#TARGETS[@]} )); then
      error "target 'resume' requires a session identifier"
    fi
    process_resume_target "${TARGETS[$((target_index + 1))]}"
    ((target_index+=2))
    continue
  fi
  dispatch_target "$current_target"
  ((target_index+=1))
done
