#!/bin/bash
# claude-code - Smart Claude Code session launcher with cmux/tmux
#
# Enhances the basic Claude workflow with:
#   - Project template initialization
#   - Per-project multiplexer sessions/workspaces
#   - Working directory awareness
#   - Session transcript logging (~/.claude/logs/)
#
# Usage:
#   claude-code [path]                  Start/resume Claude session (default: current dir)
#   claude-code init <type> [path]      Initialize new project from template
#   claude-code sync [path] [--dry-run] Sync project against its template
#   claude-code list                    List available project templates
#   claude-code help                    Show usage
#
# Examples:
#   claude-code                         Resume/start session in current directory
#   claude-code ~/projects/my-api       Start session in existing project
#   claude-code init ml-rag             Init ML RAG project in current directory
#   claude-code init java-enterprise ~/projects/order-service
#   claude-code sync                    Sync current project against its template
#   claude-code sync ~/projects/my-api --dry-run   Preview sync changes
#
# Setup:
#   Run claude-setup.sh first to create ~/.claude/templates/

set -e

TEMPLATES_DIR="$HOME/.claude/templates"
LOGS_DIR="$HOME/.claude/logs"
CLAUDE_CMD=("claude")
BACKEND_AUTO="auto"
BACKEND_TMUX="tmux"
BACKEND_CMUX="cmux"
LAUNCHER_CONFIG="$HOME/.claude/launcher.json"
SESSION_BACKEND="$BACKEND_AUTO"

# Force IPv4 DNS resolution order. Claude Code's Bun runtime prefers IPv6
# but lacks Happy Eyeballs (RFC 8305) fallback, causing ECONNRESET on
# networks with partial/broken IPv6. BUN_OPTIONS prepends CLI flags to
# any Bun execution. Preserves existing BUN_OPTIONS if set.
if [[ -z "${BUN_OPTIONS:-}" ]]; then
    export BUN_OPTIONS="--dns-result-order=ipv4first"
elif [[ "$BUN_OPTIONS" != *"--dns-result-order"* ]]; then
    export BUN_OPTIONS="--dns-result-order=ipv4first $BUN_OPTIONS"
fi

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

is_interactive_terminal() {
    [[ -t 0 && -t 1 ]]
}

is_macos() {
    [[ "$(uname -s)" == "Darwin" ]]
}

is_inside_cmux() {
    [[ -n "${CMUX_WORKSPACE_ID:-}" ]]
}

is_inside_tmux() {
    [[ -n "${TMUX:-}" ]]
}

read_saved_backend() {
    local val=""
    if [[ -f "$LAUNCHER_CONFIG" ]] && command_exists jq; then
        val=$(jq -r '.sessionBackend // empty' "$LAUNCHER_CONFIG" 2>/dev/null) || val=""
    elif [[ -f "$LAUNCHER_CONFIG" ]]; then
        val=$(grep -o '"sessionBackend"[[:space:]]*:[[:space:]]*"[^"]*"' "$LAUNCHER_CONFIG" 2>/dev/null \
            | head -1 | sed 's/.*"sessionBackend"[[:space:]]*:[[:space:]]*"//; s/"//') || val=""
    fi
    # Only return recognized backends; ignore malformed or unsupported values
    case "$val" in
        "$BACKEND_CMUX"|"$BACKEND_TMUX") printf '%s\n' "$val" ;;
    esac
}

resolve_session_backend() {
    if [[ "$SESSION_BACKEND" != "$BACKEND_AUTO" ]]; then
        printf '%s\n' "$SESSION_BACKEND"
        return
    fi

    # If already inside a multiplexer, reuse it (avoids nesting)
    if [[ -n "${ZELLIJ_SESSION_NAME:-}" ]]; then
        printf '%s\n' "direct"
        return
    fi
    if is_inside_cmux; then
        printf '%s\n' "$BACKEND_CMUX"
        return
    fi
    if is_inside_tmux; then
        printf '%s\n' "$BACKEND_TMUX"
        return
    fi

    # Use saved preference from setup.sh if available
    local saved
    saved=$(read_saved_backend)
    if [[ -n "$saved" ]]; then
        printf '%s\n' "$saved"
        return
    fi

    # Default: cmux on macOS, tmux elsewhere
    if is_macos; then
        printf '%s\n' "$BACKEND_CMUX"
    else
        printf '%s\n' "$BACKEND_TMUX"
    fi
}

cmux_app_path() {
    local app_path
    for app_path in \
        "/Applications/cmux.app" \
        "$HOME/Applications/cmux.app"
    do
        if [[ -d "$app_path" ]]; then
            printf '%s\n' "$app_path"
            return 0
        fi
    done
    return 1
}

ensure_cmux_cli() {
    local app_path cli_path target_link

    if command_exists cmux; then
        return 0
    fi

    app_path=$(cmux_app_path) || return 1
    cli_path="$app_path/Contents/Resources/bin/cmux"
    [[ -x "$cli_path" ]] || return 1

    mkdir -p "$HOME/.local/bin"
    target_link="$HOME/.local/bin/cmux"
    ln -sf "$cli_path" "$target_link"
    command_exists cmux
}

prompt_install_cmux() {
    local reply=""

    if ! is_macos; then
        echo "Error: cmux is only supported on macOS."
        return 1
    fi

    if ! is_interactive_terminal || [[ -n "${CI:-}" ]]; then
        echo "cmux is not installed and automatic install was skipped because this shell is non-interactive."
        return 1
    fi

    if ! command_exists brew; then
        echo "Error: cmux is not installed and Homebrew is unavailable."
        echo "Install Homebrew first: https://brew.sh"
        return 1
    fi

    echo "cmux is not installed."
    echo -n "Install cmux now with Homebrew? [Y/n] "
    read -r reply
    if [[ -n "$reply" && ! "$reply" =~ ^[Yy]$ ]]; then
        return 1
    fi

    brew tap manaflow-ai/cmux
    brew install --cask cmux
    ensure_cmux_cli >/dev/null 2>&1 || true
    command_exists cmux
}

ensure_cmux_available() {
    ensure_cmux_cli >/dev/null 2>&1 || true
    if command_exists cmux; then
        return 0
    fi

    prompt_install_cmux || return 1
    ensure_cmux_cli >/dev/null 2>&1 || true
    command_exists cmux
}

ensure_tmux_available() {
    if command_exists tmux; then
        return 0
    fi

    echo "Error: tmux is not installed."
    if is_macos && command_exists brew; then
        echo "Install it with: brew install tmux"
    fi
    return 1
}

resolve_shell_bin() {
    printf '%s\n' "${SHELL:-/bin/bash}"
}

create_log_file() {
    local dir_name="$1"
    local timestamp

    mkdir -p "$LOGS_DIR"
    timestamp=$(date +%Y-%m-%d-%H%M%S)
    printf '%s/%s-%s.log\n' "$LOGS_DIR" "$dir_name" "$timestamp"
}

build_run_cmd_string() {
    local session_start="$1"
    local run_cmd=""
    local quoted_cmd=""

    # %q-quote each element of CLAUDE_CMD separately so multi-token forms
    # like ("claude" "--dangerously-skip-permissions") survive shell
    # re-parsing inside tmux/cmux. A naive %q on the whole string would
    # escape internal spaces, making the resulting command look for a
    # single executable literally named "claude --flag…".
    printf -v quoted_cmd '%q ' "${CLAUDE_CMD[@]}"
    quoted_cmd="${quoted_cmd% }"

    if [[ "$PEER_REVIEW_ENABLED" == "true" ]]; then
        local extra_env=""
        [[ -n "${REVIEW_MAX_ROUNDS:-}" ]] && printf -v extra_env 'CCT_REVIEW_MAX_ROUNDS=%q ' "$REVIEW_MAX_ROUNDS"
        printf -v run_cmd 'env CCT_PEER_REVIEW_ENABLED=true CCT_PEER_PROVIDER=%q CCT_PEER_TRIGGER=phase-complete CCT_PEER_REVIEW_SCOPE=%q CCT_REVIEW_COMMITS=%q %sCCT_SESSION_START=%q %s' \
            "${PEER_PROVIDER:-}" \
            "$PEER_SCOPE" \
            "$REVIEW_COMMITS" \
            "$extra_env" \
            "$session_start" \
            "$quoted_cmd"
    else
        run_cmd="$quoted_cmd"
    fi

    printf '%s\n' "$run_cmd"
}

build_launch_command() {
    local project_dir="$1"
    local session_start="$2"
    local log_file="$3"
    local no_exec="${4:-0}"
    local run_cmd
    local shell_bin
    local launch_cmd=""
    local exec_prefix="exec "

    if [[ "$no_exec" == "1" ]]; then
        exec_prefix=""
    fi

    run_cmd=$(build_run_cmd_string "$session_start")
    shell_bin=$(resolve_shell_bin)

    printf -v launch_cmd 'cd %q && %s%s' "$project_dir" "$exec_prefix" "$run_cmd"

    printf '%s\n' "$launch_cmd"
}

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

copy_to_clipboard() {
    local text="$1"
    if is_macos && command_exists pbcopy; then
        printf '%s' "$text" | pbcopy
    fi
}

launch_cmux_with_cli() {
    local launch_cmd="$1"
    cmux new-workspace >/dev/null 2>&1 || return 1
    cmux send "$launch_cmd"$'\n' >/dev/null 2>&1 || return 1
}

launch_cmux_with_osascript() {
    local launch_cmd="$1"
    local escaped_cmd

    escaped_cmd=$(escape_applescript_text "$launch_cmd")
    open -a cmux >/dev/null 2>&1 || return 1

    osascript <<EOF >/dev/null 2>&1
tell application "cmux" to activate
delay 0.8
tell application "System Events"
    keystroke "n" using {command down}
    delay 0.2
    keystroke "$escaped_cmd"
    key code 36
end tell
EOF
}

exec_claude_in_current_shell() {
    local project_dir="$1"
    local log_file="$2"
    local session_start
    local run_cmd
    local shell_bin

    session_start=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    cd "$project_dir"

    run_cmd=$(build_run_cmd_string "$session_start")
    shell_bin=$(resolve_shell_bin)

    if [[ "$PEER_REVIEW_ENABLED" == "true" ]]; then
        local env_args=(
            CCT_PEER_REVIEW_ENABLED=true
            CCT_PEER_PROVIDER="${PEER_PROVIDER:-}"
            CCT_PEER_TRIGGER=phase-complete
            CCT_PEER_REVIEW_SCOPE="$PEER_SCOPE"
            CCT_REVIEW_COMMITS="$REVIEW_COMMITS"
            CCT_SESSION_START="$session_start"
        )
        [[ -n "${REVIEW_MAX_ROUNDS:-}" ]] && env_args+=(CCT_REVIEW_MAX_ROUNDS="$REVIEW_MAX_ROUNDS")
        exec env "${env_args[@]}" "${CLAUDE_CMD[@]}"
    fi

    exec "${CLAUDE_CMD[@]}"
}

# ── Helpers ──────────────────────────────────────────────────

compute_template_hash() {
    local file="$1"
    if command_exists md5sum; then
        md5sum "$file" | cut -d' ' -f1
    elif command_exists md5; then
        md5 -q "$file"
    else
        echo "unknown"
    fi
}

infer_template() {
    local target="$1"
    [[ -f "$target/CLAUDE.md" ]] || return 1

    local heading
    heading=$(head -1 "$target/CLAUDE.md")

    # Exact heading match only — no fuzzy/prefix matching.
    # Multiple templates can share a heading prefix (e.g. "# ML/AI"),
    # so a fuzzy match would pick the wrong one and persist it.
    for dir in "$TEMPLATES_DIR"/*/; do
        [[ -f "$dir/CLAUDE.md" ]] || continue
        local tmpl_heading
        tmpl_heading=$(head -1 "$dir/CLAUDE.md")
        if [[ "$heading" == "$tmpl_heading" ]]; then
            basename "$dir"
            return 0
        fi
    done

    return 1
}

resolve_project_root() {
    local target="$1"
    git -C "$target" rev-parse --show-toplevel 2>/dev/null \
        || (cd "$target" && pwd -P 2>/dev/null) \
        || echo "$target"
}

hash_string() {
    local value="$1"
    if command_exists md5; then
        printf '%s' "$value" | md5 -q
    elif command_exists md5sum; then
        printf '%s' "$value" | md5sum | cut -d' ' -f1
    else
        printf '%s' "$value" | cksum | awk '{print $1}'
    fi
}

compute_repo_hash() {
    local target="$1"
    hash_string "$(resolve_project_root "$target")"
}

compute_memkernel_project_id() {
    local target="$1"
    local project_root project_slug project_hash

    project_root=$(resolve_project_root "$target")
    project_slug=$(basename "$project_root" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-')
    project_slug=$(printf '%s' "$project_slug" | sed 's/^-*//; s/-*$//')
    [[ -n "$project_slug" ]] || project_slug="project"
    project_hash=$(hash_string "$project_root")

    printf '%s-%s\n' "$project_slug" "${project_hash:0:12}"
}

memkernel_available() {
    command_exists memkernel
}

ensure_memkernel_settings_local() {
    local target="$1"
    local settings_file="$target/.claude/settings.local.json"
    local project_id current_json updated_json

    if ! memkernel_available; then
        printf '%s\n' "unavailable"
        return 0
    fi

    project_id=$(compute_memkernel_project_id "$target")
    mkdir -p "$target/.claude"

    if [[ -f "$settings_file" ]]; then
        if ! command_exists jq; then
            printf '%s\n' "warn-no-jq"
            return 0
        fi

        current_json=$(jq -S . "$settings_file" 2>/dev/null) || {
            printf '%s\n' "warn-merge"
            return 0
        }

        updated_json=$(jq -S --arg pid "$project_id" '
            .mcpServers //= {} |
            .mcpServers.memkernel //= {} |
            .mcpServers.memkernel.command //= "memkernel" |
            .mcpServers.memkernel.args //= [] |
            .mcpServers.memkernel.env //= {} |
            .mcpServers.memkernel.env.MEMKERNEL_PROJECT_ID //= $pid
        ' "$settings_file" 2>/dev/null) || {
            printf '%s\n' "warn-merge"
            return 0
        }

        if [[ "$current_json" == "$updated_json" ]]; then
            printf '%s\n' "present"
            return 0
        fi

        printf '%s\n' "$updated_json" > "$settings_file"
        printf '%s\n' "updated"
        return 0
    fi

    if command_exists jq; then
        jq -n -S --arg pid "$project_id" '
            {
              mcpServers: {
                memkernel: {
                  command: "memkernel",
                  args: [],
                  env: {
                    MEMKERNEL_PROJECT_ID: $pid
                  }
                }
              }
            }
        ' > "$settings_file"
    else
        cat > "$settings_file" << EOF
{
  "mcpServers": {
    "memkernel": {
      "command": "memkernel",
      "args": [],
      "env": {
        "MEMKERNEL_PROJECT_ID": "$project_id"
      }
    }
  }
}
EOF
    fi

    printf '%s\n' "created"
}

show_help() {
    cat << 'HELP'
claude-code - Smart Claude Code session launcher

USAGE:
    claude-code [path]                              Start Claude session (cmux on macOS, tmux elsewhere)
    claude-code --continue [path]                   Continue last session
    claude-code --resume [path]                     Resume a named session (interactive picker)
    claude-code init <type> [path] [--playwright] [--license <type>]   Initialize project from template
    claude-code sync [path] [--dry-run]             Sync project against its template
    claude-code playwright [path]                   Install Playwright CLI skills into a project
    claude-code list                                List available templates
    claude-code help                                Show this help

OPTIONS:
    --dry-run            With 'sync': show what would change without modifying files
    --playwright         With 'init': install Playwright CLI skills into the new project
    --license <type>     With 'init': add Copyright & Licensing section to CLAUDE.md (e.g. MIT, Apache-2.0, Proprietary)
    --peer-review [name] Enable peer review (optional: specify provider)
    --peer-review-off    Disable peer review for this session
    --peer-review-scope <scope>  Review scope: code|design|both (default: both)
    --review-commits <mode>  Commit strategy: single|per-round|squash (default: squash)
    --review-max-rounds <n>  Max review rounds before breaker (default: 5)
    --continue, -c       Continue the most recent Claude session
    --resume, -r         Resume a named session (interactive picker)
    --shell <auto|cmux|tmux>  Session backend (default: auto)

Any other --flag is forwarded verbatim to the underlying `claude` CLI
(e.g. --dangerously-skip-permissions). Use --flag=value for value-bearing
flags to avoid ambiguity with positional args.

PROJECT TYPES:
    ml-rag              Python RAG + Knowledge Graph
    ml-app              Python + Next.js full-stack LLM application
    ml-utils            Python headless utility / MCP tool server
    ml-langchain        Python LangChain/LangGraph/LangSmith
    ml-n8n              Python + n8n workflow automation
    java-enterprise     Full-stack Java (Spring Boot + React + GraphQL)
    java-tooling        Java tooling (Annotation Processors, Gradle Plugins)
    gradle-plugin       Gradle plugin (Plugin<Project> + TestKit + Plugin Portal)
    domain-pack         Versioned content distribution (Maven Central + PyPI)
    web-static          Static site (Astro/Next.js/Hugo)
    web-dynamic         Dynamic web app (Next.js/Remix full-stack)

WORKFLOW - New Project:
    claude-code init ml-rag ~/projects/my-rag-app
    claude-code ~/projects/my-rag-app

WORKFLOW - Existing Project:
    claude-code ~/projects/existing-app
    claude-code --shell cmux ~/projects/existing-app

WORKFLOW - Sync Project to Latest Template:
    claude-setup.sh --sync                          # update templates from repo first
    claude-code sync ~/projects/existing-app        # sync project files
    claude-code sync --dry-run                      # preview changes without applying

SETUP:
    Run claude-setup.sh once to create templates in ~/.claude/templates/
HELP
}

list_templates() {
    echo "Available project templates:"
    echo ""
    if [[ -d "$TEMPLATES_DIR" ]]; then
        for dir in "$TEMPLATES_DIR"/*/; do
            if [[ -f "$dir/CLAUDE.md" ]]; then
                type_name=$(basename "$dir")
                # Extract first line after "# " as description
                desc=$(head -1 "$dir/CLAUDE.md" | sed 's/^# //')
                printf "  %-20s %s\n" "$type_name" "$desc"
            fi
        done
    else
        echo "  No templates found. Run claude-setup.sh first."
    fi
    echo ""
}

init_project() {
    local type="$1"
    local target="${2:-.}"

    # Validate template exists
    if [[ ! -d "$TEMPLATES_DIR/$type" ]]; then
        echo "Error: Unknown project type '$type'"
        echo ""
        list_templates
        exit 1
    fi

    # Create target directory if needed
    mkdir -p "$target"
    target=$(cd "$target" && pwd)

    # Check if CLAUDE.md already exists
    if [[ -f "$target/CLAUDE.md" ]]; then
        echo "Warning: $target/CLAUDE.md already exists."
        read -p "Overwrite? [y/N] " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            echo "Aborted."
            exit 0
        fi
    fi

    # Copy template files
    cp "$TEMPLATES_DIR/$type/CLAUDE.md" "$target/CLAUDE.md"

    # Copy .claude directory contents if they exist
    if [[ -d "$TEMPLATES_DIR/$type/.claude" ]]; then
        mkdir -p "$target/.claude"
        cp -r "$TEMPLATES_DIR/$type/.claude/"* "$target/.claude/" 2>/dev/null || true
    fi

    # Copy commands if they exist
    if [[ -d "$TEMPLATES_DIR/$type/commands" ]]; then
        mkdir -p "$target/.claude/commands"
        cp -r "$TEMPLATES_DIR/$type/commands/"* "$target/.claude/commands/" 2>/dev/null || true
    fi

    # Copy .github directory (CI workflows) if it exists
    if [[ -d "$TEMPLATES_DIR/$type/.github" ]]; then
        cp -r "$TEMPLATES_DIR/$type/.github" "$target/" 2>/dev/null || true
    fi

    # Copy any other top-level template subdirs (e.g. domain-pack ships
    # content/, jvm-wrapper/, python-wrapper/, scripts/; gradle-plugin
    # ships plugin/, sample-consumer/, gradle/). Skip ones already handled
    # above and never overwrite the project's existing dirs.
    #
    # NOTE: trailing slash on the source path unwraps the directory contents
    # into $target. Strip it with `${extra%/}` so `cp -r` copies the
    # directory itself into $target, preserving the layout.
    for extra in "$TEMPLATES_DIR/$type"/*/; do
        [[ -d "$extra" ]] || continue
        local extra_name
        extra_name=$(basename "$extra")
        case "$extra_name" in
            commands|.claude|.github) continue ;;
        esac
        if [[ ! -e "$target/$extra_name" ]]; then
            cp -r "${extra%/}" "$target/" 2>/dev/null || true
        fi
    done

    # Copy any top-level regular files other than CLAUDE.md (e.g.
    # gradle-plugin ships settings.gradle.kts, build.gradle.kts, .gitignore at
    # the template root). Never overwrite an existing project file.
    while IFS= read -r -d '' extra_file; do
        local extra_name
        extra_name=$(basename "$extra_file")
        case "$extra_name" in
            CLAUDE.md|PROJECT.md) continue ;;
        esac
        if [[ ! -e "$target/$extra_name" ]]; then
            cp "$extra_file" "$target/$extra_name" 2>/dev/null || true
        fi
    done < <(find "$TEMPLATES_DIR/$type" -maxdepth 1 -type f -print0 2>/dev/null)

    # Write template metadata for sync support
    local hash
    hash=$(compute_template_hash "$TEMPLATES_DIR/$type/CLAUDE.md")
    mkdir -p "$target/.claude"
    cat > "$target/.claude/template.json" << TMPLEOF
{
  "name": "$type",
  "initialized": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "templateHash": "$hash"
}
TMPLEOF

    # Pre-approve git approval commands in settings.local.json.
    # The protect-git.sh hook blocks every git commit/push and instructs Claude to
    # create a one-time approval file via a combined "mkdir && touch" command.
    # That combined form must be pre-approved here so it runs without a permission
    # prompt — otherwise Claude Code prompts the user on every commit attempt.
    local repo_hash _uid
    _uid=$(id -u)
    repo_hash=$(compute_repo_hash "$target")
    local _adir="/tmp/.claude-git-approvals"
    local _cf="${_adir}/commit-${_uid}-${repo_hash}"
    local _pf="${_adir}/push-${_uid}-${repo_hash}"
    # Three approval variants matching protect-git.sh's three block-message branches:
    #   commit-only, push-only, and compound commit+push (one touch with two file args)
    local _cp="Bash(mkdir -p ${_adir} && touch ${_cf})"
    local _pp="Bash(mkdir -p ${_adir} && touch ${_pf})"
    local _both="Bash(mkdir -p ${_adir} && touch ${_cf} ${_pf})"
    local _sl="$target/.claude/settings.local.json"
    if [[ -f "$_sl" ]] && command_exists jq; then
        local _updated
        _updated=$(jq --arg cp "$_cp" --arg pp "$_pp" --arg both "$_both" '
            .permissions.allow //= [] |
            if (.permissions.allow | index($cp))   == null then .permissions.allow += [$cp]   else . end |
            if (.permissions.allow | index($pp))   == null then .permissions.allow += [$pp]   else . end |
            if (.permissions.allow | index($both)) == null then .permissions.allow += [$both] else . end
        ' "$_sl")
        echo "$_updated" > "$_sl"
        echo "[done] Added git approval permissions to .claude/settings.local.json"
    elif [[ ! -f "$_sl" ]]; then
        mkdir -p "$target/.claude"
        printf '{\n  "permissions": {\n    "allow": [\n      "%s",\n      "%s",\n      "%s"\n    ]\n  }\n}\n' \
            "$_cp" "$_pp" "$_both" > "$_sl"
        echo "[done] Created .claude/settings.local.json with git approval permissions"
    else
        echo "[WARN] jq not installed — could not write git approval permissions to settings.local.json"
        echo "       Add manually:"
        echo "         $_cp"
        echo "         $_pp"
        echo "         $_both"
    fi

    local _memkernel_status
    _memkernel_status=$(ensure_memkernel_settings_local "$target")
    case "$_memkernel_status" in
        updated)
            echo "[done] Added MemKernel MCP config to .claude/settings.local.json"
            ;;
        created)
            echo "[done] Created .claude/settings.local.json with MemKernel MCP config"
            ;;
        warn-no-jq)
            echo "[WARN] jq not installed — could not add MemKernel MCP config to settings.local.json"
            ;;
        warn-merge)
            echo "[WARN] Could not safely merge MemKernel MCP config into settings.local.json"
            ;;
    esac

    # Append Copyright & Licensing section to CLAUDE.md if --license was given
    if [[ -n "$LICENSE_TYPE" ]]; then
        local _year _company=""
        _year=$(date +%Y)
        local _providers="$HOME/.code-copilot-team/providers.toml"
        if [[ -f "$_providers" ]]; then
            _company=$(grep '^company[[:space:]]*=[[:space:]]*' "$_providers" \
                | head -1 \
                | sed 's/^company[[:space:]]*=[[:space:]]*"//; s/"[[:space:]]*$//' 2>/dev/null || echo "")
        fi
        {
            printf '\n## Copyright & Licensing\n\n'
            printf -- '- **Company**: %s\n' "${_company:-Unknown}"
            printf -- '- **License**: %s\n' "$LICENSE_TYPE"
        } >> "$target/CLAUDE.md"
        echo "[done] Added copyright section (company: '${_company:-Unknown}', license: $LICENSE_TYPE) to CLAUDE.md"
    fi

    # Install Playwright CLI skills for web templates if --playwright flag is set
    if [[ "$PLAYWRIGHT" == "1" ]]; then
        if command -v playwright-cli &>/dev/null; then
            (cd "$target" && playwright-cli install --skills 2>/dev/null) \
                && echo "[done] Playwright CLI skills installed" \
                || echo "[WARN] Playwright skills install failed (run 'playwright-cli install --skills' in project)"
        else
            echo "[WARN] playwright-cli not found. Install first:"
            echo "       npm install -g @playwright/cli@latest"
            echo "       Then run: cd $target && playwright-cli install --skills"
        fi
    fi

    echo "Initialized '$type' project at $target"
    echo ""
    echo "Created files:"
    find "$target" -name "CLAUDE.md" -o -name "*.md" -path "*/.claude/*" | sort | while read f; do
        echo "  ${f#$target/}"
    done
    echo ""
    echo "Next steps:"
    echo "  1. Edit $target/CLAUDE.md to customize for your project"
    echo "  2. Run: claude-code $target"
    echo ""
}

sync_project() {
    local target="${1:-.}"
    local dry_run="${2:-0}"

    # Resolve to absolute path
    if [[ ! -d "$target" ]]; then
        echo "Error: Directory '$target' does not exist."
        exit 1
    fi
    target=$(cd "$target" && pwd)

    # 1. Determine template name
    local template_name=""
    if [[ -f "$target/.claude/template.json" ]]; then
        if command_exists jq; then
            template_name=$(jq -r '.name // empty' "$target/.claude/template.json" 2>/dev/null)
        else
            template_name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$target/.claude/template.json" 2>/dev/null | head -1 | sed 's/.*"name"[[:space:]]*:[[:space:]]*"//; s/"//')
        fi
    fi

    # If no template.json, infer from CLAUDE.md heading (one-time backfill)
    local inferred=0
    if [[ -z "$template_name" ]]; then
        template_name=$(infer_template "$target" 2>/dev/null) || true
        if [[ -n "$template_name" ]]; then
            inferred=1
            echo "[info] No .claude/template.json found — inferred '$template_name' from CLAUDE.md heading."
            if [[ "$dry_run" == "1" ]]; then
                echo "       Run without --dry-run to record this in .claude/template.json."
            else
                echo "       This will be recorded so future syncs don't need inference."
            fi
            echo ""
        fi
    fi

    if [[ -z "$template_name" ]]; then
        echo "Error: Cannot determine template for this project."
        echo ""
        echo "No .claude/template.json found and could not match CLAUDE.md heading"
        echo "against any installed template."
        echo ""
        echo "Available templates:"
        list_templates
        echo "To set the template manually:"
        echo "  mkdir -p .claude"
        echo '  echo '"'"'{"name":"<template-name>"}'"'"' > .claude/template.json'
        exit 1
    fi

    # 2. Validate template exists
    if [[ ! -d "$TEMPLATES_DIR/$template_name" ]]; then
        echo "Error: Template '$template_name' not found in $TEMPLATES_DIR"
        echo "Run 'claude-setup.sh --sync' to update templates first."
        exit 1
    fi

    local tmpl_dir="$TEMPLATES_DIR/$template_name"
    local changes=0

    if [[ "$dry_run" == "1" ]]; then
        echo "Dry run: syncing $(basename "$target") against template '$template_name'"
    else
        echo "Syncing $(basename "$target") against template '$template_name'"
    fi
    echo ""

    # 3. Sync commands
    if [[ -d "$tmpl_dir/commands" ]]; then
        for cmd_file in "$tmpl_dir/commands/"*.md; do
            [[ -f "$cmd_file" ]] || continue
            local cmd_name
            cmd_name=$(basename "$cmd_file")
            local dest="$target/.claude/commands/$cmd_name"

            if [[ ! -f "$dest" ]]; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [add] .claude/commands/$cmd_name (new)"
                else
                    mkdir -p "$target/.claude/commands"
                    cp "$cmd_file" "$dest"
                    echo "  [add] .claude/commands/$cmd_name"
                fi
                changes=$((changes + 1))
            elif ! diff -q "$cmd_file" "$dest" >/dev/null 2>&1; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [upd] .claude/commands/$cmd_name (changed)"
                    diff --unified=3 "$dest" "$cmd_file" 2>/dev/null | head -20 || true
                    echo ""
                else
                    cp "$cmd_file" "$dest"
                    echo "  [upd] .claude/commands/$cmd_name"
                fi
                changes=$((changes + 1))
            fi
        done
    fi

    # 4. Sync .claude directory contents (remediation.json, settings, etc.)
    if [[ -d "$tmpl_dir/.claude" ]]; then
        while IFS= read -r -d '' tmpl_file; do
            local rel_path="${tmpl_file#$tmpl_dir/}"
            local dest="$target/$rel_path"

            # Skip commands/ — handled above
            [[ "$rel_path" == .claude/commands/* ]] && continue

            if [[ ! -f "$dest" ]]; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [add] $rel_path (new)"
                else
                    mkdir -p "$(dirname "$dest")"
                    cp "$tmpl_file" "$dest"
                    echo "  [add] $rel_path"
                fi
                changes=$((changes + 1))
            elif ! diff -q "$tmpl_file" "$dest" >/dev/null 2>&1; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [upd] $rel_path (changed)"
                    diff --unified=3 "$dest" "$tmpl_file" 2>/dev/null | head -20 || true
                    echo ""
                else
                    cp "$tmpl_file" "$dest"
                    echo "  [upd] $rel_path"
                fi
                changes=$((changes + 1))
            fi
        done < <(find "$tmpl_dir/.claude" -type f -print0 2>/dev/null)
    fi

    # 4b. Sync .github directory contents (CI workflows)
    if [[ -d "$tmpl_dir/.github" ]]; then
        while IFS= read -r -d '' tmpl_file; do
            local rel_path="${tmpl_file#$tmpl_dir/}"
            local dest="$target/$rel_path"

            if [[ ! -f "$dest" ]]; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [add] $rel_path (new)"
                else
                    mkdir -p "$(dirname "$dest")"
                    cp "$tmpl_file" "$dest"
                    echo "  [add] $rel_path"
                fi
                changes=$((changes + 1))
            elif ! diff -q "$tmpl_file" "$dest" >/dev/null 2>&1; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [upd] $rel_path (changed)"
                    diff --unified=3 "$dest" "$tmpl_file" 2>/dev/null | head -20 || true
                    echo ""
                else
                    cp "$tmpl_file" "$dest"
                    echo "  [upd] $rel_path"
                fi
                changes=$((changes + 1))
            fi
        done < <(find "$tmpl_dir/.github" -type f -print0 2>/dev/null)
    fi

    # 4c. Sync any other top-level template subdirs (e.g. domain-pack ships
    # content/, jvm-wrapper/, python-wrapper/, scripts/; gradle-plugin ships
    # plugin/, sample-consumer/, gradle/). Treat these like
    # CLAUDE.md: add new files, but NEVER auto-overwrite existing ones —
    # consumers customize these (their actual content, their loader code,
    # their publish credentials). Changed files are reported as a hint, not
    # silently clobbered.
    for extra in "$tmpl_dir"/*/; do
        [[ -d "$extra" ]] || continue
        local extra_name
        extra_name=$(basename "$extra")
        case "$extra_name" in
            commands|.claude|.github) continue ;;
        esac
        while IFS= read -r -d '' tmpl_file; do
            local rel_path="${tmpl_file#$tmpl_dir/}"
            local dest="$target/$rel_path"

            if [[ ! -f "$dest" ]]; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [add] $rel_path (new)"
                else
                    mkdir -p "$(dirname "$dest")"
                    cp "$tmpl_file" "$dest"
                    echo "  [add] $rel_path"
                fi
                changes=$((changes + 1))
            elif ! diff -q "$tmpl_file" "$dest" >/dev/null 2>&1; then
                if [[ "$dry_run" == "1" ]]; then
                    echo "  [diff] $rel_path (consumer-owned — not overwritten)"
                    diff --unified=3 "$dest" "$tmpl_file" 2>/dev/null | head -20 || true
                    echo ""
                else
                    echo "  [diff] $rel_path differs from template — not overwritten (consumer-owned). Compare against $tmpl_dir/$rel_path to review."
                fi
                changes=$((changes + 1))
            fi
        done < <(find "$extra" -type f -print0 2>/dev/null)
    done

    # 4d. Sync top-level regular files (e.g. gradle-plugin ships
    # settings.gradle.kts, build.gradle.kts, .gitignore at the template
    # root). Same consumer-owned semantics: add new, never clobber.
    while IFS= read -r -d '' tmpl_file; do
        local extra_name
        extra_name=$(basename "$tmpl_file")
        case "$extra_name" in
            CLAUDE.md|PROJECT.md) continue ;;
        esac
        local dest="$target/$extra_name"
        if [[ ! -f "$dest" ]]; then
            if [[ "$dry_run" == "1" ]]; then
                echo "  [add] $extra_name (new)"
            else
                cp "$tmpl_file" "$dest"
                echo "  [add] $extra_name"
            fi
            changes=$((changes + 1))
        elif ! diff -q "$tmpl_file" "$dest" >/dev/null 2>&1; then
            if [[ "$dry_run" == "1" ]]; then
                echo "  [diff] $extra_name (consumer-owned — not overwritten)"
                diff --unified=3 "$dest" "$tmpl_file" 2>/dev/null | head -20 || true
                echo ""
            else
                echo "  [diff] $extra_name differs from template — not overwritten (consumer-owned). Compare against $tmpl_dir/$extra_name to review."
            fi
            changes=$((changes + 1))
        fi
    done < <(find "$tmpl_dir" -maxdepth 1 -type f -print0 2>/dev/null)

    # 5. Show CLAUDE.md diff (never auto-overwrite — user has customizations)
    if [[ -f "$tmpl_dir/CLAUDE.md" && -f "$target/CLAUDE.md" ]]; then
        local tmpl_hash proj_hash
        tmpl_hash=$(compute_template_hash "$tmpl_dir/CLAUDE.md")
        proj_hash=$(compute_template_hash "$target/CLAUDE.md")

        if [[ "$tmpl_hash" != "$proj_hash" ]]; then
            echo ""
            echo "  [info] CLAUDE.md differs from template (expected — you have customizations)"
            if [[ "$dry_run" == "1" ]]; then
                echo "  Template CLAUDE.md diff:"
                diff --unified=3 "$target/CLAUDE.md" "$tmpl_dir/CLAUDE.md" 2>/dev/null | head -40 || true
            else
                echo "  Review manually: diff '$target/CLAUDE.md' '$tmpl_dir/CLAUDE.md'"
            fi
        fi
    fi

    # 6. Update template.json (preserve initialized, add lastSynced)
    if [[ "$dry_run" != "1" ]]; then
        local hash initialized
        hash=$(compute_template_hash "$tmpl_dir/CLAUDE.md")
        initialized=""
        if [[ -f "$target/.claude/template.json" ]]; then
            if command_exists jq; then
                initialized=$(jq -r '.initialized // empty' "$target/.claude/template.json" 2>/dev/null)
            else
                initialized=$(grep -o '"initialized"[[:space:]]*:[[:space:]]*"[^"]*"' "$target/.claude/template.json" 2>/dev/null | head -1 | sed 's/.*"initialized"[[:space:]]*:[[:space:]]*"//; s/"//')
            fi
        fi
        if [[ -z "$initialized" ]]; then
            initialized=$(date -u +%Y-%m-%dT%H:%M:%SZ)
        fi
        mkdir -p "$target/.claude"
        cat > "$target/.claude/template.json" << TMPLEOF
{
  "name": "$template_name",
  "initialized": "$initialized",
  "lastSynced": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "templateHash": "$hash"
}
TMPLEOF
    fi

    if [[ "$dry_run" != "1" ]]; then
        local memkernel_status
        memkernel_status=$(ensure_memkernel_settings_local "$target")
        case "$memkernel_status" in
            created)
                echo "  [add] .claude/settings.local.json (MemKernel MCP)"
                changes=$((changes + 1))
                ;;
            updated)
                echo "  [upd] .claude/settings.local.json (MemKernel MCP)"
                changes=$((changes + 1))
                ;;
            warn-no-jq)
                echo "  [warn] jq not installed — could not add MemKernel MCP config"
                ;;
            warn-merge)
                echo "  [warn] Could not safely merge MemKernel MCP config"
                ;;
        esac
    fi

    echo ""
    if [[ $changes -eq 0 ]]; then
        echo "Already up to date."
    elif [[ "$dry_run" == "1" ]]; then
        echo "$changes file(s) would be updated. Run without --dry-run to apply."
    else
        echo "$changes file(s) synced."
    fi
}

start_tmux_session() {
    local project_dir="${1:-.}"
    local session_start
    local run_cmd

    # Resolve to absolute path
    if [[ ! -d "$project_dir" ]]; then
        echo "Error: Directory '$project_dir' does not exist."
        echo "To create a new project, use: claude-code init <type> $project_dir"
        exit 1
    fi
    project_dir=$(cd "$project_dir" && pwd)

    # Generate session name from directory (sanitize for tmux)
    local dir_name=$(basename "$project_dir")
    local session_name="claude-${dir_name//[^a-zA-Z0-9_-]/_}"

    # Show project context summary
    echo "──────────────────────────────────────"
    echo "  Project: $dir_name"
    if git -C "$project_dir" rev-parse --is-inside-work-tree &>/dev/null; then
        local branch=$(git -C "$project_dir" rev-parse --abbrev-ref HEAD 2>/dev/null)
        local last_commit=$(git -C "$project_dir" log -1 --oneline 2>/dev/null)
        local dirty=$(git -C "$project_dir" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
        echo "  Branch:  $branch"
        echo "  Latest:  $last_commit"
        [[ "$dirty" -gt 0 ]] && echo "  Status:  $dirty uncommitted change(s)"
    fi
    [[ -f "$project_dir/CLAUDE.md" ]] && echo "  Config:  CLAUDE.md found" || echo "  Config:  No CLAUDE.md (run 'claude-code init <type>')"
    if [[ "$PEER_REVIEW_ENABLED" == "true" ]]; then
        echo "  Peer:    ${PEER_PROVIDER:-profile default} (scope: $PEER_SCOPE)"
    fi

    # Session transcript logging
    mkdir -p "$LOGS_DIR"
    local timestamp=$(date +%Y-%m-%d-%H%M%S)
    local log_file="$LOGS_DIR/${dir_name}-${timestamp}.log"

    echo "  Shell:   tmux"
    echo "  Log:     $log_file"
    echo "──────────────────────────────────────"
    echo ""

    # If already inside tmux, run directly in the current pane
    if is_inside_tmux; then
        echo "Launching Claude in the current tmux pane..."
        exec_claude_in_current_shell "$project_dir" "$log_file"
    fi

    # Check if session already exists
    if tmux has-session -t "$session_name" 2>/dev/null; then
        # If peer-review flags changed, recreate the session so env vars are injected
        if [[ -n "$PEER_REVIEW_ENABLED" ]]; then
            echo "Recreating session '$session_name' with peer-review config..."
            tmux kill-session -t "$session_name" 2>/dev/null
        else
            echo "Session '$session_name' exists. Attaching..."
            # Start logging on the existing session (append to new log file)
            tmux pipe-pane -t "$session_name" -o "sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' >> '$log_file'"
            tmux attach-session -t "$session_name"
            return
        fi
    fi

    echo "Creating session '$session_name'..."
    echo "  Log: $log_file"
    session_start=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    run_cmd=$(build_run_cmd_string "$session_start")
    # Create detached, set up logging, then attach
    tmux new-session -d -s "$session_name" -c "$project_dir" "$run_cmd"
    tmux pipe-pane -t "$session_name" -o "sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' >> '$log_file'"
    # Show peer review banner inside tmux (after attach redraws the terminal)
    if [[ "$PEER_REVIEW_ENABLED" == "true" ]]; then
        tmux display-message -t "$session_name" \
            "Peer review: ${PEER_PROVIDER:-profile default} (scope: $PEER_SCOPE)"
    fi
    tmux attach-session -t "$session_name"
}

start_cmux_session() {
    local project_dir="${1:-.}"
    local dir_name session_start launch_cmd log_file

    if [[ ! -d "$project_dir" ]]; then
        echo "Error: Directory '$project_dir' does not exist."
        echo "To create a new project, use: claude-code init <type> $project_dir"
        exit 1
    fi
    project_dir=$(cd "$project_dir" && pwd)
    dir_name=$(basename "$project_dir")
    log_file=$(create_log_file "$dir_name")

    echo "──────────────────────────────────────"
    echo "  Project: $dir_name"
    if git -C "$project_dir" rev-parse --is-inside-work-tree &>/dev/null; then
        local branch last_commit dirty
        branch=$(git -C "$project_dir" rev-parse --abbrev-ref HEAD 2>/dev/null)
        last_commit=$(git -C "$project_dir" log -1 --oneline 2>/dev/null)
        dirty=$(git -C "$project_dir" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
        echo "  Branch:  $branch"
        echo "  Latest:  $last_commit"
        [[ "$dirty" -gt 0 ]] && echo "  Status:  $dirty uncommitted change(s)"
    fi
    [[ -f "$project_dir/CLAUDE.md" ]] && echo "  Config:  CLAUDE.md found" || echo "  Config:  No CLAUDE.md (run 'claude-code init <type>')"
    if [[ "$PEER_REVIEW_ENABLED" == "true" ]]; then
        echo "  Peer:    ${PEER_PROVIDER:-profile default} (scope: $PEER_SCOPE)"
    fi
    echo "  Shell:   cmux"
    echo "  Log:     $log_file"
    echo "──────────────────────────────────────"
    echo ""

    if is_inside_cmux; then
        echo "Launching Claude in the current cmux terminal..."
        exec_claude_in_current_shell "$project_dir" "$log_file"
    fi

    session_start=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    launch_cmd=$(build_launch_command "$project_dir" "$session_start" "$log_file")

    if command_exists cmux && launch_cmux_with_cli "$launch_cmd"; then
        echo "Opened a new cmux workspace and launched Claude."
        return
    fi

    if launch_cmux_with_osascript "$launch_cmd"; then
        echo "Opened a new cmux workspace and launched Claude."
        return
    fi

    copy_to_clipboard "$launch_cmd"
    echo "Unable to automate cmux launch."
    echo "The Claude launch command has been copied to your clipboard."
    echo "Paste it into a new cmux workspace:"
    echo "  $launch_cmd"
    exit 1
}

start_session() {
    local project_dir="${1:-.}"
    local selected_backend

    selected_backend=$(resolve_session_backend)

    # Inside an unsupported multiplexer (e.g. Zellij) — run directly
    if [[ "$selected_backend" == "direct" ]]; then
        if [[ ! -d "$project_dir" ]]; then
            echo "Error: Directory '$project_dir' does not exist."
            exit 1
        fi
        project_dir=$(cd "$project_dir" && pwd)
        local dir_name log_file
        dir_name=$(basename "$project_dir")
        log_file=$(create_log_file "$dir_name")
        echo "[info] Detected active Zellij session — launching Claude directly (Zellij is no longer a managed backend)."
        exec_claude_in_current_shell "$project_dir" "$log_file"
    fi

    # If explicitly requested, fail hard if unavailable
    if [[ "$SESSION_BACKEND" != "$BACKEND_AUTO" ]]; then
        case "$selected_backend" in
            "$BACKEND_CMUX")
                ensure_cmux_available || { echo "Error: cmux backend was requested explicitly but is unavailable."; exit 1; }
                start_cmux_session "$project_dir"
                return
                ;;
            "$BACKEND_TMUX")
                ensure_tmux_available || exit 1
                start_tmux_session "$project_dir"
                return
                ;;
            *)
                echo "Error: Unsupported shell backend '$selected_backend'. Use auto, cmux, or tmux."
                exit 1
                ;;
        esac
    fi

    # Auto mode: try resolved backend, then fall through alternatives
    if [[ "$selected_backend" == "$BACKEND_CMUX" ]] && ensure_cmux_available 2>/dev/null; then
        start_cmux_session "$project_dir"
        return
    fi

    if [[ "$selected_backend" == "$BACKEND_TMUX" ]] && command_exists tmux; then
        start_tmux_session "$project_dir"
        return
    fi

    # Resolved choice unavailable — try remaining backends in preference order
    if is_macos && ensure_cmux_available 2>/dev/null; then
        echo "[info] $selected_backend is unavailable; falling back to cmux."
        start_cmux_session "$project_dir"
        return
    fi

    if ensure_tmux_available; then
        echo "[info] $selected_backend is unavailable; falling back to tmux."
        start_tmux_session "$project_dir"
        return
    fi

    echo "Error: No session backend available. Install cmux or tmux."
    exit 1
}

# ── Main ─────────────────────────────────────────────────────

# ── Flag parsing ─────────────────────────────────────────────
PLAYWRIGHT=0
LICENSE_TYPE=""
PEER_REVIEW_ENABLED=""
PEER_PROVIDER=""
PEER_SCOPE="both"
REVIEW_COMMITS="squash"
REVIEW_MAX_ROUNDS=""
CLAUDE_ARGS=()
POSITIONAL=()

while [[ $# -gt 0 ]]; do
    case "$1" in
        --continue|-c)
            CLAUDE_ARGS+=("--continue")
            shift
            ;;
        --resume|-r)
            CLAUDE_ARGS+=("--resume")
            shift
            ;;
        --peer-review)
            PEER_REVIEW_ENABLED="true"
            # Next arg is provider name only if it's not a flag, not a path, and not an existing directory
            if [[ -n "${2:-}" && "${2:0:2}" != "--" && "${2:0:1}" != "/" && "${2:0:2}" != "./" && "${2:0:1}" != "~" && "$2" != */* && ! -d "$2" ]]; then
                PEER_PROVIDER="$2"
                shift
            fi
            shift
            ;;
        --peer-review-off)
            PEER_REVIEW_ENABLED="false"
            shift
            ;;
        --peer-review-scope)
            PEER_SCOPE="${2:-both}"
            shift 2
            ;;
        --review-commits)
            REVIEW_COMMITS="${2:-squash}"
            case "$REVIEW_COMMITS" in
                single|per-round|squash) ;;
                *) echo "Error: --review-commits must be single, per-round, or squash"; exit 1 ;;
            esac
            shift 2
            ;;
        --review-max-rounds)
            REVIEW_MAX_ROUNDS="${2:?--review-max-rounds requires a number}"
            if ! [[ "$REVIEW_MAX_ROUNDS" =~ ^[0-9]+$ ]]; then
                echo "Error: --review-max-rounds must be a positive integer, got '$REVIEW_MAX_ROUNDS'"
                exit 1
            fi
            shift 2
            ;;
        --shell)
            if [[ -z "${2:-}" ]]; then
                echo "Error: --shell requires one of: auto, cmux, tmux"
                exit 1
            fi
            SESSION_BACKEND="${2:-$BACKEND_AUTO}"
            case "$SESSION_BACKEND" in
                "$BACKEND_AUTO"|"$BACKEND_CMUX"|"$BACKEND_TMUX")
                    ;;
                *)
                    echo "Error: Unsupported shell backend '$SESSION_BACKEND'. Use auto, cmux, or tmux."
                    exit 1
                    ;;
            esac
            shift 2
            ;;
        --playwright)
            PLAYWRIGHT=1
            shift
            ;;
        *)
            # Subcommand boundary: once we hit a known wrapper subcommand,
            # stop global parsing so the subcommand owns its remaining
            # flags. Without this, `claude-code sync --dry-run` would
            # forward --dry-run to claude before the sync parser ran,
            # and `claude-code init <type> --license MIT` would lose
            # --license the same way.
            #
            # Otherwise: unknown --flag → forward verbatim to claude.
            # Boolean flags work as-is (e.g. --dangerously-skip-permissions).
            # For value-bearing flags use the --flag=value form to avoid
            # ambiguity with the project-dir positional on the
            # start-session path.
            case "$1" in
                init|list|sync|playwright|help|-h|--help)
                    POSITIONAL+=("$@")
                    break
                    ;;
                --*)
                    CLAUDE_ARGS+=("$1")
                    shift
                    ;;
                *)
                    POSITIONAL+=("$1")
                    shift
                    ;;
            esac
            ;;
    esac
done
set -- "${POSITIONAL[@]+"${POSITIONAL[@]}"}"

# Append claude-specific flags to the command. CLAUDE_CMD is an array
# so multi-token forms (e.g. "claude" "--dangerously-skip-permissions")
# can be exec'd correctly without bash collapsing them into a single
# token whose path doesn't resolve.
if [[ ${#CLAUDE_ARGS[@]} -gt 0 ]]; then
    CLAUDE_CMD+=("${CLAUDE_ARGS[@]}")
fi

case "${1:-}" in
    init)
        shift
        # Parse --playwright and --license flags
        ARGS=()
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --playwright)
                    PLAYWRIGHT=1
                    shift
                    ;;
                --license)
                    if [[ -z "${2:-}" || "${2:0:2}" == "--" ]]; then
                        echo "Error: --license requires a value (e.g. --license MIT)"
                        exit 1
                    fi
                    LICENSE_TYPE="$2"
                    shift 2
                    ;;
                *)
                    ARGS+=("$1")
                    shift
                    ;;
            esac
        done
        if [[ ${#ARGS[@]} -eq 0 ]]; then
            echo "Error: Missing project type."
            echo "Usage: claude-code init <type> [path] [--playwright] [--license <type>]"
            echo ""
            list_templates
            exit 1
        fi
        init_project "${ARGS[0]}" "${ARGS[1]:-}"
        ;;
    list)
        list_templates
        ;;
    sync)
        shift
        SYNC_DRY_RUN=0
        SYNC_ARGS=()
        for arg in "$@"; do
            if [[ "$arg" == "--dry-run" ]]; then
                SYNC_DRY_RUN=1
            else
                SYNC_ARGS+=("$arg")
            fi
        done
        sync_project "${SYNC_ARGS[0]:-.}" "$SYNC_DRY_RUN"
        ;;
    playwright)
        target="${2:-.}"
        if [[ ! -d "$target" ]]; then
            echo "Error: Directory '$target' does not exist."
            exit 1
        fi
        target=$(cd "$target" && pwd)
        if command -v playwright-cli &>/dev/null; then
            (cd "$target" && playwright-cli install --skills) \
                && echo "[done] Playwright CLI skills installed in $target" \
                || echo "[FAIL] Playwright skills install failed"
        else
            echo "Error: playwright-cli not found."
            echo "Install first: npm install -g @playwright/cli@latest"
            exit 1
        fi
        ;;
    help|-h|--help)
        show_help
        ;;
    *)
        start_session "${1:-}"
        ;;
esac
