#!/usr/bin/env bash
# Info: Script to run Provisioning
# Author: Jesus Perez Lorenzo
# Release: 3.0.11
# Date: 2026-01-14

set +o errexit
set +o pipefail

# Debug: log startup
[ "${PROVISIONING_DEBUG_STARTUP:-false}" = "true" ] && echo "[DEBUG] Wrapper started with args: $@" >&2

export NU=$(type -P nu)

_release() {
    grep "^# Release:" "$0" | sed "s/# Release: //g"
}

export PROVISIONING_VERS=$(_release)

set -o allexport
## shellcheck disable=SC1090
[ -n "$PROVISIONING_ENV" ] && [ -r "$PROVISIONING_ENV" ] && source "$PROVISIONING_ENV"
[ -r "../env-provisioning" ] && source ../env-provisioning
[ -r "env-provisioning" ] && source ./env-provisioning
#[ -r ".env" ] && source .env set

# Show provisioning logo/banner by default (can be overridden by env var)
export PROVISIONING_NO_TITLES=${PROVISIONING_NO_TITLES:-true}

set +o allexport

export PROVISIONING=${PROVISIONING:-/usr/local/provisioning}

# For development: search upward from script location to find provisioning directory
if [ ! -d "$PROVISIONING/resources" ]; then
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    current="$SCRIPT_DIR"
    # Search up to 5 levels up from script directory
    for _ in {1..5}; do
        if [ -d "$current/provisioning/resources" ]; then
            export PROVISIONING="$current/provisioning"
            break
        fi
        parent="$(dirname "$current")"
        [ "$parent" = "$current" ] && break # Stop at filesystem root
        current="$parent"
    done
fi

export PROVISIONING_RESOURCES=${PROVISIONING_RESOURCES:-"$PROVISIONING/resources"}
PROVIISONING_WKPATH=${PROVIISONING_WKPATH:-/tmp/tmp.}

RUNNER="provisioning-cli.nu"
PROVISIONING_MODULE=""
PROVISIONING_MODULE_TASK=""

# Main help function (defined early for early help detection)
_show_help() {
    local category="${1:-}"

    # If help cache available and fresh, use it for speed
    if [ -n "$HELP_CACHE_DIR" ] && [ -f "$HELP_CACHE_DIR/main.txt" ]; then
        local cache_age=$(($(date +%s) - $(stat -f %m "$HELP_CACHE_DIR/main.txt" 2>/dev/null || echo 0)))
        if [ "$cache_age" -lt "$HELP_CACHE_TTL" ]; then
            cat "$HELP_CACHE_DIR/main.txt"
            return 0
        fi
    fi

    # Fallback: Call Nushell for help via single CLI entry
    $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" help $category
}

# Workflow help function (defined early for early help detection)
_workflow_help() {
    echo "Workflow Management Commands"
    echo ""
    echo "Available commands:"
    echo "  l  | list         - List workflows"
    echo "  s  | status       - Show workflow status"
    echo "  m  | monitor      - Monitor workflow progress"
    echo "  st | stats        - Show workflow statistics"
    echo "  c  | cleanup      - Clean up old workflows"
    echo "  b  | browse       - Browse workflows"
    echo "  o  | orchestrator - Show orchestrator health"
    echo ""
    echo "Usage:"
    echo "  provisioning workflow [command] [arguments]"
    echo "  provisioning workflow <number>      - List with limit"
    echo ""
    echo "Examples:"
    echo "  provisioning wf l       - List workflows"
    echo "  provisioning wf 5       - List last 5 workflows"
    echo "  provisioning wf st      - Show statistics"
    echo "  provisioning wf s <id>  - Show status of specific task"
}

# ════════════════════════════════════════════════════════════════════════════════
# Daemon Routing Helpers - Route operations to provisioning-daemon (port 9095)
# ════════════════════════════════════════════════════════════════════════════════

# Get daemon port from user configuration (or default to 9095)
# Reads from: ~/.config/provisioning/daemon.conf or PROVISIONING_DAEMON_PORT env var
_get_daemon_port() {
    local port
    # Priority 1: Environment variable
    if [ -n "${PROVISIONING_DAEMON_PORT:-}" ]; then
        echo "$PROVISIONING_DAEMON_PORT"
        return
    fi

    # Priority 2: User config file
    local config_file="${HOME}/.config/provisioning/daemon.conf"
    if [ -f "$config_file" ]; then
        port=$(grep "^DAEMON_PORT=" "$config_file" | cut -d'=' -f2 | tr -d '[:space:]')
        if [ -n "$port" ]; then
            echo "$port"
            return
        fi
    fi

    # Default port
    echo "9095"
}

DAEMON_PORT=$(_get_daemon_port)
DAEMON_ENDPOINT="http://127.0.0.1:${DAEMON_PORT}"
DAEMON_EXECUTE_ENDPOINT="${DAEMON_ENDPOINT}/api/v1/execute"
DAEMON_TIMEOUT_FAST="0.5"   # Help/quick operations: 500ms
DAEMON_TIMEOUT_NORMAL="1.0" # Template rendering: 1s
DAEMON_TIMEOUT_BATCH="5.0"  # Batch operations: 5s

# Cache directory for help and other CLI outputs
HELP_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/provisioning/help"
HELP_CACHE_TTL=86400 # 24 hours in seconds

# ════════════════════════════════════════════════════════════════════════════════
# Help Cache Functions - Instant help output (after first run)
# ════════════════════════════════════════════════════════════════════════════════

# Get cache file path for a help category
_get_cache_path() {
    echo "${HELP_CACHE_DIR}/$1.txt"
}

# Check if cache is valid (not expired)
_is_cache_valid() {
    local cache_file="$1"
    local now
    local mtime
    local age
    [ ! -f "$cache_file" ] && return 1

    now=$(date +%s)
    mtime=$(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file" 2>/dev/null || echo 0)
    age=$((now - mtime))

    [ $age -lt $HELP_CACHE_TTL ] && return 0
    return 1
}

# Store help output in cache (handle special characters safely)
_cache_help() {
    local category="$1"
    local content="$2"

    mkdir -p "$HELP_CACHE_DIR"
    # Use printf to safely handle newlines and special characters
    printf '%s\n' "$content" >"$(_get_cache_path "$category")"
}

# Get help from cache (if valid) or fetch fresh
_get_help_cached() {
    local category="$1"
    local cache_file
    cache_file="$(_get_cache_path "$category")"

    # Try cache first (instant!)
    if _is_cache_valid "$cache_file"; then
        cat "$cache_file"
        return 0
    fi

    # Cache miss or expired - fetch fresh from daemon or Nushell
    return 1
}

# Try daemon first with timeout, fall back to direct execution
# Usage: _route_daemon_or_fallback "command_name" "timeout" "fallback_cmd"
_route_daemon_or_fallback() {
    local cmd_name="$1"
    local timeout="$2"
    local fallback_cmd="$3"
    shift 3
    local cmd_args=("$@")
    local response
    local json_args

    if command -v timeout &>/dev/null && command -v curl &>/dev/null; then
        # Build JSON payload for daemon
        json_args=$(printf '%s\n' "${cmd_args[@]}" | jq -R . | jq -s .)
        payload="{\"command\": \"$cmd_name\", \"args\": $json_args}"

        # Try daemon with timeout
        response=$(timeout "$timeout" curl -s -m "$timeout" -X POST "$DAEMON_ENDPOINT" \
            -H "Content-Type: application/json" \
            -d "$payload" 2>/dev/null)

        if [ -n "$response" ] && [ "$response" != "null" ] && [ "$response" != "{}" ]; then
            echo "$response"
            return 0
        fi
    fi

    # Fallback: execute directly
    eval "$fallback_cmd"
}

# Daemon render wrapper for tera templates
# Usage: _daemon_render "template_path" "context_json_file"
_daemon_render() {
    local template_path="$1"
    local context_file="$2"
    local context
    local payload

    context=$(cat "$context_file" 2>/dev/null)
    payload="{\"command\": \"tera-render\", \"template\": \"$(cat "$template_path")\", \"context\": $context}"

    if command -v timeout &>/dev/null && command -v curl &>/dev/null; then
        timeout "$DAEMON_TIMEOUT_NORMAL" curl -s -m "$DAEMON_TIMEOUT_NORMAL" -X POST "$DAEMON_ENDPOINT" \
            -H "Content-Type: application/json" \
            -d "$payload" 2>/dev/null
        return $?
    fi

    return 1
}

# Safe argument handling - use default empty value if unbound
[ "${1:-}" == "" ] && shift

[ -z "$NU" ] || [ "${1:-}" == "install" ] || [ "${1:-}" == "reinstall" ] || [ "${1:-}" == "mode" ] && exec bash $PROVISIONING/core/bin/install_nu.sh $PROVISIONING ${1:-} ${2:-}

[ "${1:-}" == "rmwk" ] && rm -rf "$PROVIISONING_WKPATH"* && echo "$PROVIISONING_WKPATH deleted" && exit
[ "${1:-}" == "-x" ] && debug=-x && export PROVISIONING_DEBUG=true && shift
[ "${1:-}" == "-xm" ] && export PROVISIONING_METADATA=true && shift
[ "${1:-}" == "nu" ] && export PROVISIONING_DEBUG=true
[ "${1:-}" == "--x" ] && set -x && debug=-x && export PROVISIONING_DEBUG=true && shift
[ "${1:-}" == "-i" ] || [ "${2:-}" == "-i" ] && echo "$(basename "$0") $(grep "^# Info:" "$0" | sed "s/# Info: //g") " && exit
[ "${1:-}" == "-v" ] || [ "${1:-}" == "--version" ] || [ "${2:-}" == "-v" ] && _release && exit

# ════════════════════════════════════════════════════════════════════════════════
# EARLY DETECTION - Avoid expensive parsing for no-args and workflow help
# ════════════════════════════════════════════════════════════════════════════════

# No arguments at all - show quick usage (don't load Nushell)
if [ -z "$1" ]; then
    echo "Usage: provisioning [command] [options]"
    echo ""
    echo "Use 'provisioning help' for available commands"
    exit 0
fi

# Job help detection (before expensive parsing) — "job" is the orchestrator job command
case "$1" in
    job|j)
        case "$2" in
            help|-h|--help|-help)
                _workflow_help
                exit 0
                ;;
        esac
        ;;
esac

# ════════════════════════════════════════════════════════════════════════════════
# FLOW-AWARE TTY COMMAND FILTER
# Manages three execution flows: exit (standalone), pipe (inter-command), continue (Nushell)
# Registry: provisioning/core/cli/tty-commands.conf
# Filter:   provisioning/core/cli/tty-filter.sh
# ════════════════════════════════════════════════════════════════════════════════
if [ -f "$PROVISIONING/core/cli/tty-filter.sh" ]; then
    # Source filter function
    # shellcheck source=/dev/null
    source "$PROVISIONING/core/cli/tty-filter.sh"

    # Try to filter TTY command (full command line as single string)
    # Return codes:
    #   - filter_tty_command returns 0: flow=continue case handled, continue to Nushell with $TTY_OUTPUT
    #   - filter_tty_command exits: flow=exit/pipe case completed (already exited)
    #   - filter returns 1: not a TTY command, continue to normal processing
    if filter_tty_command "$@"; then
        # Flow=continue: TTY wrapper executed, output in $TTY_OUTPUT, bypass daemon
        # $env.PROVISIONING_BYPASS_DAEMON and $env.TTY_OUTPUT available to Nushell
        : # Continue to Nushell dispatcher below
    fi
fi

CMD_ARGS="$*"

# Note: Flag ordering is handled by Nushell's reorder_args function
# which automatically reorders flags before positional arguments.
# Flags can be placed anywhere on the command line.
case "${1:-}" in
# Note: "setup" is now handled by the main provisioning CLI dispatcher
# No special module handling needed
-mod)
    PROVISIONING_MODULE=$(echo "$2" | sed 's/ //g' | cut -f1 -d"|")
    PROVISIONING_MODULE_TASK=$(echo "$2" | sed 's/ //g' | cut -f2 -d"|")
    [ "$PROVISIONING_MODULE" == "$PROVISIONING_MODULE_TASK" ] && PROVISIONING_MODULE_TASK=""
    shift 2
    CMD_ARGS="$*"
    [ "${PROVISIONING_DEBUG_STARTUP:-false}" = "true" ] && echo "[DEBUG] -mod detected: MODULE=$PROVISIONING_MODULE, TASK=$PROVISIONING_MODULE_TASK, CMD_ARGS=$CMD_ARGS" >&2
    ;;
esac
NU_ARGS=""

DEFAULT_CONTEXT_TEMPLATE="default_context.yaml"
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
    PROVISIONING_USER_CONFIG="$HOME/.config/provisioning/nushell"
    PROVISIONING_CONTEXT_PATH="$HOME/.config/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
    PROVISIONING_USER_PLATFORM="$HOME/.config/provisioning/platform"
    ;;
darwin)
    PROVISIONING_USER_CONFIG="$HOME/Library/Application Support/provisioning/nushell"
    PROVISIONING_CONTEXT_PATH="$HOME/Library/Application Support/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
    PROVISIONING_USER_PLATFORM="$HOME/Library/Application Support/provisioning/platform"
    ;;
*)
    PROVISIONING_USER_CONFIG="$HOME/.config/provisioning/nushell"
    PROVISIONING_CONTEXT_PATH="$HOME/.config/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
    PROVISIONING_USER_PLATFORM="$HOME/.config/provisioning/platform"
    ;;
esac

# ════════════════════════════════════════════════════════════════════════════════
# Workflow help function (DRY) - defined early for use in global help handler
_workflow_help() {
    echo "Workflow Management Commands"
    echo ""
    echo "Available commands:"
    echo "  l  | list         - List workflows"
    echo "  s  | status       - Show workflow status"
    echo "  m  | monitor      - Monitor workflow progress"
    echo "  st | stats        - Show workflow statistics"
    echo "  c  | cleanup      - Clean up old workflows"
    echo "  b  | browse       - Browse workflows"
    echo "  o  | orchestrator - Show orchestrator health"
    echo ""
    echo "Usage:"
    echo "  provisioning workflow [command] [arguments]"
    echo "  provisioning workflow <number>      - List with limit"
    echo ""
    echo "Examples:"
    echo "  provisioning wf l       - List workflows"
    echo "  provisioning wf 5       - List last 5 workflows"
    echo "  provisioning wf st      - Show statistics"
    echo "  provisioning wf s <id>  - Show status of specific task"
}

# DAEMON ROUTING - Try daemon for all commands (except setup/help/interactive)
# Falls back to traditional handlers if daemon unavailable
# ════════════════════════════════════════════════════════════════════════════════

# NOTE: DAEMON_ENDPOINT is already defined above as http://127.0.0.1:9095
# Do NOT redefine it here

# Function to execute command via daemon
execute_via_daemon() {
    local cmd="$1"
    shift
    local cwd_json
    local response

    # Build JSON array of arguments (simple bash)
    local args_json="["
    local first=1
    for arg in "$@"; do
        [ $first -eq 0 ] && args_json="$args_json,"
        args_json="$args_json\"$(echo "$arg" | sed 's/"/\\"/g')\""
        first=0
    done
    args_json="$args_json]"
    cwd_json=$(printf '%s' "$PWD" | sed 's/\\/\\\\/g; s/"/\\"/g')

    # Determine timeout based on command type
    # Heavy commands (create, delete, update) get longer timeout
    local timeout=0.5
    case "$cmd" in
    create | delete | update | setup | init) timeout=5 ;;
    *) timeout=0.2 ;;
    esac

    # Make request and extract stdout
    response=$(curl -s -m $timeout -X POST "$DAEMON_EXECUTE_ENDPOINT" \
        -H "Content-Type: application/json" \
        -d "{\"command\":\"$cmd\",\"args\":$args_json,\"cwd\":\"$cwd_json\",\"timeout_ms\":30000}" 2>/dev/null)

    if [ -z "$response" ] || [ "$response" = "null" ] || [ "$response" = "{}" ]; then
        return 1
    fi

    if command -v jq >/dev/null 2>&1; then
        printf '%s' "$response" | jq -r '.stdout // empty'
    else
        printf '%s' "$response" |
            sed -n 's/.*"stdout":"\(.*\)","execution.*/\1/p' |
            sed 's/\\n/\n/g'
    fi
}

# Intercept: server volume → volume (avoids loading full server module)
if [ "${1:-}" = "server" ] || [ "${1:-}" = "s" ]; then
    if [ "${2:-}" = "volume" ] || [ "${2:-}" = "vol" ]; then
        shift 2
        exec "$0" volume "$@"
    fi
fi

# Try daemon ONLY for lightweight commands (list, show, status)
# Skip daemon for heavy commands (create, delete, update) because bash wrapper is slow
# ALSO skip daemon for flow=continue commands (need stdin for TTY interaction)
if [ "${PROVISIONING_BYPASS_DAEMON:-}" != "true" ] && [ "${PROVISIONING_NO_DAEMON:-}" != "true" ] && ([ "${1:-}" = "server" ] || [ "${1:-}" = "s" ]); then
    if [ "${2:-}" = "list" ] || [ "${2:-}" = "ls" ] || [ "${2:-}" = "l" ] || [ -z "${2:-}" ]; then
        # Light command - try daemon
        [ -n "${PROVISIONING_DEBUG:-}" ] && [ "${PROVISIONING_DEBUG:-}" = "true" ] && echo "⚡ Attempting daemon execution..." >&2
        DAEMON_OUTPUT=$(execute_via_daemon "$@" 2>/dev/null)
        if [ -n "$DAEMON_OUTPUT" ]; then
            echo "$DAEMON_OUTPUT"
            exit 0
        fi
        [ -n "${PROVISIONING_DEBUG:-}" ] && [ "${PROVISIONING_DEBUG:-}" = "true" ] && echo "⚠️ Daemon unavailable, using traditional handlers..." >&2
    fi
    # NOTE: Command reordering (server create -> create server) has been removed.
    # The Nushell dispatcher in provisioning/core/nulib/main_provisioning/dispatcher.nu
    # handles command routing correctly and expects "server create" format.
    # The reorder_args function in provisioning script handles any flag reordering needed.
fi

# ════════════════════════════════════════════════════════════════════════════════
# FAST-PATH: Commands that don't need full config loading or platform bootstrap
# These commands use lib_minimal.nu for <100ms execution
# (ONLY REACHED if daemon is not available)
# ═══���════════════════════════════════════════════════════════════════════════════

# Help commands fast-path (uses help_minimal.nu)
# Detects "help" in ANY argument position, not just first

# Normalize help category aliases to canonical names
_normalize_help_category() {
    local category="$1"
    case "$category" in
    # Infrastructure aliases
    s | server | infra | i) echo "infrastructure" ;;

    # Orchestration aliases
    wf | flow | workflow | orch | orchestrator | bat | batch) echo "orchestration" ;;

    # Development aliases
    mod | module | lyr | layer | pack | dev) echo "development" ;;

    # Workspace aliases
    ws | workspace | tpl | tmpl | template) echo "workspace" ;;

    # Platform aliases
    p | plat | platform) echo "platform" ;;

    # Setup aliases
    st | setup | config) echo "setup" ;;

    # Authentication aliases
    auth | authentication) echo "authentication" ;;

    # Plugin aliases
    plugin | plugins) echo "plugins" ;;

    # Utilities aliases
    utils | utilities | cache) echo "utilities" ;;

    # Diagnostics aliases
    diag | diagnostics | status | health) echo "diagnostics" ;;

    # Other categories
    orchestration | development | workspace | authentication | mfa | plugins | utilities | tools | vm | diagnostics | concepts | guides | integrations | build | infrastructure | setup)
        echo "$category"
        ;;

    # Unknown - return as-is
    *) echo "$category" ;;
    esac
}

help_category=""
help_found=false
help_subcmd=""          # subcommand after the main command (e.g. "delete" in "server delete --help")
_pos_count=0            # count of positional (non-flag, non-help) args

# Check if first arg is empty (no args provided) - treat as help request
if [ -z "${1:-}" ]; then
    help_found=true
else
    # Loop through all arguments to find help variant and extract category
    for arg in "$@"; do
        case "$arg" in
        help | h | -h | --help | --helpinfo)
            help_found=true
            ;;
        -*)
            # Skip flags (like -x, -xm, -i, -v, etc.)
            ;;
        *)
            _pos_count=$((_pos_count + 1))
            if [ "$help_category" = "" ]; then
                help_category="$(_normalize_help_category "$arg")"
            elif [ "$help_subcmd" = "" ]; then
                help_subcmd="$arg"   # second positional = subcommand
            fi
            ;;
        esac
    done
fi

# If help was requested for a SUBCOMMAND (e.g. "server delete --help"),
# clear help_found so the fast-path is skipped and the Nu module handles --help.
if [ "$help_found" = true ] && [ -n "$help_subcmd" ]; then
    help_found=false
fi

# Execute help fast-path if help was requested
if [ "$help_found" = true ]; then
    # List of known help categories - if not in this list, let command handle --help
    case "$help_category" in
    infrastructure | orchestration | development | workspace | setup | platform | authentication | mfa | plugins | utilities | tools | vm | diagnostics | concepts | guides | integrations | build)
        # TIER 1: Try local cache first (instant! <1ms)
        if _get_help_cached "$help_category"; then
            exit 0
        fi

        # TIER 2: Try daemon next - DISABLED (daemon not critical for help)
        # The daemon is optional - help can be generated directly via Nushell

        # TIER 3: Fall back to Nushell (slower ~2-3s)
        export LANG

        # Execute Nushell help and capture output
        HELP_OUTPUT=$($NU -n -c "source '$PROVISIONING/core/nulib/help_minimal.nu'; provisioning-help '$help_category' | print")

        # Cache the output for next time (if not empty)
        if [ -n "$HELP_OUTPUT" ]; then
            _cache_help "$help_category" "$HELP_OUTPUT"
            echo "$HELP_OUTPUT"
            exit 0
        else
            # If output is empty, exit gracefully
            exit 1
        fi
        ;;
    "")
        # No category specified - show main help with all categories
        # TIER 1: Try local cache for main help
        if _get_help_cached "main"; then
            exit 0
        fi

        # TIER 2: Try daemon next
        if command -v timeout &>/dev/null && command -v curl &>/dev/null; then
            DAEMON_OUTPUT=$(timeout 0.5 curl -s -m 0.5 -X POST "$DAEMON_ENDPOINT" \
                -H "Content-Type: application/json" \
                -d "{\"command\": \"help\", \"args\": []}" 2>/dev/null)
            if [ -n "$DAEMON_OUTPUT" ] && [ "$DAEMON_OUTPUT" != "null" ] && [ "$DAEMON_OUTPUT" != "{}" ]; then
                # Store in cache for next time
                _cache_help "main" "$DAEMON_OUTPUT"
                echo "$DAEMON_OUTPUT"
                exit 0
            fi
        fi

        # TIER 3: Fall back to Nushell
        export LANG
        HELP_OUTPUT=$($NU -n -c "source '$PROVISIONING/core/nulib/help_minimal.nu'; provisioning-help | print")

        if [ -n "$HELP_OUTPUT" ]; then
            _cache_help "main" "$HELP_OUTPUT"
            echo "$HELP_OUTPUT"
            exit 0
        else
            exit 1
        fi
        ;;
    *)
        # Unknown category/command - let the main dispatcher handle it
        # Don't process help here, just continue to normal flow
        # The dispatcher will pass --help to the command for handling
        unset help_found
        ;;
    esac
fi

# ════════════════════════════════════════════════════════════════════════════════
# Commands requiring arguments - Fast-path: serve cached help when run without args
# ════════════════════════════════════════════════════════════════════════════════

# Map command to help category (for commands that require arguments)
# Get help category from Nickel schema registry
_get_help_category_for_command() {
    local cmd="$1"
    local schema_file="$PROVISIONING/core/nulib/commands-registry.ncl"

    if [ ! -f "$schema_file" ]; then
        return 1
    fi

    # Use external Nushell script for better maintainability
    $NU "$PROVISIONING/core/nulib/scripts/get-help-category.nu" "$schema_file" "$cmd" 2>/dev/null
}

# Execute Nushell command with minimal lib (fast-path commands)
_nu_minimal() {
    local nu_command="$1"
    $NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; $nu_command" 2>/dev/null
}

# Execute Nushell command with full user config (workflow commands)
_nu_with_config() {
    local nu_command="$1"
    $NU --config "$PROVISIONING_USER_CONFIG/config.nu" --env-config "$PROVISIONING_USER_CONFIG/env.nu" -c "$nu_command"
}

# Check if first arg is a command that requires arguments and has no second arg
if [ -n "${1:-}" ] && [ -z "${2:-}" ]; then
    help_cat=$(_get_help_category_for_command "${1}")
    if [ -n "$help_cat" ]; then
        # Command requires arguments but none provided - serve cached help
        if _get_help_cached "$help_cat"; then
            exit 0
        fi
        # Fallback to normal help system if cache miss
        PROVISIONING_HELP_CATEGORY="$help_cat"
        export PROVISIONING_HELP_CATEGORY
    fi
fi

# workspace fast-path removed (ADR-025 Phase 4 — single-route principle).
# All workspace subcommands now route to main_provisioning/workspace.nu via
# the main dispatch case. --help still intercepts before full load.
if [ "${1:-}" = "workspace" ] || [ "${1:-}" = "ws" ]; then
    case "${2:-}" in
    "-help" | "h" | "help")
        exec "$0" "${1}" --help
        ;;
    esac
fi

# Status/Health check (fast-path) - DISABLED to fix dispatcher loop
# Use normal dispatcher path instead of fast-path with lib_minimal.nu
# if [ "$1" = "status" ] || [ "$1" = "health" ]; then
#   $NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; status-quick | table" 2>/dev/null
#   exit $?
# fi

# env fast-path removed (ADR-025 Phase 4 — single-route principle).
# env/allenv now route to the full dispatcher via the *) default case.

# Alias list fast-path — reads JSON cache directly in bash, no Nu process
if [ "${1:-}" = "alias" ] || [ "${1:-}" = "a" ] || [ "${1:-}" = "al" ]; then
    _ALIAS_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/provisioning/commands-registry.json"
    echo ""
    echo "ALIASES"
    echo "════════════════════════════════════════════════════"
    if [ -f "$_ALIAS_CACHE" ]; then
        # Single awk pass: extract all command→aliases pairs, then filter by category
        _alias_table=$(awk '
          BEGIN { cmd=""; als=""; in_al=0 }
          /"command": *"[^"]*"/ {
              match($0, /"command": *"[^"]*"/)
              s = substr($0, RSTART, RLENGTH)
              gsub(/"command": *"|"$/, "", s); gsub(/"/, "", s)
              cmd = s
          }
          /"aliases": *\[/ { in_al=1; als=""; next }
          in_al && /^ *"[^"]*"/ {
              match($0, /"[^"]*"/)
              a = substr($0, RSTART+1, RLENGTH-2)
              if (a != "") als = als (als==""?"":"  ") a
          }
          /^ *\]/ && in_al { in_al=0 }
          /^ *\}/ && cmd != "" && als != "" { print cmd "|" als; cmd=""; als="" }
        ' "$_ALIAS_CACHE")

        echo ""
        echo "INFRASTRUCTURE"
        echo "$_alias_table" | grep -E "^(server|taskserv|component|extension)\|" | \
            awk -F'|' '{ printf "  %-14s →  %s\n", $2, $1 }'

        echo ""
        echo "ORCHESTRATION"
        echo "$_alias_table" | grep -E "^(job|workflow|batch|orchestrator)\|" | \
            awk -F'|' '{ printf "  %-14s →  %s\n", $2, $1 }'

        echo ""
        echo "OTHER"
        echo "$_alias_table" | grep -E "^(alias|workspace|platform|build|validate|help)\|" | \
            awk -F'|' '{ printf "  %-14s →  %s\n", $2, $1 }'
        unset _alias_table
    else
        echo ""
        echo "  s               →  server"
        echo "  t  task         →  taskserv"
        echo "  c  comp         →  component"
        echo "  e  ext          →  extension"
        echo "  w  wflow        →  workflow"
        echo "  j               →  job"
        echo "  b  bat          →  batch"
        echo "  o  orch         →  orchestrator"
        echo "  a  al           →  alias"
    fi
    echo ""
    echo "════════════════════════════════════════════════════"
    echo "Tip: prvng <alias> help  →  subcommand details"
    echo ""
    exit 0
fi

# Job commands fast-path (orchestrator jobs — was "workflow")
if [ "${1:-}" = "job" ] || [ "${1:-}" = "j" ]; then
    WORKFLOW_CMD="${2:-list}"
    ARG="${3:-}"

    # Handle help commands (matches -h, -help, h, ?)
    case "$WORKFLOW_CMD" in
        -h|-help|h|\?)
            _workflow_help
            exit 0
            ;;
    esac

    # Expand short command aliases
    case "$WORKFLOW_CMD" in
    l) WORKFLOW_CMD="list" ;;
    s) WORKFLOW_CMD="status" ;;
    m) WORKFLOW_CMD="monitor" ;;
    st) WORKFLOW_CMD="stats" ;;
    b) WORKFLOW_CMD="browse" ;;
    c) WORKFLOW_CMD="cleanup" ;;
    o) WORKFLOW_CMD="orchestrator" ;;
    help) WORKFLOW_CMD="h" ;;
    -help) WORKFLOW_CMD="h" ;;
    --help) WORKFLOW_CMD="h" ;;
    esac

    # If WORKFLOW_CMD is a number, treat it as 'list <number>'
    if [ -n "$WORKFLOW_CMD" ] && [ "$WORKFLOW_CMD" -ge 0 ] 2>/dev/null; then
        ARG="$WORKFLOW_CMD"
        WORKFLOW_CMD="list"
    fi

    # Use minimal config for quick execution
    case "$WORKFLOW_CMD" in
    list)
        # Note: No < /dev/null here to allow interactive typedialog
        if [ -z "$ARG" ]; then
            _nu_with_config "use workflows/management.nu *; workflow list"
        else
            _nu_with_config "use workflows/management.nu *; workflow list $ARG"
        fi
        exit $?
        ;;
    status)
        if [ -z "$ARG" ]; then
            echo "❌ Error: workflow status requires a task ID"
            exit 1
        fi
        _nu_with_config "use workflows/management.nu *; workflow status '$ARG'"
        exit $?
        ;;
    monitor)
        if [ -z "$ARG" ]; then
            echo "❌ Error: workflow monitor requires a task ID"
            exit 1
        fi
        _nu_with_config "use workflows/management.nu *; workflow monitor '$ARG'"
        exit $?
        ;;
    stats)
        _nu_with_config "use workflows/management.nu *; workflow stats"
        exit $?
        ;;
    *)
        echo "❌ Error: unknown workflow command '$WORKFLOW_CMD'"
        echo ""
        _workflow_help
        exit 1
        ;;
    esac
fi

# provider fast-path removed (ADR-025 Phase 4 — single-route principle).
# Falls through to main dispatch case.

# Fast-paths removed (ADR-025 Phase 4 — single-route principle).
# taskserv/server/cluster `list` now route to their thin handlers which invoke
# the full semantic path (middleware + live provider state). Daemon routing
# (for server list/ls/l) is preserved further down in the dispatch case.

# infra fast-path removed (ADR-025 Phase 4 — single-route principle).
# Falls through to main dispatch case. Help with no args still shows help menu.
if [ "${1:-}" = "infra" ] || [ "${1:-}" = "inf" ]; then
    if [ -z "${2:-}" ]; then
        provisioning help infrastructure
        exit 0
    fi
fi

# validate fast-path removed (ADR-025 Phase 4 — single-route principle).
# Falls through to main dispatch case.

if [ ! -d "$PROVISIONING_USER_CONFIG" ] || [ ! -r "$PROVISIONING_CONTEXT_PATH" ]; then
    [ ! -x "$PROVISIONING/core/nulib/provisioning setup" ] && echo "$PROVISIONING/core/nulib/provisioning setup  not found" && exit 1
    cd "$PROVISIONING/core/nulib"
    ./"provisioning setup"
    echo ""
    read -p "Use [enter] to continue or [ctrl-c] to cancel"
fi
[ ! -r "$PROVISIONING_USER_CONFIG/config.nu" ] && echo "$PROVISIONING_USER_CONFIG/config.nu not found" && exit 1
[ ! -r "$PROVISIONING_USER_CONFIG/env.nu" ] && echo "$PROVISIONING_USER_CONFIG/env.nu not found" && exit 1

NU_ARGS=(--config "$PROVISIONING_USER_CONFIG/config.nu" --env-config "$PROVISIONING_USER_CONFIG/env.nu")
export PROVISIONING_ARGS="$CMD_ARGS" NU_ARGS="$NU_ARGS"
#export NU_ARGS=${NU_ARGS//Application Support/Application\\ Support}

# Suppress repetitive config export output during initialization
export PROVISIONING_QUIET_EXPORT="true"

# Export NU_LIB_DIRS so Nushell can find modules during parsing
export NU_LIB_DIRS="$PROVISIONING/core/nulib:/opt/provisioning/core/nulib:/usr/local/provisioning/core/nulib"

# Export NICKEL_IMPORT_PATH so all nickel invocations resolve schemas/ and extensions/ without --import-path per call
export NICKEL_IMPORT_PATH="$PROVISIONING"

# ============================================================================
# COMMAND VALIDATION - Fast-fail for invalid commands + daemon check
# ============================================================================
# Read command-registry.txt and validate commands BEFORE invoking Nushell.
# This prevents hanging on invalid commands (like "prvng ps").
#
# Registry format: command|aliases|requires_daemon|requires_services|uses_cache|description
# Validation checks:
#   1. Command exists in registry (command or alias)
#   2. If requires_daemon=true, verify daemon is listening on port
# Fail-fast: Exit immediately with clear error if validation fails
#
_validate_command() {
    local cmd="$1"
    local registry_file="$PROVISIONING/core/nulib/commands-registry.ncl"

    # Skip validation for empty command or help flags
    if [ -z "$cmd" ] || [[ "$cmd" =~ ^(--help|--info|-i|-v|--version|-h|-V)$ ]]; then
        return 0
    fi

    # Check if Nickel registry exists
    if [ ! -f "$registry_file" ]; then
        echo "ERROR: commands-registry.ncl not found at $registry_file" >&2
        return 1
    fi

    # Cache: ~/.cache/provisioning/commands-registry.json
    # Rebuilt via nickel export only when registry source changes (mtime check).
    # Validated in pure bash using grep — no Nu process launched for validation.
    local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/provisioning"
    local cache_file="$cache_dir/commands-registry.json"

    # Rebuild cache if stale or missing
    if [ ! -f "$cache_file" ] || [ "$registry_file" -nt "$cache_file" ]; then
        mkdir -p "$cache_dir"
        nickel export --format json --import-path "$PROVISIONING" "$registry_file" \
            > "$cache_file" 2>/dev/null || rm -f "$cache_file"
    fi

    local found=false
    local requires_daemon=false

    if [ -f "$cache_file" ]; then
        # Pure bash grep: find the entry whose "command" or "aliases" contains $cmd.
        # Extract all command names and alias values as a line-per-name list, then check.
        local all_names
        all_names=$(grep -o '"[a-zA-Z0-9_\-\+\.]*"' "$cache_file" | tr -d '"')

        if echo "$all_names" | grep -qx "$cmd"; then
            found=true
            # Check requires_daemon for this specific command block.
            # Strategy: find the block containing our cmd, check its requires_daemon value.
            # Simple grep: look for "requires_daemon": true in the same JSON object as $cmd.
            # We extract the 30-line window around the match and check for requires_daemon true.
            local window
            window=$(grep -n "\"$cmd\"" "$cache_file" | head -1 | cut -d: -f1)
            if [ -n "$window" ]; then
                local block
                block=$(sed -n "$((window > 10 ? window - 10 : 1)),$((window + 15))p" "$cache_file")
                if echo "$block" | grep -q '"requires_daemon": *true'; then
                    requires_daemon=true
                fi
            fi
        else
            found=false
        fi
    else
        # No cache and nickel failed — fall back to Nu script (slow, one-time)
        local validate_script="$PROVISIONING/core/nulib/scripts/validate-command.nu"
        local query_result
        query_result=$($NU -n "$validate_script" "$cmd" 2>&1)
        if [[ "$query_result" == "NOT_FOUND" ]]; then
            found=false
        elif [[ "$query_result" =~ ^FOUND\|(true|false)$ ]]; then
            found=true
            requires_daemon="${BASH_REMATCH[1]}"
        fi
    fi

    # ERROR 1: Command not found in registry
    if [ "$found" = "false" ]; then
        echo "" >&2
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
        echo "❌ Unknown command: $cmd" >&2
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
        echo "" >&2
        echo "This command is not recognized by the provisioning system." >&2
        echo "" >&2
        echo "To see available commands:" >&2
        echo "  provisioning help" >&2
        echo "  prvng help               # short alias" >&2
        echo "" >&2
        echo "Common commands:" >&2
        echo "  provisioning help        - Show help" >&2
        echo "  provisioning platform    - Manage platform services" >&2
        echo "  provisioning workspace   - Workspace management" >&2
        echo "  provisioning create      - Create resources" >&2
        echo "" >&2
        exit 1
    fi

    # ERROR 2: Command requires daemon but daemon is not available
    if [ "$requires_daemon" = "true" ]; then
        # Check if daemon is listening on port (using lsof)
        if ! lsof -i :"$DAEMON_PORT" -P -n 2>/dev/null | grep -q LISTEN; then
            echo "" >&2
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
            echo "❌ CRITICAL: provisioning_daemon not available" >&2
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
            echo "" >&2
            echo "The provisioning daemon is required for operation: $cmd" >&2
            echo "Daemon is not listening on port $DAEMON_PORT" >&2
            echo "" >&2
            echo "The daemon is a CRITICAL component - all operations require it." >&2
            echo "" >&2
            echo "To check daemon status:" >&2
            echo "  provisioning platform status" >&2
            echo "  prvng plat st               # short alias" >&2
            echo "" >&2
            echo "To start the daemon:" >&2
            echo "  provisioning platform start provisioning_daemon" >&2
            echo "  prvng plat start provisioning_daemon      # short alias" >&2
            echo "" >&2
            echo "Allowed operations without daemon:" >&2
            echo "  • help / -h / --help        - View help" >&2
            echo "  • platform <cmd>            - Manage platform services" >&2
            echo "  • setup                     - Initial setup" >&2
            echo "" >&2
            exit 1
        fi
    fi

    return 0
}

# ============================================================================
# DAEMON ROUTING - ENABLED (Phase 3.7: CLI Daemon Integration)
# ============================================================================
# Redesigned daemon with pre-loaded Nushell environment (no CLI callback).
# Routes eligible commands to HTTP daemon for <100ms execution.
# Gracefully falls back to full load if daemon unavailable.
#
# ARCHITECTURE:
#   1. Check daemon health (curl with 5ms timeout)
#   2. Route eligible commands to daemon via HTTP POST
#   3. Fall back to full load if daemon unavailable
#   4. Zero breaking changes (graceful degradation)
#
# PERFORMANCE:
#   - With daemon: <100ms for ALL commands
#   - Without daemon: ~430ms (normal behavior)
#   - Daemon fallback: Automatic, user sees no difference

if [ -n "$PROVISIONING_MODULE" ]; then
    # -mod <module> mode: provisioning-cli.nu reads PROVISIONING_MODULE from env
    # and dispatches to the module's main function directly.
    $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS </dev/null
else
    # Only redirect stdin for non-interactive commands (nu command needs interactive stdin)
    if [ "${1:-}" = "nu" ]; then
        # For interactive mode, start nu with provisioning environment
        export PROVISIONING_CONFIG="$PROVISIONING_USER_CONFIG"
        # Start nu interactively - it will use the config and env from NU_ARGS
        $NU "${NU_ARGS[@]}"
    else
        FIRST_ARG="${1:-}"

        # CRITICAL: Handle help/version FIRST (avoid Nushell module loading hang)
        case "$FIRST_ARG" in
        help | h | --help | -h)
            _show_help "${2:-}"
            exit 0
            ;;
        version | v | --version | -v | -V)
            echo "$PROVISIONING_VERS"
            exit 0
            ;;
        about | --info | -i)
            echo "Provisioning System v$PROVISIONING_VERS"
            exit 0
            ;;
        esac

        # Expand single-char and short top-level aliases before validation.
        # These map directly to canonical command names so the dispatcher and
        # _validate_command see the canonical form.
        case "$FIRST_ARG" in
        s)    FIRST_ARG="server"      ;;
        t)    FIRST_ARG="taskserv"    ;;
        c)    FIRST_ARG="component"   ;;
        e)    FIRST_ARG="extension"   ;;
        w)    FIRST_ARG="workflow"    ;;
        j)    FIRST_ARG="job"         ;;
        b)    FIRST_ARG="batch"       ;;
        o)    FIRST_ARG="orchestrator" ;;
        a|al) FIRST_ARG="alias"       ;;
        esac

        # Validate command to prevent hanging on invalid commands
        # Uses commands-registry.json cache (pure bash grep, no Nu process).
        # This will exit immediately with clear error if:
        #   1. Command not found in registry
        #   2. Command requires daemon but daemon is not available
        _validate_command "$FIRST_ARG"

        # Don't redirect stdin for infrastructure commands - they may need interactive input
        # Only redirect for commands we know are safe
        case "$FIRST_ARG" in
        status | health | diagnostics)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-status.nu" $CMD_ARGS </dev/null
            ;;
        workspace | ws)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/main_provisioning/workspace.nu" $CMD_ARGS </dev/null
            ;;
        env | allenv | list | ls | l | provider | providers | validate | plugin | plugins | nuinfo)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS </dev/null
            ;;
        platform | plat | p)
            # logs needs interactive stdin for typedialog — keep stdin open.
            # All other platform subcommands use the thin entry (~50ms vs ~3s).
            case "${2:-}" in
            logs | log)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS
                ;;
            *)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-platform.nu" $CMD_ARGS </dev/null
                ;;
            esac
            ;;
        batch | bat)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-batch.nu" $CMD_ARGS </dev/null
            ;;
        bootstrap)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-bootstrap.nu" $CMD_ARGS </dev/null
            ;;
        taskserv | task)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-taskserv.nu" $CMD_ARGS </dev/null
            ;;
        component | comp)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-component.nu" $CMD_ARGS </dev/null
            ;;
        extension | ext)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-extension.nu" $CMD_ARGS </dev/null
            ;;
        job)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-job.nu" $CMD_ARGS </dev/null
            ;;
        workflow | wflow)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-workflow.nu" $CMD_ARGS </dev/null
            ;;
        alias)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS </dev/null
            ;;
        create | new)
            # "prvng create server ..." → "prvng server create ..."
            shift
            _resource="${1:-}"
            [ -n "$_resource" ] && shift
            case "$_resource" in
            server|s)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server.nu" server create "$@"
                exit $? ;;
            taskserv|task|t)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-taskserv.nu" taskserv create "$@" </dev/null
                exit $? ;;
            cluster|cl)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cluster.nu" cluster create "$@" </dev/null
                exit $? ;;
            *)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" create "$_resource" "$@"
                exit $? ;;
            esac
            ;;
        server | s)
            # Route list/sync to the lightweight handler (loads only list.nu, ~255ms).
            # All other subcommands go to the full handler (~1.15s).
            _srv_sub="${2:-}"
            case "$_srv_sub" in
            list|ls|l|sync)
                $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server-list.nu" $CMD_ARGS </dev/null
                exit $? ;;
            esac
            # Intercept subcommand --help before Nu absorbs it at the top-level main
            _has_help=false
            for _a in "$@"; do [ "$_a" = "--help" ] || [ "$_a" = "-h" ] && _has_help=true && break; done
            if [ "$_has_help" = "true" ]; then
                case "$_srv_sub" in
                delete|d|del)
                    $NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/delete.nu' *; main delete --help"
                    exit $? ;;
                create|c)
                    $NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/create.nu' *; main create --help"
                    exit $? ;;
                list|l)
                    $NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/list.nu' *; main list --help"
                    exit $? ;;
                ssh)
                    $NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/ssh.nu' *; main ssh --help"
                    exit $? ;;
                esac
            fi
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server.nu" $CMD_ARGS
            ;;
        ssh)
            # Shortcut: provisioning ssh <hostname> → provisioning server ssh <hostname> --run
            shift
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server.nu" server ssh "$@" --run
            ;;
        state | st)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-state.nu" $CMD_ARGS </dev/null
            ;;
        cluster | cl)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cluster.nu" $CMD_ARGS </dev/null
            ;;
        volume | vol)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-volume.nu" "${@:2}" </dev/null
            ;;
        fip | floating-ip)
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/main_provisioning/fip.nu" "${@:2}"
            ;;
        *)
            # All other commands — provisioning-cli.nu is the single fallback entry.
            # stdin kept open for interactive commands (delete, update, etc.).
            $NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS
            ;;
        esac
    fi
fi
