#!/usr/bin/env bash
# Switch how this app consumes boboko-core, then restart the dev stack so the
# entrypoints re-resolve it.
#
#   bin/core-mode local   ../boboko-core checkout (Composer path repo + npm file:)
#   bin/core-mode repo    tagged releases from the forge (Composer VCS repo + npm git+semver)
#   bin/core-mode         print the current mode
#
# Running it for the mode you're already in skips the file edits but still
# restarts the stack — that's how repo mode picks up a newly pushed tag.
#
# The toggle lives in composer.json ("repositories" <-> "_repositories") and
# package.json (@boboko/core in "dependencies" <-> "_dependencies"). The dev
# entrypoints run `composer update boboko/*` and `npm update @boboko/core` on
# every boot, which rewrites composer.lock / package-lock.json for the mode.
set -euo pipefail

cd "$(dirname "$0")/.."

fail() { echo "!!! $*" >&2; exit 1; }

# Escape a string for use as a sed regex / replacement with "|" as delimiter.
sed_pattern() { printf '%s' "$1" | sed 's/[][\.*^$|]/\\&/g'; }
sed_replacement() { printf '%s' "$1" | sed 's/[\&|]/\\&/g'; }

current_mode() {
    local composer_type npm_dep composer_mode npm_mode
    # The live block is "repositories" — the leading quote keeps "_repositories" out.
    composer_type=$(sed -n '/"repositories": \[/,/"type"/p' composer.json | grep -o '"type": "[a-z]*"' | head -1)
    npm_dep=$(sed -n '/"dependencies": {/,/}/p' package.json | grep -o '"@boboko/core": "[^"]*"' | head -1)

    case "$composer_type" in
        *'"path"') composer_mode=local ;;
        *'"vcs"') composer_mode=repo ;;
        *) fail "Can't tell the mode from composer.json's \"repositories\" block." ;;
    esac
    case "$npm_dep" in
        *'"file:'*) npm_mode=local ;;
        *'"git+'*) npm_mode=repo ;;
        *) fail "Can't tell the mode from package.json's @boboko/core dependency." ;;
    esac
    [ "$composer_mode" = "$npm_mode" ] ||
        fail "composer.json ($composer_mode) and package.json ($npm_mode) disagree — fix by hand first."

    echo "$composer_mode"
}

swap_files() {
    # composer.json: swap the key names, values stay where they are.
    sed -i \
        -e 's/"_repositories":/"__core_mode_swap__":/' \
        -e 's/"repositories":/"_repositories":/' \
        -e 's/"__core_mode_swap__":/"repositories":/' \
        composer.json

    # package.json: swap the two @boboko/core values (live and parked).
    local a b
    a=$(grep -o '"@boboko/core": "[^"]*"' package.json | sed -n 1p)
    b=$(grep -o '"@boboko/core": "[^"]*"' package.json | sed -n 2p)
    [ -n "$a" ] && [ -n "$b" ] || fail "Expected two @boboko/core entries in package.json."
    sed -i \
        -e "s|$(sed_pattern "$a")|__core_mode_swap__|" \
        -e "s|$(sed_pattern "$b")|$(sed_replacement "$a")|" \
        -e "s|__core_mode_swap__|$(sed_replacement "$b")|" \
        package.json
}

wrapper_for() {
    if [ "$1" = local ]; then echo bin/dc-core.sh; else echo bin/dc; fi
}

# The app healthcheck (php-fpm -t) passes before the entrypoint finishes, so
# wait on the entrypoints' own log lines instead: the app's composer block is
# done once it clears caches, the vite container's npm block once Vite is up.
wait_for_log() {
    local dc=$1 service=$2 pattern=$3 label=$4 waited=0
    printf '>>> Waiting for %s' "$label"
    until $dc logs --no-color "$service" 2>&1 | grep -q "$pattern"; do
        if [ -z "$($dc ps -q --status running "$service")" ]; then
            echo
            $dc logs --no-color --tail 40 "$service"
            fail "$service stopped before finishing (logs above)."
        fi
        if [ "$waited" -ge 600 ]; then
            echo
            $dc logs --no-color --tail 40 "$service"
            fail "Timed out after 600s waiting for $service (logs above)."
        fi
        printf '.'
        sleep 3
        waited=$((waited + 3))
    done
    echo " done"
}

mode=$(current_mode)

if [ $# -eq 0 ]; then
    echo "$mode"
    exit 0
fi

target=$1
[ "$target" = local ] || [ "$target" = repo ] || fail "Usage: bin/core-mode [local|repo]"
if [ "$target" = local ] && [ ! -d ../boboko-core ]; then
    fail "../boboko-core not found — local mode needs the core checkout next to this repo."
fi

old_dc=$(wrapper_for "$mode")
new_dc=$(wrapper_for "$target")

echo ">>> Stopping stack ($mode mode)..."
$old_dc down

if [ "$mode" != "$target" ]; then
    echo ">>> Switching composer.json / package.json: $mode -> $target"
    swap_files
    [ "$(current_mode)" = "$target" ] || fail "Switch didn't take — check composer.json / package.json."
else
    echo ">>> Already in $target mode, restarting to re-resolve boboko-core"
fi

# The entrypoints run `composer install` / `npm install` from the lockfiles
# BEFORE re-resolving core. Local-mode lockfiles point at ../boboko-core,
# which isn't mounted in repo mode, so the install fails. The committed
# lockfiles are always repo mode — restore those and let the entrypoints bump
# them to the newest tag.
if [ "$target" = repo ]; then
    if grep -A14 '"name": "boboko/core"' composer.lock | grep -q '"type": "path"' ||
        grep -A6 '"node_modules/@boboko/core": {' package-lock.json | grep -q '"link": true'; then
        echo ">>> Lockfiles still point at ../boboko-core — restoring the committed ones"
        git checkout HEAD -- composer.lock package-lock.json
    fi
fi

echo ">>> Starting stack ($target mode)..."
if ! $new_dc up -d; then
    $new_dc logs --no-color --tail 40 app
    fail "Stack failed to start (app logs above)."
fi

wait_for_log "$new_dc" app '\[entrypoint\] Caches cleared' "composer to re-resolve boboko/core"
wait_for_log "$new_dc" vite 'ready in' "npm to re-resolve @boboko/core"

version=$(grep -A1 '"name": "boboko/core"' composer.lock | grep -o '"version": "[^"]*"' | cut -d'"' -f4)
echo ">>> Now in $target mode — boboko/core $version"
