#!/usr/bin/env bash
# appctl — convenience wrapper for the dev/prod compose split.
#
# Replaces the old Makefile. The dev compose chain layers
# docker-compose.dev.yml on top of docker-compose.yml so source bind
# mounts and the css-watcher sidecar only show up for `appctl dev …`.
# Prod uses docker-compose.yml alone.

set -euo pipefail

# Resolve the repo root (parent of bin/) regardless of where the user
# invokes us from, so cd into the right place even when called via the
# top-level ./appctl symlink.
SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
REPO_ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
cd "$REPO_ROOT"

# Exported so the css-watcher service can run as the host user via
# `user: "${HOST_UID}:${HOST_GID}"` in docker-compose.dev.yml. Files
# the watcher writes into bind-mounted host paths
# (public/assets/css/app.css, public/assets/js/vendor/*) then land
# with normal ownership.
export HOST_UID="${HOST_UID:-$(id -u)}"
export HOST_GID="${HOST_GID:-$(id -g)}"

COMPOSE_PROD=(docker compose)
COMPOSE_DEV=(docker compose -f docker-compose.yml -f docker-compose.dev.yml)

usage() {
    cat <<'EOF'
appctl — Sprint Planner dev/prod/test wrapper

USAGE
  appctl <command> [subcommand]

DEV
  appctl dev start       start dev stack (app + css-watcher) in foreground
  appctl dev stop        stop and remove dev containers
  appctl dev build       rebuild dev images
  appctl dev shell       bash into the running app container
  appctl dev logs        tail logs from the dev stack

PROD
  appctl prod start          start prod stack detached
  appctl prod stop           stop and remove prod containers
  appctl prod build          rebuild prod images
  appctl prod upgrade [VER]  stop, fetch, checkout VER, rebuild, start
                             VER defaults to the `latest` git tag;
                             `test` is a virtual keyword that resolves
                             to origin/main HEAD after fetch.

CHECKS (one-shot containers, no running stack required)
  appctl lint            php -l on src/ + tests/
  appctl test            phpunit
  appctl check           lint + test (used by the /check Claude Code skill)

OTHER
  appctl completion      print the bash completion script to stdout
  appctl help            show this message
EOF
}

die() {
    printf 'appctl: %s\n' "$*" >&2
    exit 2
}

# --- bash completion auto-source on first run --------------------------
#
# We ship bin/appctl-completion.bash next to this script. On the very
# first interactive invocation we offer to add a `source` line to the
# user's ~/.bashrc — only once, gated by a marker file in $HOME so the
# prompt never appears again, and only when stdin is a TTY (CI / piped
# invocations are silent).
maybe_offer_completion() {
    [[ -t 0 && -t 1 ]] || return 0
    [[ "${APPCTL_NO_COMPLETION_PROMPT:-}" != "1" ]] || return 0

    local marker="${HOME}/.config/appctl/completion-installed"
    [[ ! -e "$marker" ]] || return 0

    local completion_script="${REPO_ROOT}/bin/appctl-completion.bash"
    [[ -f "$completion_script" ]] || return 0

    local bashrc="${HOME}/.bashrc"
    if [[ -f "$bashrc" ]] && grep -Fq "appctl-completion.bash" "$bashrc"; then
        # Already wired up by a previous repo checkout; just write the
        # marker so we stop asking.
        mkdir -p "$(dirname "$marker")"
        : > "$marker"
        return 0
    fi

    printf '\n[appctl] bash completion is not yet installed.\n'
    printf '        Add `source %s` to %s? [y/N] ' \
        "$completion_script" "$bashrc"
    local reply
    read -r reply || reply=""
    case "$reply" in
        y|Y|yes|YES)
            mkdir -p "$(dirname "$marker")"
            {
                printf '\n# appctl bash completion (added by appctl on first run)\n'
                printf 'source %q\n' "$completion_script"
            } >> "$bashrc"
            : > "$marker"
            printf '[appctl] added. Open a new shell or `source %s` to activate.\n\n' "$bashrc"
            ;;
        *)
            mkdir -p "$(dirname "$marker")"
            : > "$marker"
            printf '[appctl] skipped. Re-run with APPCTL_NO_COMPLETION_PROMPT=1 to silence; delete %s to ask again.\n\n' "$marker"
            ;;
    esac
}

# --- subcommands -------------------------------------------------------

cmd_dev() {
    local sub="${1:-}"
    case "$sub" in
        start)  "${COMPOSE_DEV[@]}" up ;;
        stop)   "${COMPOSE_DEV[@]}" down ;;
        build)  "${COMPOSE_DEV[@]}" build ;;
        shell)  "${COMPOSE_DEV[@]}" exec app bash ;;
        logs)   "${COMPOSE_DEV[@]}" logs -f ;;
        ""|help|-h|--help) usage ;;
        *) die "unknown dev subcommand: $sub (try: start|stop|build|shell|logs)" ;;
    esac
}

cmd_prod() {
    local sub="${1:-}"
    shift || true
    case "$sub" in
        start)    "${COMPOSE_PROD[@]}" up -d ;;
        stop)     "${COMPOSE_PROD[@]}" down ;;
        build)    "${COMPOSE_PROD[@]}" build ;;
        upgrade)  cmd_prod_upgrade "$@" ;;
        ""|help|-h|--help) usage ;;
        *) die "unknown prod subcommand: $sub (try: start|stop|build|upgrade)" ;;
    esac
}

# Pinned redeploy. Resolves VER (default `latest` tag; `test` ⇒ origin/main
# HEAD after fetch) to a commit, then runs: stop → fetch → checkout →
# build → start. Dirty trees prompt before any destructive step.
cmd_prod_upgrade() {
    local version="${1:-latest}"

    if [[ -n "$(git status --porcelain)" ]]; then
        printf 'appctl: working tree has uncommitted changes:\n' >&2
        git status --short >&2
        printf '\nThe checkout step will fail or clobber these. Continue anyway? [y/N] ' >&2
        local reply
        read -r reply || reply=""
        case "$reply" in
            y|Y|yes|YES) ;;
            *) die "aborted by user (working tree dirty)" ;;
        esac
    fi

    printf 'appctl: git fetch --tags origin...\n'
    git fetch --tags origin

    local target_ref
    if [[ "$version" == "test" ]]; then
        target_ref="origin/main"
    else
        target_ref="$version"
    fi

    if ! git rev-parse --verify --quiet "${target_ref}^{commit}" >/dev/null; then
        die "unknown version: '$version' (expected a release tag, 'latest', or 'test')"
    fi
    local target_sha
    target_sha="$(git rev-parse --short "${target_ref}^{commit}")"
    printf 'appctl: upgrading to %s (%s)\n' "$version" "$target_sha"

    printf 'appctl: stopping prod stack...\n'
    "${COMPOSE_PROD[@]}" down

    printf 'appctl: git checkout --detach %s...\n' "$target_ref"
    git checkout --detach "$target_ref"

    printf 'appctl: building prod images...\n'
    "${COMPOSE_PROD[@]}" build

    printf 'appctl: starting prod stack...\n'
    "${COMPOSE_PROD[@]}" up -d

    printf 'appctl: upgrade to %s (%s) complete.\n' "$version" "$target_sha"
}

cmd_lint() {
    "${COMPOSE_DEV[@]}" --profile test run --rm tests \
        sh -c 'find src tests -name "*.php" -print0 | xargs -0 -n1 -P 4 php -l > /dev/null && echo "lint: OK"'
}

cmd_test() {
    "${COMPOSE_DEV[@]}" --profile test run --rm tests
}

cmd_check() {
    cmd_lint
    cmd_test
}

cmd_completion() {
    local completion_script="${REPO_ROOT}/bin/appctl-completion.bash"
    [[ -f "$completion_script" ]] || die "completion script missing: $completion_script"
    cat "$completion_script"
}

# --- dispatch ----------------------------------------------------------

maybe_offer_completion

cmd="${1:-help}"
shift || true

case "$cmd" in
    dev)         cmd_dev "$@" ;;
    prod)        cmd_prod "$@" ;;
    lint)        cmd_lint ;;
    test)        cmd_test ;;
    check)       cmd_check ;;
    completion)  cmd_completion ;;
    help|-h|--help|"") usage ;;
    *) die "unknown command: $cmd (try: appctl help)" ;;
esac
