#!/usr/bin/env bash
# Build a MacOS virtual machine that runs AI agents and developer tools
set -Eeuo pipefail
trap 'echo >&2 "${BASH_SOURCE[0]}: line $LINENO: $BASH_COMMAND: exitcode $?"' ERR

# Resolve symlinks (macOS bash 3.2 compatible, no readlink -f)
SOURCE="${BASH_SOURCE[0]}"
while [[ -L "$SOURCE" ]]; do
    SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd -P)"
    SOURCE="$(readlink "$SOURCE")"
    [[ "$SOURCE" = /* ]] || SOURCE="$SOURCE_DIR/$SOURCE"
done
WORKSPACE="$(cd -P "$(dirname "$SOURCE")" && pwd -P)"

# Homebrew Cellar: use stable opt/ symlink instead of versioned path
if [[ "$WORKSPACE" =~ ^(.*)/homebrew/Cellar/([^/]+)/[^/]+$ ]]; then
    WORKSPACE="${BASH_REMATCH[1]}/homebrew/opt/${BASH_REMATCH[2]}"
fi


###############################################################################
# Overrides
###############################################################################
MACOS_VERSION="${MACOS_VERSION:-tahoe}"
MACOS_FLAVOR="${MACOS_FLAVOR:-xcode}"


###############################################################################
# Libraries
###############################################################################
source "$WORKSPACE/lib/common.sh"
source "$WORKSPACE/lib/config.sh"
source "$WORKSPACE/lib/db.sh"
source "$WORKSPACE/lib/vm.sh"
source "$WORKSPACE/lib/ssh.sh"
source "$WORKSPACE/lib/project.sh"
source "$WORKSPACE/lib/instance.sh"
source "$WORKSPACE/lib/commands.sh"
source "$WORKSPACE/lib/build.sh"

validate_platform
validate_config
init_config
install_tools
init_db
migrate_vm_names


# Pre-parse global options so they take effect even on early-dispatched subcommands.
# Unknown flags and positional args are preserved in $@ for subcommand parsers.
NO_GRAPHICS="${NO_GRAPHICS:-}"
SHOULD_SELECT_PROJECT=true
ALLOW_SUDO=
FORWARD_PORTS=()
_pre_args=()
while [[ $# -gt 0 ]]; do
    case "$1" in
        -v|--verbose)
            ((VERBOSE++)) || true
            shift
            ;;
        -vv)
            ((VERBOSE+=2)) || true
            shift
            ;;
        -vvv)
            ((VERBOSE+=3)) || true
            shift
            ;;
        --graphics)
            NO_GRAPHICS=
            shift
            ;;
        --no-graphics)
            NO_GRAPHICS=true
            shift
            ;;
        --rebuild-base)
            REBUILD_BASE=true
            shift
            ;;
        --rebuild-oci)
            REBUILD_OCI=true
            shift
            ;;
        --allow-sudo)
            ALLOW_SUDO=true
            shift
            ;;
        --no-allow-sudo)
            ALLOW_SUDO=false
            shift
            ;;
        -n|--no-select)
            SHOULD_SELECT_PROJECT=false
            shift
            ;;
        --forward-port)
            [[ $# -ge 2 ]] || abort "Error: --forward-port requires a port number"
            if ! [[ "$2" =~ ^[0-9]+$ ]] || (( $2 < 1 || $2 > 65535 )); then
                abort "Error: --forward-port requires a port in 1-65535 (got '$2')"
            fi
            FORWARD_PORTS+=("$2")
            shift 2
            ;;
        --)
            _pre_args+=("$@")
            break
            ;;
        *)
            _pre_args+=("$1")
            shift
            ;;
    esac
done
set -- ${_pre_args[@]+"${_pre_args[@]}"}

# Intercept commands that need their own option parsing before the global loop.
# But let -h/--help/--version pass through to the normal handler.
for arg in "$@"; do
    case "$arg" in
        -h|--help|--version|-V)
            break  # let global parser handle these
            ;;
        -*)
            continue
            ;;
        build-base)
            while [[ "${1:-}" != "$arg" ]]; do shift; done
            shift
            for _a in "$@"; do
                case "$_a" in
                    --)
                        break
                        ;;
                    -h|--help)
                        show_help_build_base
                        exit 0
                        ;;
                    *)
                        ;;
                esac
            done
            cmd_build_base "$@"
            exit 0
            ;;
        clone-base)
            while [[ "${1:-}" != "$arg" ]]; do shift; done
            shift
            cmd_clone_base "$@"
            exit 0
            ;;
        create|cr)
            while [[ "${1:-}" != "$arg" ]]; do shift; done
            shift
            for _a in "$@"; do
                case "$_a" in
                    --)
                        break
                        ;;
                    -h|--help)
                        show_help_create
                        exit 0
                        ;;
                    *)
                        ;;
                esac
            done
            vm_create "$@"
            exit 0
            ;;
        destroy)
            while [[ "${1:-}" != "$arg" ]]; do shift; done
            shift
            for _a in "$@"; do
                case "$_a" in
                    --)
                        break
                        ;;
                    -h|--help)
                        show_help_destroy
                        exit 0
                        ;;
                    *)
                        ;;
                esac
            done
            vm_destroy "$@"
            exit 0
            ;;
        set)
            while [[ "${1:-}" != "$arg" ]]; do shift; done
            shift
            for _a in "$@"; do
                case "$_a" in
                    --)
                        break
                        ;;
                    -h|--help)
                        show_help_set
                        exit 0
                        ;;
                    *)
                        ;;
                esac
            done
            vm_set "$@"
            exit 0
            ;;
        shell|sh|s)
            while [[ "${1:-}" != "$arg" ]]; do shift; done
            shift
            # Check --help before shell's own parser (stop at --)
            for _a in "$@"; do
                case "$_a" in
                    --)
                        break
                        ;;
                    -h|--help)
                        show_help_shell
                        exit 0
                        ;;
                    *)
                        ;;
                esac
            done
            # Parse shell args: [--ram SIZE] [target] [-- cmd...]
            COMMAND_ARGS=()
            _shell_ram=""
            _shell_target=""
            while [[ $# -gt 0 ]]; do
                case "$1" in
                    --ram)
                        [[ $# -ge 2 ]] || abort "Error: --ram requires a size value (e.g. 8G)"
                        _shell_ram="$(parse_ram_size "$2")"
                        shift 2
                        ;;
                    --)
                        shift
                        while [[ $# -gt 0 ]]; do
                            COMMAND_ARGS+=("$1")
                            shift
                        done
                        break
                        ;;
                    *)
                        if [[ -z "$_shell_target" ]]; then
                            _shell_target="$1"
                        fi
                        shift
                        ;;
                esac
            done
            # Named target that matches an existing instance → connect to it.
            # Anything else (path, project name, or no target) falls through
            # to the unified bare flow, which targets xcode and lets project
            # resolution handle paths/names.
            if [[ -n "$_shell_target" ]] && vm_instance_exists "$_shell_target"; then
                SHELL_RAM_OVERRIDE="$_shell_ram" vm_shell "$_shell_target"
                exit 0
            fi
            # Carry --ram into the bare flow via the env var vm_shell reads.
            [[ -z "$_shell_ram" ]] || export SHELL_RAM_OVERRIDE="$_shell_ram"
            # Reinject as `shell [target]` for the main parser
            set -- shell ${_shell_target:+"$_shell_target"}
            break
            ;;
        *)
            break
            ;;
    esac
done

# Parse remaining arguments (global options already consumed by pre-parse above)
NEW_ARGS=()
COMMAND_ARGS=()
while [[ $# -gt 0 ]]; do
    case "$1" in
        --)
            shift
            while [[ $# -gt 0 ]]; do
                COMMAND_ARGS+=("$1")
                shift
            done
            break
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        --version)
            show_version
            ;;
        -*)
            echo "Unknown option: $1" >&2
            exit 1
            ;;
        *)
            NEW_ARGS+=("$1")
            shift
            ;;
    esac
done

# bash 3.2 treats empty arrays as unbound even when initialized.
# With set -u enabled, "${NEW_ARGS[@]}" fails if the array is empty.
# The fix is to use ${array[@]+"${array[@]}"} which only expands if the array has elements.
set -- ${NEW_ARGS[@]+"${NEW_ARGS[@]}"}

# Parse fixed arguments
case "${1:-}" in
    claude|cl|c)
        COMMAND=claude
        PROJECT_DIR="${2:-}"
        PROJECT_NAME="${3:-}"
        ;;
    codex|co)
        COMMAND=codex
        PROJECT_DIR="${2:-}"
        PROJECT_NAME="${3:-}"
        ;;
    cursor|cu|ca)
        COMMAND=cursor
        PROJECT_DIR="${2:-}"
        PROJECT_NAME="${3:-}"
        ;;
    gemini|gem|g)
        COMMAND=gemini
        PROJECT_DIR="${2:-}"
        PROJECT_NAME="${3:-}"
        ;;
    shell|sh|s)
        unset COMMAND
        IS_SHELL_COMMAND=true
        PROJECT_DIR="${2:-}"
        PROJECT_NAME="${3:-}"
        ;;
    start)
        # Named target → start it. No name → start xcode (build if needed).
        if [[ -n "${2:-}" ]]; then
            vm_start_dispatch "${2:-}"
            exit 0
        fi
        COMMAND=start
        ;;
    stop)
        if [[ -n "${2:-}" ]]; then
            vm_stop_instance "$2"
        else
            stop_all_vms
        fi
        exit 0
        ;;
    add|a)
        add_project "${2:-}" "${3:-}"
        exit 0
        ;;
    remove|rm)
        remove_project "${2:-}"
        exit 0
        ;;
    list|ls|l)
        list_all
        exit 0
        ;;
    migrate)
        migrate_db
        exit 0
        ;;
    help|h)
        dispatch_help "${2:-}"
        exit 0
        ;;
    status|st)
        COMMAND=status
        ;;
    *)
        # Bare `clod` with no command: show help unless a rebuild flag was
        # passed, in which case treat it as `clod start`.
        if [[ -z "${REBUILD_BASE:-}" ]] && [[ -z "${REBUILD_OCI:-}" ]]; then
            show_help
            exit 0
        fi
        COMMAND=start
        ;;
esac

# Named instance shell dispatch is handled in early-dispatch above.
# Legacy shell (path/project) falls through to here.

# Decide whether sudo-setting changes require rebuild
STORED_ALLOW_SUDO="$(get_setting "allow_sudo" "false")"
if [[ "${COMMAND:-}" == "status" ]]; then
    check_oci_provenance true
    show_status "$STORED_ALLOW_SUDO"
    exit 0
fi

ALLOW_SUDO="${ALLOW_SUDO:-"$STORED_ALLOW_SUDO"}"
if [[ "$ALLOW_SUDO" != "$STORED_ALLOW_SUDO" ]]; then
    info "Sudo setting changed ($STORED_ALLOW_SUDO -> $ALLOW_SUDO); forcing base rebuild"
    REBUILD_BASE=true
fi

# Resolve explicit command target argument
if [[ -n "${PROJECT_DIR:-}" ]]; then
    if [[ "${IS_SHELL_COMMAND:-}" == "true" ]] && project_path="$(get_project_path_by_name "$PROJECT_DIR")" && [[ -n "$project_path" ]]; then
        # NAME-only form
        PROJECT_NAME="$PROJECT_DIR"
        PROJECT_DIR="$project_path"
        SHOULD_SELECT_PROJECT=false
        set_active_project "$PROJECT_NAME" "$PROJECT_DIR"
    else
        resolved_path="$(resolve_physical_path "$PROJECT_DIR" 2>/dev/null || true)"
        if [[ -d "$resolved_path" ]]; then
            # PATH [NAME] form
            add_project "$PROJECT_DIR" "$PROJECT_NAME"
            SHOULD_SELECT_PROJECT=false
        elif project_path="$(get_project_path_by_name "$PROJECT_DIR")" && [[ -n "$project_path" ]]; then
            # NAME-only form
            PROJECT_NAME="$PROJECT_DIR"
            PROJECT_DIR="$project_path"
            SHOULD_SELECT_PROJECT=false
            set_active_project "$PROJECT_NAME" "$PROJECT_DIR"
        else
            if [[ "${IS_SHELL_COMMAND:-}" == "true" ]]; then
                abort "Error: project path or instance not found ($PROJECT_DIR)"
            fi
            abort "Error: Project not found ($PROJECT_DIR)"
        fi
    fi
fi

# Get most recent project. If none exists then add the current directory as a project.
IFS='|' read -r PROJECT_NAME PROJECT_DIR < <(sqlite3 "$DB_FILE" "SELECT name, path FROM
    projects ORDER BY date_added DESC LIMIT 1;" 2>/dev/null) || true
if [[ -z "${PROJECT_DIR:-}" ]]; then
    # Try to set the project directory to the top-level directory of the git repository
    # to minimize the number of directories that get mapped into the virtual machine.
    PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null)" || PROJECT_DIR="$PWD"
    PROJECT_DIR="$(resolve_physical_path "$PROJECT_DIR" 2>/dev/null || echo "$PROJECT_DIR")"
    PROJECT_NAME="$(basename "$PROJECT_DIR")"
    add_project "$PROJECT_DIR" "$PROJECT_NAME"
    SHOULD_SELECT_PROJECT=false
fi

if [[ "$SHOULD_SELECT_PROJECT" == "true" ]]; then
    if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
        warn "Interactive project selection disabled (no TTY); using active project"
    elif selected_project=$(select_project); then
        IFS='|' read -r PROJECT_NAME PROJECT_DIR <<< "$selected_project"
        set_active_project "$PROJECT_NAME" "$PROJECT_DIR"
    else
        selection_rc=$?
        if [[ "$selection_rc" -eq 1 ]]; then
            info "Cancelled"
            exit 1
        else
            warn "Interactive project selection unavailable; using active project"
        fi
    fi
fi

info "Active project: $PROJECT_NAME ($PROJECT_DIR)"
check_oci_provenance

ensure_ssh_key
ensure_guest_home
configure_dhcp_lease

# --rebuild-base implies the xcode VM must also be rebuilt (it was cloned from
# the old base). Destroy it so vm_ensure_xcode_instance rebuilds from the new
# base after build_base_vm runs.
if [[ -n "${REBUILD_BASE:-}" ]] && vm_instance_exists "xcode"; then
    if [[ "$(get_vm_state "$(vm_get_instance_vm_name "xcode")")" == "running" ]]; then
        read -p "xcode is running; restart-rebuild it? (y/N)" -n 1 -r response
        echo
        [[ "$response" =~ ^[Yy]$ ]] || exit 0
    fi
    vm_destroy_instance "xcode"
fi

# Rebuild OCI/base if requested (vm_ensure_xcode_instance handles bare first-run).
if [[ -n "${REBUILD_OCI:-}" ]] || [[ -n "${REBUILD_BASE:-}" ]]; then
    REBUILD_BASE=true
    prepare_rebuilds
    build_base_vm
fi

vm_ensure_xcode_instance

if [[ "${COMMAND:-}" == "start" ]]; then
    vm_start_instance "xcode"
    info "clodpod VM running"
    exit 0
fi

vm_shell "xcode"
