#!/usr/bin/env bash
#
# jerboa — CLI entry point for the Jerboa Scheme environment
#

set -euo pipefail

# Auto-detect JERBOA_HOME as the parent of this script's directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JERBOA_HOME="${JERBOA_HOME:-$(cd "$SCRIPT_DIR/.." && pwd)}"

SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}"
LIBDIRS="$JERBOA_HOME/lib"
VERSION="$(tr -d '[:space:]' < "$JERBOA_HOME/VERSION" 2>/dev/null || true)"
VERSION="${VERSION:-0.2.0}"

if [ ! -x "$SCHEME" ]; then
    echo "Error: Chez Scheme not found at $SCHEME" >&2
    echo "Run 'make chez' from $JERBOA_HOME to build the repo-local Scheme." >&2
    exit 1
fi

# Colors for test output (disabled if not a terminal)
if [ -t 1 ]; then
    GREEN='\033[0;32m'
    RED='\033[0;31m'
    BOLD='\033[1m'
    RESET='\033[0m'
else
    GREEN='' RED='' BOLD='' RESET=''
fi

usage() {
    cat <<EOF
Usage: jerboa [command] [args...]

Commands:
  (no args)          Launch the Jerboa REPL
  repl               Launch the Jerboa REPL
  run <file> [args]  Run a Scheme script
  exec <file>        Run a self-contained script with a requires: header
                     (auto-installs declared dependencies before running)
  eval '<expr>'      Evaluate a single expression
  test [dir]         Discover and run test files (default: tests/)
  build              Run make build in the project directory
  install <gh-url>   Install a package (e.g. github.com/user/repo)
  uninstall <name>   Uninstall a package by name
  update [name]      Update one or all installed packages
  list               List installed packages
  pkg <cmd> [args]   jpkg package manager (see: jerboa pkg --help)
  version            Print version info
  help               Print this help message

Environment:
  JERBOA_HOME        Project root (auto-detected from script location)
  SCHEME             Chez Scheme binary (default: scheme)
EOF
}

cmd_repl() {
    exec "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<'REPL'
(import (jerboa prelude) (std repl))
(jerboa-repl)
REPL
    )
}

cmd_run() {
    if [ $# -eq 0 ]; then
        echo "Error: jerboa run requires a file argument" >&2
        echo "Usage: jerboa run <file> [args...]" >&2
        exit 1
    fi
    local file="$1"
    shift
    if [ ! -f "$file" ]; then
        echo "Error: file not found: $file" >&2
        exit 1
    fi
    exec "$SCHEME" --libdirs "$LIBDIRS" --script "$file" "$@"
}

##
## parse_requires_header <file>
##
## Extract dependency URLs from a ";;; jerboa-package" header block.
## The header lives at the top of the file and looks like:
##
##     ;;; jerboa-package
##     ;;; name:    my-app
##     ;;; requires:
##     ;;;   github.com/alice/jerboa-foo
##     ;;;   github.com/bob/jerboa-bar
##     ;;;
##
## The block ends at the first line that is not a ";;;" comment, or at a
## bare ";;;" (blank comment) after the requires: key. Unknown keys are
## ignored. Any line that isn't "requires:" or an entry under it is
## skipped.
##
## Prints one URL per line. Prints nothing if no header is present.
parse_requires_header() {
    awk '
        BEGIN { in_block = 0; in_requires = 0 }
        # A line like ";;;" or ";;; " ends the requires sub-block.
        /^;;;[[:space:]]*$/ {
            if (in_block) { in_requires = 0; next }
            exit
        }
        # Non-comment line: header is over.
        !/^;;;/ {
            if (in_block) exit
            next
        }
        # Header marker
        /^;;;[[:space:]]*jerboa-package[[:space:]]*$/ { in_block = 1; next }
        # Only process comments inside the block
        in_block == 0 { next }
        # Key: value line
        /^;;;[[:space:]]*[A-Za-z_-]+:[[:space:]]*/ {
            # "requires:" with no value starts a sub-block
            if ($0 ~ /^;;;[[:space:]]*requires:[[:space:]]*$/) {
                in_requires = 1
                next
            }
            # Any other key: line exits the requires sub-block
            in_requires = 0
            next
        }
        # Entry line under requires:
        in_requires == 1 {
            line = $0
            sub(/^;;;[[:space:]]*/, "", line)
            sub(/[[:space:]]+$/, "", line)
            if (line != "") print line
        }
    ' "$1"
}

cmd_exec() {
    if [ $# -eq 0 ]; then
        echo "Error: jerboa exec requires a file argument" >&2
        echo "Usage: jerboa exec <file>" >&2
        exit 1
    fi
    local file="$1"
    shift
    if [ ! -f "$file" ]; then
        echo "Error: file not found: $file" >&2
        exit 1
    fi

    local -a deps=()
    while IFS= read -r dep; do
        [ -n "$dep" ] && deps+=("$dep")
    done < <(parse_requires_header "$file")

    if [ ${#deps[@]} -gt 0 ]; then
        local -a missing=()
        for dep in "${deps[@]}"; do
            local name
            name="$(basename "$dep")"
            if ! "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<CHECK
(import (jerboa registry))
(exit (if (package-installed? "$name") 0 1))
CHECK
            ) >/dev/null 2>&1; then
                missing+=("$dep")
            fi
        done

        if [ ${#missing[@]} -gt 0 ]; then
            echo "jerboa exec: $file declares ${#missing[@]} missing package(s):"
            for dep in "${missing[@]}"; do
                echo "  - $dep"
            done
            if [ "${JERBOA_EXEC_YES:-}" != "1" ]; then
                printf "Install now? [y/N] "
                read -r reply
                case "$reply" in
                    y|Y|yes|YES) ;;
                    *) echo "Aborted." >&2; exit 1 ;;
                esac
            fi
            for dep in "${missing[@]}"; do
                "$0" install "$dep" || {
                    echo "Failed to install $dep" >&2
                    exit 1
                }
            done
        fi
    fi

    exec "$SCHEME" --libdirs "$LIBDIRS" --script "$file" "$@"
}

cmd_eval() {
    if [ $# -eq 0 ]; then
        echo "Error: jerboa eval requires an expression" >&2
        echo "Usage: jerboa eval '<expr>'" >&2
        exit 1
    fi
    local expr="$1"
    "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<EVAL
(import (jerboa prelude))
$expr
EVAL
    )
}

cmd_test() {
    local test_dir="${1:-$JERBOA_HOME/tests}"
    if [ ! -d "$test_dir" ]; then
        echo "Error: test directory not found: $test_dir" >&2
        exit 1
    fi

    local files=()
    while IFS= read -r -d '' f; do
        files+=("$f")
    done < <(find "$test_dir" -name 'test-*.ss' -type f -print0 | sort -z)

    if [ ${#files[@]} -eq 0 ]; then
        echo "No test files (test-*.ss) found in $test_dir"
        exit 0
    fi

    local passed=0 failed=0 total=${#files[@]}
    echo -e "${BOLD}Running $total test file(s) from $test_dir${RESET}"
    echo ""

    for f in "${files[@]}"; do
        local name
        name="$(basename "$f")"
        if "$SCHEME" --libdirs "$LIBDIRS" --script "$f" 2>&1; then
            echo -e "  ${GREEN}PASS${RESET}  $name"
            ((passed++))
        else
            echo -e "  ${RED}FAIL${RESET}  $name"
            ((failed++))
        fi
    done

    echo ""
    echo -e "${BOLD}Results: $passed passed, $failed failed, $total total${RESET}"
    [ "$failed" -eq 0 ]
}

cmd_build() {
    exec make -C "$JERBOA_HOME" build
}

cmd_install() {
    if [ $# -eq 0 ]; then
        echo "Error: jerboa install requires a GitHub path" >&2
        echo "Usage: jerboa install github.com/user/repo" >&2
        exit 1
    fi
    local gh_path="$1"
    "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<INSTALL
(import (jerboa registry))
(let ([entry (package-install! "$gh_path")])
  (display "Installed: ")
  (display "$gh_path")
  (newline))
INSTALL
    )
}

cmd_uninstall() {
    if [ $# -eq 0 ]; then
        echo "Error: jerboa uninstall requires a package name" >&2
        echo "Usage: jerboa uninstall <package-name>" >&2
        exit 1
    fi
    local name="$1"
    "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<UNINSTALL
(import (jerboa registry))
(package-uninstall! "$name")
(display "Uninstalled: ")
(display "$name")
(newline)
UNINSTALL
    )
}

cmd_update() {
    if [ $# -eq 0 ]; then
        # Update all packages
        "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<'UPDATEALL'
(import (except (chezscheme) make-hash-table hash-table? iota 1+ 1-)
        (jerboa runtime)
        (jerboa registry))
(let ([pkgs (installed-packages)])
  (if (null? pkgs)
    (display "No packages installed.\n")
    (for-each
      (lambda (e)
        (let ([name (hash-ref e "name" "")])
          (display (string-append "Updating " name "...\n"))
          (package-update! name)))
      pkgs)))
UPDATEALL
        )
    else
        local name="$1"
        "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<UPDATE
(import (jerboa registry))
(package-update! "$name")
(display "Updated: ")
(display "$name")
(newline)
UPDATE
        )
    fi
}

cmd_list() {
    "$SCHEME" --libdirs "$LIBDIRS" --program <(cat <<'LIST'
(import (except (chezscheme) make-hash-table hash-table? iota 1+ 1-)
        (jerboa runtime)
        (jerboa registry))
(let ([pkgs (installed-packages)])
  (if (null? pkgs)
    (display "No packages installed.\n")
    (for-each
      (lambda (e)
        (let ([name (hash-ref e "name" "")]
              [ver  (hash-ref e "version" "?")]
              [url  (hash-ref e "url" "")])
          (display (string-append "  " name " (" ver ") — " url "\n"))))
      pkgs)))
LIST
    )
}

cmd_version() {
    echo "jerboa $VERSION"
    echo "home: $JERBOA_HOME"
    "$SCHEME" --version 2>&1 || true
}

# --- Main dispatch ---

case "${1:-}" in
    ""|repl)
        cmd_repl
        ;;
    run)
        shift
        cmd_run "$@"
        ;;
    exec)
        shift
        cmd_exec "$@"
        ;;
    eval)
        shift
        cmd_eval "$@"
        ;;
    test)
        shift
        cmd_test "$@"
        ;;
    build)
        cmd_build
        ;;
    install)
        shift
        cmd_install "$@"
        ;;
    uninstall)
        shift
        cmd_uninstall "$@"
        ;;
    update)
        shift
        cmd_update "$@"
        ;;
    list)
        cmd_list
        ;;
    pkg|jpkg)
        shift
        exec "$SCHEME" --libdirs "$LIBDIRS" --script "$JERBOA_HOME/tools/jpkg-main.ss" "$@"
        ;;
    version|--version|-v)
        cmd_version
        ;;
    help|--help|-h)
        usage
        ;;
    *)
        echo "Error: unknown command '$1'" >&2
        echo "" >&2
        usage >&2
        exit 1
        ;;
esac
