#!/usr/bin/env bash
# cmd — script launcher for cmd.mle.sh
#
# Usage:
#   curl -fsSL https://cmd.mle.sh | bash                                 interactive menu
#   curl -fsSL https://cmd.mle.sh | bash -s -- <name> [args...]          run by name
#   curl -fsSL https://cmd.mle.sh | bash -s -- --list                    print index, no menu
#   curl -fsSL https://cmd.mle.sh | bash -s -- --get <name>              download to ./<name>.sh
#   curl -fsSL https://cmd.mle.sh | bash -s -- --help
#
# Source: https://git.mle.sh/${CMD_OWNER:-matthias}/cmd

set -euo pipefail

REPO_HOST="${CMD_HOST:-git.mle.sh}"
REPO_OWNER="${CMD_OWNER:-matthias}"
REPO_NAME="${CMD_REPO:-cmd}"
REPO_BRANCH="${CMD_BRANCH:-main}"
PARALLEL="${CMD_PARALLEL:-8}"

API="https://${REPO_HOST}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}"
RAW="https://${REPO_HOST}/${REPO_OWNER}/${REPO_NAME}/raw/branch/${REPO_BRANCH}"

err()  { printf 'cmd: %s\n' "$*" >&2; }
die()  { err "$*"; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"; }

need curl
need jq

# stdin is the launcher source when invoked as `curl … | bash`,
# so any interactive prompts must read from /dev/tty directly.
if [ -e /dev/tty ]; then
    HAS_TTY=1
else
    HAS_TTY=0
fi

read_tty() {
    [ "$HAS_TTY" -eq 1 ] || die "interactive selection requires a TTY"
    IFS= read -r "$1" </dev/tty
}

fetch_tree() {
    local sha
    sha=$(curl -fsSL "${API}/branches/${REPO_BRANCH}" | jq -r '.commit.id') \
        || die "could not reach ${API}/branches/${REPO_BRANCH}"
    [ -n "$sha" ] && [ "$sha" != "null" ] || die "could not resolve branch ${REPO_BRANCH}"
    curl -fsSL "${API}/git/trees/${sha}?recursive=true" \
        | jq -r '.tree[] | select(.type=="blob") | .path' \
        | grep -E '^scripts/.+\.(sh|py)$' || true
}

# Fetch a single script's header (first 25 lines) and emit one TSV row.
# Exported for xargs -P parallelism.
_fetch_header() {
    local path="$1"
    local hdr name desc tags interactive args
    hdr=$(curl -fsSL "${RAW}/${path}" 2>/dev/null | head -25) || return 0
    name="${path##*/}"; name="${name%.sh}"
    desc=$(grep -m1        '^#[[:space:]]*cmd:desc'        <<<"$hdr" | sed -E 's/^#[[:space:]]*cmd:desc[[:space:]]+//' || true)
    tags=$(grep -m1        '^#[[:space:]]*cmd:tags'        <<<"$hdr" | sed -E 's/^#[[:space:]]*cmd:tags[[:space:]]+//' || true)
    interactive=$(grep -m1 '^#[[:space:]]*cmd:interactive' <<<"$hdr" | sed -E 's/^#[[:space:]]*cmd:interactive[[:space:]]+//' || true)
    args=$(grep -m1        '^#[[:space:]]*cmd:args'        <<<"$hdr" | sed -E 's/^#[[:space:]]*cmd:args[[:space:]]+//' || true)
    printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$name" "$path" "${desc:-}" "${tags:-}" "${interactive:-}" "${args:-}"
}
export -f _fetch_header

build_index() {
    local paths
    paths=$(fetch_tree)
    [ -n "$paths" ] || die "no scripts found in ${REPO_OWNER}/${REPO_NAME}@${REPO_BRANCH}:scripts/"
    export RAW
    printf '%s\n' "$paths" | xargs -I{} -P"$PARALLEL" bash -c '_fetch_header "$@"' _ {} \
        | sort -t$'\t' -k2,2
}

resolve_path() {
    awk -F'\t' -v n="$1" '$1==n {print $2; exit}' "$INDEX"
}

resolve_meta() {
    awk -F'\t' -v n="$1" '$1==n {print $5"|"$6; exit}' "$INDEX"
}

run_script() {
    local name="$1"; shift
    local path tmp shebang ext meta interactive
    path=$(resolve_path "$name")
    [ -n "$path" ] || die "unknown script: $name"

    meta=$(resolve_meta "$name")
    interactive="${meta%%|*}"

    ext="${path##*.}"
    tmp=$(mktemp -t "cmd.${name}.XXXXXX.${ext}")
    # shellcheck disable=SC2064
    trap "rm -f '$tmp'" RETURN
    curl -fsSL "${RAW}/${path}" -o "$tmp"
    chmod +x "$tmp"

    # Trust the shebang. Run the file directly so the kernel dispatches to the
    # right interpreter (bash, python3, ruby, etc.). Fall back to bash only
    # when there is no shebang.
    shebang=$(head -n1 "$tmp")
    local -a runner
    if [[ "$shebang" == "#!"* ]]; then
        runner=( "$tmp" )
    else
        runner=( bash "$tmp" )
    fi

    # Give the script a real stdin: TTY for interactive scripts so prompts work,
    # /dev/null otherwise so accidental reads don't hang on the launcher source.
    if [ "$interactive" = "yes" ] && [ "$HAS_TTY" -eq 1 ]; then
        "${runner[@]}" "$@" </dev/tty
    else
        "${runner[@]}" "$@" </dev/null
    fi
}

download_script() {
    local name="$1" path ext out
    path=$(resolve_path "$name")
    [ -n "$path" ] || die "unknown script: $name"
    ext="${path##*.}"
    out="./${name}.${ext}"
    curl -fsSL "${RAW}/${path}" -o "$out"
    chmod +x "$out"
    echo "saved: $out"
}

print_list() {
    awk -F'\t' '{ display=$2; sub(".*/", "", display); printf "%-26s  %s\n", display, $3 }' "$INDEX"
}

render_menu() {
    : > "$MENU_MAP"
    awk -F'\t' '{ printf "%d\t%s\n", NR, $1 }' "$INDEX" > "$MENU_MAP"

    local C_TITLE="" C_GROUP="" C_NUM="" C_NAME="" C_DESC="" C_RST=""
    if [ -t 2 ]; then
        C_TITLE=$'\033[1m'
        C_GROUP=$'\033[36m'
        C_NUM=$'\033[33m'
        C_NAME=$'\033[1m'
        C_DESC=$'\033[2m'
        C_RST=$'\033[0m'
    fi

    awk -F'\t' \
        -v C_TITLE="$C_TITLE" -v C_GROUP="$C_GROUP" -v C_NUM="$C_NUM" \
        -v C_NAME="$C_NAME" -v C_DESC="$C_DESC" -v C_RST="$C_RST" '
    function group(p,    g) {
        g = p
        sub(/^scripts\//, "", g)
        if (index(g, "/")) {
            sub(/\/[^\/]+$/, "", g)
            return g
        }
        return "."
    }
    function display(p,    d) { d = p; sub(".*/", "", d); return d }
    NR == FNR {
        g = group($2)
        if (!(g in seen)) { seen[g] = 1; order[++ng] = g }
        count[g]++
        d = display($2)
        if (length(d) > maxn) maxn = length(d)
        next
    }
    FNR == 1 {
        printf "\n%scmd%s\n", C_TITLE, C_RST
    }
    {
        g = group($2)
        pos[g]++
        if (g != prev) {
            gn++
            is_last_g = (gn == ng)
            gbranch    = is_last_g ? "└─" : "├─"
            cur_prefix = is_last_g ? "   " : "│  "
            if (g != ".") {
                printf "%s %s%s/%s\n", gbranch, C_GROUP, g, C_RST
            } else {
                cur_prefix = ""
            }
            prev = g
        }
        is_last_s = (pos[g] == count[g])
        sbranch = is_last_s ? "└─" : "├─"
        printf "%s%s %s%2d%s  %s%-*s%s  %s%s%s\n",
               cur_prefix, sbranch,
               C_NUM, FNR, C_RST,
               C_NAME, maxn, display($2), C_RST,
               C_DESC, $3, C_RST
    }
    ' "$INDEX" "$INDEX" >&2
}

resolve_ref() {
    local ref="$1" name
    if [[ "$ref" =~ ^[0-9]+$ ]]; then
        name=$(awk -F'\t' -v n="$ref" '$1==n {print $2; exit}' "$MENU_MAP")
    else
        name=$(awk -F'\t' -v n="$ref" '$1==n {print $1; exit}' "$INDEX")
        if [ -z "$name" ] && [[ "$ref" == *.* ]]; then
            local stripped="${ref%.*}"
            name=$(awk -F'\t' -v n="$stripped" '$1==n {print $1; exit}' "$INDEX")
        fi
    fi
    [ -n "$name" ] || return 1
    printf '%s\n' "$name"
}

interactive_menu() {
    render_menu

    local C_HINT="" C_PROMPT="" C_RST=""
    if [ -t 2 ]; then
        C_HINT=$'\033[2m'
        C_PROMPT=$'\033[1;35m'
        C_RST=$'\033[0m'
    fi
    printf '\n%s  <n|name> [args...]   ·   /dl <n|name>   ·   ^C exit%s\n' \
        "$C_HINT" "$C_RST" >&2

    while :; do
        printf '%s›%s ' "$C_PROMPT" "$C_RST" >&2
        local choice
        read_tty choice

        local -a parts
        read -ra parts <<<"$choice"
        [ ${#parts[@]} -eq 0 ] && continue
        local head="${parts[0]}"

        if [[ "$head" == /dl* ]]; then
            local ref
            if [ "$head" = "/dl" ]; then
                ref="${parts[1]:-}"
            else
                ref="${head#/dl}"
            fi
            if [ -z "$ref" ]; then
                err "usage: /dl <number|name>"
                continue
            fi
            local name
            if ! name=$(resolve_ref "$ref"); then
                err "unknown: $ref"
                continue
            fi
            download_script "$name"
            return 0
        fi

        local name
        if ! name=$(resolve_ref "$head"); then
            err "unknown: $head"
            continue
        fi
        run_script "$name" "${parts[@]:1}"
        return 0
    done
}

usage() {
    sed -n '2,11p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

INDEX=$(mktemp -t cmd.index.XXXXXX)
MENU_MAP=$(mktemp -t cmd.menu.XXXXXX)
trap 'rm -f "$INDEX" "$MENU_MAP"' EXIT

case "${1:-}" in
    -h|--help)
        usage
        ;;
    --list)
        build_index > "$INDEX"
        print_list
        ;;
    --get)
        shift
        [ $# -gt 0 ] || die "usage: --get <name>"
        build_index > "$INDEX"
        download_script "$1"
        ;;
    "")
        printf 'cmd.mle.sh — fetching index from %s/%s@%s ...\n' \
            "$REPO_OWNER" "$REPO_NAME" "$REPO_BRANCH" >&2
        build_index > "$INDEX"
        interactive_menu
        ;;
    -*)
        die "unknown option: $1 (try --help)"
        ;;
    *)
        name="$1"; shift
        build_index > "$INDEX"
        run_script "$name" "$@"
        ;;
esac
