#!/usr/bin/env bash
#
# Create a git worktree for pharmacy-starter with a self-contained local
# Docker stack (unique compose project, container names, port block and
# database), then bring it up fully seeded via ./start.sh.
#
# Usage:
#   ./scripts/worktree-create.sh
#   ./scripts/worktree-create.sh "MM-938578" "Fix basket total"
#   ./scripts/worktree-create.sh "MM-938578" "Fix basket total" bugfix
#   ./scripts/worktree-create.sh --existing feature/my-branch
#
# Arguments (all optional - prompts if not provided):
#   $1  Ticket ID / prefix  (e.g. MM-938578)
#   $2  Feature name        (e.g. "Fix basket total")
#   $3  Branch type         feature | bugfix | hotfix | refactor
#
# Flags:
#   --existing, -e <branch>  Attach an existing branch to a new worktree
#
# Remove a worktree with: ./scripts/worktree-teardown.sh <dir-name>

set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

REPO_ROOT="$(git rev-parse --show-toplevel)"
PARENT_DIR="$REPO_ROOT/worktrees"
mkdir -p "$PARENT_DIR"

# ── --existing flag ──────────────────────────────────────────────────────────
if [[ "${1:-}" == "--existing" || "${1:-}" == "-e" ]]; then
    [[ -z "${2:-}" ]] && { echo -e "${RED}Error: --existing requires a branch name${NC}"; exit 1; }
    BRANCH_NAME="$2"
    DIR_NAME="$(echo "$BRANCH_NAME" | sed 's|^[^/]*/||')"
    WORKTREE_DIR="${PARENT_DIR}/${DIR_NAME}"
    PREFIX=""; KEBAB_NAME="$DIR_NAME"; TICKET_ID=""; TICKET_TITLE=""
    [[ -d "$WORKTREE_DIR" ]] && { echo -e "${RED}Error: $WORKTREE_DIR already exists${NC}"; exit 1; }
    echo -e "${YELLOW}Attaching existing branch $BRANCH_NAME…${NC}"
    git worktree add "$WORKTREE_DIR" "$BRANCH_NAME"
    EXISTING_MODE=true
else
    EXISTING_MODE=false
fi

# ── Argument parsing ─────────────────────────────────────────────────────────
if [[ "$EXISTING_MODE" == false ]]; then
    if [[ -z "${1:-}" ]]; then
        echo -e "${YELLOW}Ticket ID / prefix (optional, Enter to skip):${NC}"; read -r PREFIX
        echo -e "${YELLOW}Feature name (required):${NC}"; read -r FEATURE_NAME
        BRANCH_TYPE=""
    elif [[ -z "${2:-}" ]]; then
        PREFIX=""; FEATURE_NAME="$1"; BRANCH_TYPE=""
    elif [[ "${2:-}" =~ ^(feature|bugfix|hotfix|refactor)$ ]]; then
        PREFIX=""; FEATURE_NAME="$1"; BRANCH_TYPE="$2"
    else
        PREFIX="$1"; FEATURE_NAME="$2"; BRANCH_TYPE="${3:-}"
    fi

    TICKET_ID="${PREFIX:-}"
    TICKET_TITLE="${FEATURE_NAME:-}"

    [[ -z "${FEATURE_NAME:-}" ]] && { echo -e "${RED}Error: feature name required${NC}"; exit 1; }

    if [[ -n "$PREFIX" ]]; then
        PREFIX="$(echo "$PREFIX" | tr '[:lower:]' '[:upper:]' | sed 's/[[:space:]_]/-/g; s/[^A-Z0-9-]//g; s/-\+/-/g; s/^-//; s/-$//')"
    fi

    if [[ -z "${BRANCH_TYPE:-}" ]]; then
        echo -e "${YELLOW}Branch type:${NC}"
        PS3="Choice [1-4]: "
        select opt in feature bugfix hotfix refactor; do
            [[ -n "$opt" ]] && { BRANCH_TYPE="$opt"; break; }
        done
    fi

    KEBAB_NAME="$(echo "$FEATURE_NAME" | sed "s/['\"]//g" | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]_]/-/g; s/[^a-z0-9-]//g; s/-\+/-/g; s/^-//; s/-$//' | cut -c1-80)"

    if [[ -n "$PREFIX" ]]; then
        BRANCH_NAME="${BRANCH_TYPE}/${PREFIX}-${KEBAB_NAME}"
        DIR_NAME="${PREFIX}-${KEBAB_NAME}"
    else
        BRANCH_NAME="${BRANCH_TYPE}/${KEBAB_NAME}"
        DIR_NAME="${KEBAB_NAME}"
    fi

    WORKTREE_DIR="${PARENT_DIR}/${DIR_NAME}"

    # Reuse if the branch is already checked out in a worktree.
    EXISTING_WT="$(git worktree list --porcelain | awk -v b="refs/heads/$BRANCH_NAME" '/^worktree /{wt=$0; sub(/^worktree /, "", wt)} $0=="branch "b{print wt;exit}')"
    if [[ -n "$EXISTING_WT" ]]; then
        echo -e "${GREEN}✓ Worktree already exists: $EXISTING_WT - ensuring stack is up…${NC}"
        if [[ -x "$EXISTING_WT/start.sh" ]]; then
            ( cd "$EXISTING_WT" && ./start.sh )
        else
            echo -e "${YELLOW}⚠ $EXISTING_WT/start.sh not found - run ./start.sh there manually.${NC}"
        fi
        exit 0
    fi

    echo -e "${YELLOW}Creating worktree…${NC}"
    echo "  Branch:    $BRANCH_NAME"
    echo "  Directory: $WORKTREE_DIR"
    echo ""

    git fetch origin 2>/dev/null || true
    if   git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
        git worktree add "$WORKTREE_DIR" "$BRANCH_NAME"
    elif git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
        git worktree add "$WORKTREE_DIR" "$BRANCH_NAME"
    else
        git worktree add --no-track -b "$BRANCH_NAME" "$WORKTREE_DIR" origin/master
    fi
fi

echo -e "${GREEN}✓ Worktree at $WORKTREE_DIR${NC}"

# ── Port block allocation ────────────────────────────────────────────────────
# Reserved via a locked, file-backed registry (worktrees/.port-registry), not
# just a live ss/docker scan - the actual `docker compose up` for a worktree
# happens later (when ./start.sh runs below), so a live scan alone is stale by
# the time two worktree-create.sh calls run close together: both would see the
# same block as free and collide once their stacks actually start. The registry
# makes a block "claimed" the moment this script picks it, for as long as the
# worktree exists. worktree-teardown.sh removes the entry on teardown.
BLOCK_SIZE=10
BASE_START=9100
PORT_REGISTRY="$PARENT_DIR/.port-registry"
PORT_LOCK="$PARENT_DIR/.port-lock"
touch "$PORT_REGISTRY"

# Snapshot ss/docker state once per run rather than re-invoking for every
# candidate port - the registry lock below is what actually prevents a race
# between concurrent calls, so a point-in-time snapshot here is sufficient.
# Fall back to lsof when ss is absent (e.g. macOS) so the live probe still works.
_SS_SNAPSHOT="$(ss -tln 2>/dev/null || lsof -nP -iTCP -sTCP:LISTEN 2>/dev/null)"; [[ -z "$_SS_SNAPSHOT" ]] && _SS_SNAPSHOT=$'\n'
_DOCKER_PORTS_SNAPSHOT="$(docker ps --format "{{.Ports}}" 2>/dev/null)"; [[ -z "$_DOCKER_PORTS_SNAPSHOT" ]] && _DOCKER_PORTS_SNAPSHOT=$'\n'
_port_free() {
  local p=$1
  # ':port' followed by a non-digit or end-of-line (portable boundary; \b is a GNU-ism).
  echo "$_SS_SNAPSHOT" | grep -qE ":${p}([^0-9]|$)" && return 1
  echo "$_DOCKER_PORTS_SNAPSHOT" | grep -qE ":${p}->" && return 1
  return 0
}
_block_registered() {
  local base=$1 other diff
  while read -r _ other; do
    [[ -z "$other" ]] && continue
    diff=$(( base - other )); diff=${diff#-}
    [[ $diff -lt $BLOCK_SIZE ]] && return 0
  done < "$PORT_REGISTRY"
  return 1
}
_block_free() {
  local base=$1 i
  for (( i=0; i<BLOCK_SIZE; i++ )); do
    _port_free $(( base + i )) || return 1
  done
  return 0
}

exec 9>"$PORT_LOCK"
flock 9
BASE_PORT=$BASE_START
while _block_registered "$BASE_PORT" || ! _block_free "$BASE_PORT"; do
    BASE_PORT=$(( BASE_PORT + BLOCK_SIZE ))
done
echo "$DIR_NAME $BASE_PORT" >> "$PORT_REGISTRY"
flock -u 9
exec 9>&-

APP_PORT=$BASE_PORT
VITE_DEV_SERVER_PORT=$(( BASE_PORT + 1 ))
DB_EXTERNAL_PORT=$(( BASE_PORT + 2 ))
MAILHOG_WEB_PORT=$(( BASE_PORT + 3 ))
MAILHOG_SMTP_PORT=$(( BASE_PORT + 4 ))

# ── Stamp an isolated .env ────────────────────────────────────────────────────
# Full isolation comes purely from env: a unique compose project + container
# names give a private network and a private data volume (docker-compose.yml
# names the volume ${DB_CONTAINER}_data), while the port block avoids host
# clashes. DB_HOST stays "mariadb" - it's the compose *service* name, resolved
# on this project's own network, so it needs no change.
# Append a short hash of the FULL DIR_NAME so two branches that share the same
# first-30-character prefix can't collapse to the same compose project /
# container / volume names (which would break isolation or cross-contaminate DBs).
# md5sum is coreutils-only (macOS ships `md5` instead), so pick whichever
# hasher is present - any of them gives a stable hex digest, which is all the
# suffix needs.
_short_hash() {
    if command -v shasum >/dev/null 2>&1; then
        printf '%s' "$1" | shasum
    elif command -v sha1sum >/dev/null 2>&1; then
        printf '%s' "$1" | sha1sum
    elif command -v md5sum >/dev/null 2>&1; then
        printf '%s' "$1" | md5sum
    else
        printf '%s' "$1" | md5
    fi | cut -c1-6
}
SLUG_BASE="$(echo "$DIR_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g; s/-\+/-/g; s/^-//; s/-$//' | cut -c1-23 | sed 's/-$//')"
SLUG="${SLUG_BASE}-$(_short_hash "$DIR_NAME")"
COMPOSE_PROJECT_NAME="$SLUG"
PHP_CONTAINER="${SLUG}-web"
DB_CONTAINER="${SLUG}-db"
MAILHOG_CONTAINER="${SLUG}-mailhog"
APP_URL="http://localhost:${APP_PORT}"

ENV_SRC=""
[[ -f "$REPO_ROOT/.env" ]] && ENV_SRC="$REPO_ROOT/.env"
[[ -z "$ENV_SRC" && -f "$REPO_ROOT/.docker/config/.env.local" ]] && ENV_SRC="$REPO_ROOT/.docker/config/.env.local"
if [[ -z "$ENV_SRC" ]]; then
    echo -e "${RED}Error: no .env or .docker/config/.env.local to copy from${NC}"; exit 1
fi
cp "$ENV_SRC" "$WORKTREE_DIR/.env"
for var in COMPOSE_PROJECT_NAME PHP_CONTAINER DB_CONTAINER MAILHOG_CONTAINER APP_URL \
           APP_PORT VITE_DEV_SERVER_PORT DB_EXTERNAL_PORT MAILHOG_WEB_PORT MAILHOG_SMTP_PORT; do
    sed -i "/^${var}=/d" "$WORKTREE_DIR/.env"
    echo "${var}=${!var}" >> "$WORKTREE_DIR/.env"
done
echo -e "${GREEN}✓ .env written (project: ${COMPOSE_PROJECT_NAME}, app :${APP_PORT}, db :${DB_EXTERNAL_PORT})${NC}"

# ── .snag.md - branch/ticket context ─────────────────────────────────────────
cat > "$WORKTREE_DIR/.snag.md" <<SNAG
# Snag

- **Ticket:** ${TICKET_ID:-<none>}
- **Title:** ${TICKET_TITLE:-<set manually>}
- **Branch:** ${BRANCH_NAME}
- **App:** http://localhost:${APP_PORT}
- **Mailhog:** http://localhost:${MAILHOG_WEB_PORT}

This worktree runs a fully isolated Docker stack (project '${COMPOSE_PROJECT_NAME}',
its own database). Run everything from this directory - e.g.
\`docker compose exec php-apache php artisan …\`.
SNAG

# ── Ensure the dev tooling is present in the worktree ────────────────────────
# start.sh + scripts/ may be untracked (not yet committed) or absent on the
# base branch this worktree was cut from, in which case `git worktree add`
# won't have populated them. Copy them from the source repo when missing so
# the worktree can start itself. Only copies when absent, so a branch that is
# intentionally editing this tooling keeps its own version.
for tool in start.sh scripts/resolve-ports.sh; do
    if [[ ! -f "$WORKTREE_DIR/$tool" && -f "$REPO_ROOT/$tool" ]]; then
        mkdir -p "$WORKTREE_DIR/$(dirname "$tool")"
        cp "$REPO_ROOT/$tool" "$WORKTREE_DIR/$tool"
        chmod +x "$WORKTREE_DIR/$tool"
    fi
done

# ── Bring the isolated stack up, seeded ──────────────────────────────────────
echo -e "${YELLOW}Starting the worktree stack (build + migrate + seed)…${NC}"
( cd "$WORKTREE_DIR" && ./start.sh )

# ── Open the worktree in a new VS Code window ────────────────────────────────
# -n forces a new window rather than adding the folder to the current one.
# Print the command as a fallback when the `code` CLI isn't on PATH.
if command -v code >/dev/null 2>&1; then
    echo -e "${GREEN}Opening in a new VS Code window…${NC}"
    code -n "$WORKTREE_DIR" || \
        echo -e "${YELLOW}Could not open VS Code; the worktree is ready at $WORKTREE_DIR${NC}"
else
    echo -e "${YELLOW}VS Code CLI not found — open it with: code -n \"$WORKTREE_DIR\"${NC}"
fi

echo ""
echo -e "${GREEN}Done.${NC}"
echo "  Worktree: $WORKTREE_DIR"
echo "  App:      http://localhost:${APP_PORT}"
echo ""
echo "To remove: ./scripts/worktree-teardown.sh ${DIR_NAME}"
