#!/usr/bin/env bash
set -euo pipefail
# POS: system bank — Persistent command bank for saving and running shell commands
# POS_SUBCMDS: list add show run edit remove alias

source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/menu-lib.sh" 2>/dev/null || source "$(dirname "$0")/menu-lib.sh"
source "$(dirname "$0")/../lib/bank-lib.sh" 2>/dev/null || source "$(dirname "$0")/bank-lib.sh"

usage() {
    cat <<'EOF'
Usage: pos system bank [subcommand] [args]

Persistent command bank for saving and running shell commands.

Subcommands:
  list                  List all saved commands
  add <name> [desc] [cmd]  Add a new command (interactive if args missing)
  show <name>           Show command details
  run <name> [key=val ...] Run a command (interactive for missing params)
  edit <name>           Edit an existing command
  remove <name>         Remove a command
  alias <name> [alias_name]  Create/update a bash alias for a bank command
  alias list            List bash aliases in ~/.bashrc
  alias remove <alias_name>  Remove a bash alias

Interactive menu:
  pos system bank              (no args on a terminal)

Examples:
  pos system bank add disk-info "Disk usage" "df -h && du -sh /mnt/12T"
  pos system bank run disk-info
  pos system bank run convert-video input=/path/to/video.mp4 quality=23 output=/path/out.mp4
EOF
    exit 0
}

case "${1:-}" in
    -h|--help) usage ;;
esac

# ── Interactive command picker ───────────────────────────────────
_pick_command() {
    bank_load
    local count="${#BANK_NAMES[@]}"
    if [ "$count" -eq 0 ]; then
        warn "Command bank is empty — add one with: pos system bank add <name> \"desc\" \"cmd\""
        return 1
    fi
    local -a items=()
    local i
    for ((i = 0; i < count; i++)); do
        if [ -n "${BANK_DESCS[$i]}" ]; then
            items+=("${BANK_NAMES[$i]} — ${BANK_DESCS[$i]}")
        else
            items+=("${BANK_NAMES[$i]}")
        fi
    done
    local choice
    choice="$(menu_pick "Pick command" "${items[@]}")" || return 1
    echo "${BANK_NAMES[$((choice - 1))]}"
}

# ── cmd_list ─────────────────────────────────────────────────────
cmd_list() {
    bank_load
    local count="${#BANK_NAMES[@]}"
    if [ "$count" -eq 0 ]; then
        log "Command bank is empty — add one with: pos system bank add <name> \"desc\" \"cmd\""
        return 0
    fi
    echo
    echo "COMMAND BANK"
    echo
    local i
    for ((i = 0; i < count; i++)); do
        if [ -n "${BANK_DESCS[$i]}" ]; then
            printf '  %-25s %s\n' "${BANK_NAMES[$i]}" "${BANK_DESCS[$i]}"
        else
            printf '  %s\n' "${BANK_NAMES[$i]}"
        fi
    done
    echo
}

# ── cmd_add ──────────────────────────────────────────────────────
cmd_add() {
    local name="" desc="" cmd=""
    case $# in
        0) ;;
        1) name="$1" ;;
        2) name="$1"; desc="$2" ;;
        *) name="$1"; desc="$2"; cmd="$3" ;;
    esac
    if [ -z "$name" ]; then
        name="$(menu_ask_value "Name")" || return 0
    fi
    if [ -z "$desc" ]; then
        desc="$(menu_ask_value "Description" "")" || return 0
    fi
    if [ -z "$cmd" ]; then
        cmd="$(menu_read_value "Command")" || return 0
    fi
    bank_valid_name "$name" || err "Invalid name: $name (use letters, numbers, hyphens, underscores)"
    [ -n "$cmd" ] || err "Command cannot be empty"
    bank_load
    local i
    for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
        [[ "${BANK_NAMES[$i]}" == "$name" ]] && err "Command '$name' already exists (use 'pos system bank edit' to modify)"
    done
    bank_add "$name" "$desc" "$cmd"
    ok "Saved: $name"
}

# ── cmd_show ─────────────────────────────────────────────────────
cmd_show() {
    [ -n "${1:-}" ] || err "Usage: pos system bank show <name>"
    local name="$1"
    bank_load
    bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
    local cmd_name cmd_desc cmd_cmd
    cmd_name="${BANK_NAMES[$BANK_IDX]}"
    cmd_desc="${BANK_DESCS[$BANK_IDX]}"
    cmd_cmd="${BANK_CMDS[$BANK_IDX]}"
    echo
    echo "Name: $cmd_name"
    echo
    echo "Description:"
    if [ -n "$cmd_desc" ]; then
        echo "$cmd_desc"
    else
        echo "  (none)"
    fi
    echo
    echo "Command:"
    echo
    echo "$cmd_cmd"
    local params
    params="$(_extract_params "$cmd_cmd")"
    if [ -n "$params" ]; then
        echo
        echo "Parameters:"
        echo "$params" | while IFS= read -r p; do
            echo "  $p"
        done
    fi
    echo
}

# ── cmd_run ──────────────────────────────────────────────────────
cmd_run() {
    local name=""
    local -a cli_params=()
    if [ $# -eq 0 ]; then
        name="$(_pick_command)" || return 0
    else
        name="$1"; shift
        while [ $# -gt 0 ]; do
            case "$1" in
                *=*) cli_params+=("$1") ;;
                *) err "Invalid parameter format: $1 (expected key=value)" ;;
            esac
            shift
        done
    fi
    bank_load
    bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
    local cmd_name cmd_desc cmd_cmd
    cmd_name="${BANK_NAMES[$BANK_IDX]}"
    cmd_desc="${BANK_DESCS[$BANK_IDX]}"
    cmd_cmd="${BANK_CMDS[$BANK_IDX]}"
    # Detect parameters
    local params_str
    params_str="$(_extract_params "$cmd_cmd")"
    local -a params=()
    if [ -n "$params_str" ]; then
        while IFS= read -r p; do
            params+=("$p")
        done <<< "$params_str"
    fi
    # Build parameter values from CLI args
    local -A param_values=()
    for p in "${cli_params[@]}"; do
        local key="${p%%=*}" val="${p#*=}"
        param_values["$key"]="$val"
    done
    # Interactively prompt for missing parameters
    local -a missing=()
    for p in "${params[@]}"; do
        [[ -z "${param_values[$p]+x}" ]] && missing+=("$p")
    done
    if [ ${#missing[@]} -gt 0 ]; then
        for p in "${missing[@]}"; do
            local val
            val="$(menu_ask_value "$p" "")" || return 0
            param_values["$p"]="$val"
        done
    fi
    # Substitute parameters
    local final_cmd="$cmd_cmd"
    if [ ${#params[@]} -gt 0 ]; then
        local -a sub_args=()
        for p in "${params[@]}"; do
            sub_args+=("${p}=${param_values[$p]:-}")
        done
        final_cmd="$(_substitute_params "$cmd_cmd" "${sub_args[@]}")"
    fi
    echo
    log "Running: $final_cmd"
    echo
    eval "$final_cmd"
    return $?
}

# ── cmd_edit ─────────────────────────────────────────────────────
cmd_edit() {
    local name="${1:-}"
    if [ -z "$name" ]; then
        name="$(_pick_command)" || return 0
    fi
    bank_load
    bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
    local old_name old_desc old_cmd
    old_name="${BANK_NAMES[$BANK_IDX]}"
    old_desc="${BANK_DESCS[$BANK_IDX]}"
    old_cmd="${BANK_CMDS[$BANK_IDX]}"
    echo
    log "Editing: $old_name"
    echo
    local new_name new_desc new_cmd
    new_name="$(menu_ask_value "New name" "$old_name")" || return 0
    new_desc="$(menu_ask_value "New description" "$old_desc")" || return 0
    new_cmd="$(menu_read_value "New command")" || return 0
    [ -z "$new_name" ] && new_name="$old_name"
    [ -z "$new_desc" ] && new_desc="$old_desc"
    [ -z "$new_cmd" ] && new_cmd="$old_cmd"
    if [ "$new_name" != "$old_name" ]; then
        bank_valid_name "$new_name" || err "Invalid name: $new_name"
        local i found=0
        for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
            [[ "${BANK_NAMES[$i]}" == "$new_name" ]] && found=1
        done
        [[ "$found" -eq 1 ]] && err "A command named '$new_name' already exists"
        bank_remove "$old_name"
        bank_add "$new_name" "$new_desc" "$new_cmd"
    else
        bank_update "$new_name" "$new_desc" "$new_cmd"
    fi
    ok "Updated: $new_name"
}

# ── cmd_remove ───────────────────────────────────────────────────
cmd_remove() {
    local name="${1:-}"
    if [ -z "$name" ]; then
        name="$(_pick_command)" || return 0
    fi
    bank_load
    bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
    if [ -t 0 ]; then
        confirm "Remove '$name'?" n || { log "Cancelled"; return 0; }
    fi
    bank_remove "$name"
    # Drop bash aliases pointing at the removed command (best-effort upkeep;
    # a malformed block is a warning, never a remove blocker).
    if [ -f "$BASH_RC_FILE" ] && grep -qF "$ALIAS_START" "$BASH_RC_FILE"; then
        if alias_block_parse; then
            local -a keep=() i
            local removed_alias=0
            for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
                if [[ "${ALIAS_TARGETS[${ALIAS_NAMES[$i]}]:-}" == "$name" ]]; then
                    removed_alias=1
                else
                    keep+=("${ALIAS_NAMES[$i]}")
                fi
            done
            if [ "$removed_alias" -eq 1 ]; then
                ALIAS_NAMES=("${keep[@]}")
                alias_block_write
                log "Removed alias pointing to '$name' from $BASH_RC_FILE"
            fi
        else
            local rc=$?
            [ "$rc" -eq 2 ] && warn "Alias block in $BASH_RC_FILE is malformed — left untouched"
        fi
    fi
    ok "Removed: $name"
}

# ── Alias management ─────────────────────────────────────────────
# Bash aliases for bank commands live in a single managed block inside
# $BASH_RC_FILE (~/.bashrc by default), so they survive rebuilds and are easy
# to see/undo by hand. Every line has the exact shape:
#   alias <alias_name>='pos system bank run <bank_name>'
# The block is written atomically (tmp + mv, preserving file permissions).
ALIAS_START='# >>> pos bank aliases (managed by pos system bank — do not hand-edit) <<<'
ALIAS_END='# <<< pos bank aliases (managed by pos system bank) <<<'
BASH_RC_FILE="${BASH_RC_FILE:-$HOME/.bashrc}"

# Valid bash alias names we manage: letters/digits/_/- (same rule as bank names).
alias_valid_name() {
    [[ "$1" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]
}

# Parse the managed block from $BASH_RC_FILE into:
#   ALIAS_NAMES   — ordered array of alias names (file order, unique)
#   ALIAS_TARGETS — assoc: alias_name → bank command name
# Returns: 0 = healthy block present, 1 = no block, 2 = malformed block.
alias_block_parse() {
    declare -g -a ALIAS_NAMES
    declare -g -A ALIAS_TARGETS
    ALIAS_NAMES=()
    ALIAS_TARGETS=()
    [ -f "$BASH_RC_FILE" ] || return 1
    local line in_block=0 starts=0 ends=0 rc=0
    while IFS= read -r line; do
        if [ "$in_block" -eq 0 ]; then
            if [[ "$line" == "$ALIAS_START" ]]; then
                in_block=1
                starts=$((starts + 1))
                [ "$starts" -gt 1 ] && rc=2
            elif [[ "$line" == "$ALIAS_END" ]]; then
                rc=2
            fi
        elif [[ "$line" == "$ALIAS_END" ]]; then
            in_block=0
            ends=$((ends + 1))
        elif [[ "$line" =~ ^alias[[:space:]]+([a-zA-Z][a-zA-Z0-9_-]*)= ]]; then
            # NOTE: no `'` inside the =~ regex (bash treats quotes there as
            # quoting operators and would literalize the ( ) groups) — the
            # quoted payload is matched with a glob `case` instead.
            local a="${BASH_REMATCH[1]}"
            local payload="${line#*=}"
            case "$payload" in
                "'pos system bank run "[a-zA-Z][a-zA-Z0-9_-]*"'")
                    local t="${payload%\'}"
                    t="${t#\'pos system bank run }"
                    if [[ " ${ALIAS_NAMES[*]:-} " == *" $a "* ]]; then
                        rc=2
                        continue
                    fi
                    ALIAS_NAMES+=("$a")
                    ALIAS_TARGETS["$a"]="$t"
                    ;;
                *) rc=2 ;;
            esac
        else
            rc=2
        fi
    done < "$BASH_RC_FILE"
    [ "$in_block" -eq 1 ] && rc=2
    [ "$rc" -eq 0 ] && [ "$starts" -eq 0 ] && return 1
    return "$rc"
}

# Require a healthy managed block (alias commands err on missing/malformed).
alias_block_require() {
    local rc
    if alias_block_parse; then
        return 0
    else
        rc=$?
    fi
    if [ "$rc" -eq 1 ]; then
        err "No alias block in $BASH_RC_FILE — add one with: pos system bank alias <name>"
    fi
    err "Alias block in $BASH_RC_FILE is malformed (markers or lines were hand-edited) — fix it manually, then retry"
}

# Scan for an alias NAME defined OUTSIDE the managed block (a collision we
# refuse to overwrite). Prints the offending line; rc 0 = found, 1 = clean.
alias_collision() {
    local name="$1"
    [ -f "$BASH_RC_FILE" ] || return 1
    local line in_block=0
    while IFS= read -r line; do
        if [ "$in_block" -eq 0 ] && [[ "$line" == "$ALIAS_START" ]]; then
            in_block=1
            continue
        fi
        if [ "$in_block" -eq 1 ] && [[ "$line" == "$ALIAS_END" ]]; then
            in_block=0
            continue
        fi
        if [ "$in_block" -eq 0 ] && [[ "$line" =~ ^[[:space:]]*alias[[:space:]]+$name= ]]; then
            printf '%s\n' "$line"
            return 0
        fi
    done < "$BASH_RC_FILE"
    return 1
}

# Rewrite $BASH_RC_FILE from ALIAS_NAMES + ALIAS_TARGETS. Callers guarantee the
# file is absent or has at most one healthy block. Removes the block entirely
# when ALIAS_NAMES is empty; appends a fresh block when none exists yet.
alias_block_write() {
    local content="" start_line=0 end_line=0 n=0 line
    local pre="" post="" dir perms tmp new_content=""
    if [ -f "$BASH_RC_FILE" ]; then
        content="$(< "$BASH_RC_FILE")"
        perms="$(stat -c %a "$BASH_RC_FILE" 2>/dev/null || printf '600')"
        while IFS= read -r line; do
            n=$((n + 1))
            if [ "$start_line" -eq 0 ] && [[ "$line" == "$ALIAS_START" ]]; then
                start_line=$n
            elif [ "$start_line" -gt 0 ] && [ "$end_line" -eq 0 ] && [[ "$line" == "$ALIAS_END" ]]; then
                end_line=$n
            fi
        done <<< "$content"
        if [ "$start_line" -gt 0 ] && [ "$end_line" -eq 0 ]; then
            err "Alias block in $BASH_RC_FILE has no end marker — fix it manually, then retry"
        fi
    else
        perms=600
    fi
    # No block and nothing to manage → never touch the file (nor create it).
    if [ "$start_line" -eq 0 ] && [ "${#ALIAS_NAMES[@]}" -eq 0 ]; then
        return 0
    fi
    if [ "$start_line" -gt 0 ]; then
        # NOTE: sed "1,0p" is NOT empty in GNU sed (0 is a last-line sentinel,
        # it prints line 1) — guard the ranges so edge blocks rewrite cleanly.
        if [ "$start_line" -gt 1 ]; then
            pre="$(printf '%s\n' "$content" | sed -n "1,$((start_line - 1))p")"
        fi
        if [ "$end_line" -lt "$n" ]; then
            post="$(printf '%s\n' "$content" | sed -n "$((end_line + 1)),\$p")"
        fi
    else
        pre="$(printf '%s\n' "$content")"
    fi
    if [ -n "$pre" ]; then
        new_content+="$pre"
        case "$new_content" in
            *$'\n') ;;
            *) new_content+=$'\n' ;;
        esac
    fi
    if [ "${#ALIAS_NAMES[@]}" -gt 0 ]; then
        new_content+="$ALIAS_START"$'\n'
        local i
        for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
            new_content+="alias ${ALIAS_NAMES[$i]}='pos system bank run ${ALIAS_TARGETS[${ALIAS_NAMES[$i]}]:-}'"$'\n'
        done
        new_content+="$ALIAS_END"$'\n'
    fi
    if [ -n "$post" ]; then
        new_content+="$post"
        case "$new_content" in
            *$'\n') ;;
            *) new_content+=$'\n' ;;
        esac
    fi
    dir="$(dirname "$BASH_RC_FILE")"
    mkdir -p "$dir" || err "Cannot create directory: $dir"
    tmp="$(mktemp "${dir}/.pos-bank-aliases.XXXXXX")" || err "Cannot create temp file in $dir"
    printf '%s' "$new_content" > "$tmp"
    chmod "$perms" "$tmp"
    mv "$tmp" "$BASH_RC_FILE"
}

# ── cmd_alias ────────────────────────────────────────────────────
cmd_alias() {
    case "${1:-}" in
        list)   shift; cmd_alias_list "$@" ;;
        remove) shift; cmd_alias_remove "$@" ;;
        "")     err "Usage: pos system bank alias <name> [alias_name] | list | remove <alias_name>" ;;
        *)      cmd_alias_add "$@" ;;
    esac
}

cmd_alias_add() {
    local name="${1:-}" alias_name="${2:-$1}"
    [ -n "$name" ] || err "Usage: pos system bank alias <name> [alias_name]"
    bank_valid_name "$name" || err "Invalid command name: $name"
    alias_valid_name "$alias_name" || err "Invalid alias name: $alias_name (use letters, numbers, hyphens, underscores)"
    bank_load
    bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
    local rc
    if alias_block_parse; then
        rc=0
    else
        rc=$?
    fi
    [ "$rc" -eq 2 ] && err "Alias block in $BASH_RC_FILE is malformed (hand-edited?) — fix it manually, then retry"
    if [ -f "$BASH_RC_FILE" ] && hit="$(alias_collision "$alias_name")"; then
        err "Alias '$alias_name' is already defined outside the managed block in $BASH_RC_FILE: $hit"
    fi
    if command -v "$alias_name" >/dev/null 2>&1; then
        warn "Note: '$alias_name' is also a command on PATH — the alias will shadow it in interactive shells"
    fi
    local found=0 i
    for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
        [[ "${ALIAS_NAMES[$i]}" == "$alias_name" ]] && found=1
    done
    [ "$found" -eq 0 ] && ALIAS_NAMES+=("$alias_name")
    ALIAS_TARGETS["$alias_name"]="$name"
    alias_block_write
    ok "Alias '$alias_name' → pos system bank run $name (in $BASH_RC_FILE)"
    log "Run: source $BASH_RC_FILE  (or open a new shell)"
}

cmd_alias_list() {
    alias_block_require
    if [ "${#ALIAS_NAMES[@]}" -eq 0 ]; then
        log "No aliases in $BASH_RC_FILE — add one with: pos system bank alias <name>"
        return 0
    fi
    echo
    echo "BANK ALIASES (in $BASH_RC_FILE)"
    echo
    local i
    for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
        printf '  %-25s → pos system bank run %s\n' "${ALIAS_NAMES[$i]}" "${ALIAS_TARGETS[${ALIAS_NAMES[$i]}]:-}"
    done
    echo
}

# ── Interactive alias picker ─────────────────────────────────────
_pick_alias() {
    local rc
    if alias_block_parse; then
        rc=0
    else
        rc=$?
    fi
    [ "$rc" -eq 2 ] && err "Alias block in $BASH_RC_FILE is malformed — fix it manually"
    [ "$rc" -eq 1 ] && err "No alias block in $BASH_RC_FILE — add one with: pos system bank alias <name>"
    if [ "${#ALIAS_NAMES[@]}" -eq 0 ]; then
        warn "No aliases yet — add one first"
        return 1
    fi
    local -a items=()
    local i
    for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
        items+=("${ALIAS_NAMES[$i]} → pos system bank run ${ALIAS_TARGETS[${ALIAS_NAMES[$i]}]:-}")
    done
    local choice
    choice="$(menu_pick "Pick alias" "${items[@]}")" || return 1
    echo "${ALIAS_NAMES[$((choice - 1))]}"
}

cmd_alias_remove() {
    local alias_name="${1:-}"
    if [ -z "$alias_name" ]; then
        if [ -t 0 ]; then
            alias_name="$(_pick_alias)" || return 0
        else
            err "Usage: pos system bank alias remove <alias_name>"
        fi
    fi
    alias_block_require
    local found=0 i
    for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
        [[ "${ALIAS_NAMES[$i]}" == "$alias_name" ]] && found=1
    done
    [ "$found" -eq 1 ] || err "Alias '$alias_name' not found (see: pos system bank alias list)"
    if [ -t 0 ]; then
        confirm "Remove alias '$alias_name'?" n || { log "Cancelled"; return 0; }
    fi
    local -a keep=()
    for ((i = 0; i < ${#ALIAS_NAMES[@]}; i++)); do
        [[ "${ALIAS_NAMES[$i]}" != "$alias_name" ]] && keep+=("${ALIAS_NAMES[$i]}")
    done
    ALIAS_NAMES=("${keep[@]}")
    unset "ALIAS_TARGETS[$alias_name]"
    alias_block_write
    ok "Removed alias '$alias_name' from $BASH_RC_FILE"
    log "Run: source $BASH_RC_FILE  (or open a new shell)"
}

alias_menu() {
    while true; do
        local choice
        choice="$(menu_run "Bank Aliases" \
            "List aliases" \
            "Add alias" \
            "Remove alias")" || return 0
        case "$choice" in
            1) cmd_alias_list ;;
            2)
                local name alias_name
                name="$(_pick_command)" || return 0
                alias_name="$(menu_ask_value "Alias name" "$name")" || return 0
                [ -z "$alias_name" ] && alias_name="$name"
                cmd_alias_add "$name" "$alias_name"
                ;;
            3)
                local alias_name
                alias_name="$(_pick_alias)" || return 0
                cmd_alias_remove "$alias_name"
                ;;
        esac
    done
}

# ── Interactive menu ─────────────────────────────────────────────
run_menu() {
    menu_guard || exit 1
    while true; do
        local choice
        choice="$(menu_run "Command Bank" \
            "List commands" \
            "Add command" \
            "Run command" \
            "Edit command" \
            "Remove command" \
            "Manage aliases")" || return 0
        case "$choice" in
            1) cmd_list ;;
            2) cmd_add ;;
            3) cmd_run ;;
            4) cmd_edit ;;
            5) cmd_remove ;;
            6) alias_menu ;;
        esac
    done
}

# ── Dispatch ─────────────────────────────────────────────────────
if [ "${1:-}" = "menu" ]; then
    run_menu
    exit 0
fi

for arg in "$@"; do
    case "$arg" in
        -h|--help) usage ;;
    esac
done

if [ $# -eq 0 ] && [ -t 0 ]; then
    run_menu
    exit 0
fi

case "${1:-}" in
    list)   shift; cmd_list "$@" ;;
    add)    shift; cmd_add "$@" ;;
    show)   shift; cmd_show "$@" ;;
    run)    shift; cmd_run "$@" ;;
    edit)   shift; cmd_edit "$@" ;;
    remove) shift; cmd_remove "$@" ;;
    alias)  shift; cmd_alias "$@" ;;
    "")     usage ;;
    *)      err "Unknown subcommand: $1 (see --help)" ;;
esac
