#!/usr/bin/env bash
#══════════════════════════════════════════════════════════════
#  FRPS Manager — Interactive multi-instance frps management
#══════════════════════════════════════════════════════════════

FRPS_BIN="/usr/local/bin/frps"
FRPS_CONFIG_DIR="/etc/frps"
FRPS_SERVICE_PREFIX="frps"
DOWNLOAD_URL_BASE="https://github.com/fatedier/frp/releases/download"

RST="\033[0m"; BOLD="\033[1m"; DIM="\033[2m"
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"
CYAN="\033[36m"; WHITE="\033[97m"; REV="\033[7m"

# ── Globals for menu communication (avoid subshell) ──────────
MENU_ACTION=""     # set by arrow_menu: ENTER / QUIT / ESC / REFRESH


# ── Terminal ─────────────────────────────────────────────────
hide_cursor()  { printf "\033[?25l"; }
show_cursor()  { printf "\033[?25h"; }
move_to()      { printf "\033[%d;%dH" "$1" "$2"; }
clear_line()   { printf "\033[2K"; }
clear_screen() { printf "\033[2J\033[H"; }

cleanup() { show_cursor; stty echo 2>/dev/null || true; tput sgr0 2>/dev/null || true; }
trap cleanup EXIT INT TERM

read_key() {
    local key=""
    IFS= read -rsn1 key 2>/dev/null || true
    if [[ "$key" == $'\x1b' ]]; then
        local seq=""
        IFS= read -rsn2 -t 0.1 seq 2>/dev/null || true
        case "$seq" in
            '[A') echo "UP"   ;; '[B') echo "DOWN" ;;
            '[C') echo "RIGHT";; '[D') echo "LEFT" ;;
            *) echo "ESC" ;;
        esac
    elif [[ "$key" == "" ]]; then echo "ENTER"
    elif [[ "$key" == "q" || "$key" == "Q" ]]; then echo "QUIT"
    elif [[ "$key" == "r" || "$key" == "R" ]]; then echo "REFRESH"
    else echo "$key"
    fi
}

# ── Drawing ──────────────────────────────────────────────────
draw_box() {
    local y="$1" x="$2" w="$3" h="$4" title="${5:-}" i
    move_to "$y" "$x"
    printf "${CYAN}╔"
    if [[ -n "$title" ]]; then
        printf "═ ${WHITE}${BOLD}%s${RST}${CYAN} " "$title"
        local tlen=$(( ${#title} + 4 ))
        for (( i=0; i < w-2-tlen; i++ )); do printf "═"; done
    else
        for (( i=0; i < w-2; i++ )); do printf "═"; done
    fi
    printf "╗${RST}"
    for (( i=1; i < h-1; i++ )); do
        move_to "$(( y+i ))" "$x"; printf "${CYAN}║${RST}"
        move_to "$(( y+i ))" "$(( x+w-1 ))"; printf "${CYAN}║${RST}"
    done
    move_to "$(( y+h-1 ))" "$x"
    printf "${CYAN}╚"
    for (( i=0; i < w-2; i++ )); do printf "═"; done
    printf "╝${RST}"
}

draw_hline() {
    local y="$1" x="$2" w="$3" i
    move_to "$y" "$x"; printf "${CYAN}╠"
    for (( i=0; i < w-2; i++ )); do printf "═"; done
    printf "╣${RST}"
}

# ── Helpers ──────────────────────────────────────────────────
get_instances() {
    [[ -d "$FRPS_CONFIG_DIR" ]] || return 0
    local old_ng; old_ng=$(shopt -p nullglob 2>/dev/null || true)
    shopt -s nullglob
    local files=("$FRPS_CONFIG_DIR"/*.toml "$FRPS_CONFIG_DIR"/*.ini)
    eval "$old_ng" 2>/dev/null || true
    local seen="" f name
    for f in "${files[@]}"; do
        [[ -f "$f" ]] || continue
        name=$(basename "$f"); name="${name%.*}"
        if [[ ":$seen:" != *":$name:"* ]]; then
            echo "$name"; seen="${seen}:${name}"
        fi
    done
}

svc_name()     { echo "${FRPS_SERVICE_PREFIX}@${1}"; }
is_installed() { [[ -x "$FRPS_BIN" ]]; }
frps_version() { is_installed && { "$FRPS_BIN" --version 2>/dev/null || echo "unknown"; } || echo "not installed"; }

get_status() {
    local svc; svc=$(svc_name "$1")
    if systemctl is-active --quiet "$svc" 2>/dev/null; then echo "running"
    elif systemctl is-enabled --quiet "$svc" 2>/dev/null; then echo "stopped"
    else echo "disabled"; fi
}
status_icon() {
    case "$1" in running) echo "●";; stopped) echo "○";; disabled) echo "◌";; *) echo "?";; esac
}
status_color() {
    case "$1" in running) printf "${GREEN}";; stopped) printf "${YELLOW}";; disabled) printf "${RED}";; *) printf "${DIM}";; esac
}

get_proc_info() {
    local svc; svc=$(svc_name "$1")
    local pid; pid=$(systemctl show -p MainPID --value "$svc" 2>/dev/null || echo "0")
    if [[ -z "$pid" ]] || [[ "$pid" == "0" ]]; then echo "- - - -"; return 0; fi
    local rss cpu etime
    rss=$(ps -p "$pid" -o rss= 2>/dev/null || echo "0")
    cpu=$(ps -p "$pid" -o %cpu= 2>/dev/null || echo "0")
    etime=$(ps -p "$pid" -o etime= 2>/dev/null || echo "-")
    rss=$(echo "$rss"|xargs); cpu=$(echo "$cpu"|xargs); etime=$(echo "$etime"|xargs)
    local rss_h
    if [[ "$rss" =~ ^[0-9]+$ ]] && [[ "$rss" -gt 1024 ]]; then rss_h="$(( rss/1024 ))M"; else rss_h="${rss}K"; fi
    echo "$pid $rss_h ${cpu}% $etime"
}

# Pull bindPort from a frps config (toml or ini); print "-" if not found
get_bind_port() {
    local conf="$1"
    [[ -f "$conf" ]] || { echo "-"; return; }
    local p
    p=$(sudo grep -E '^[[:space:]]*bindPort[[:space:]]*=' "$conf" 2>/dev/null \
        | head -n1 | sed -E 's/[^0-9]//g')
    if [[ -z "$p" ]]; then
        p=$(sudo grep -E '^[[:space:]]*bind_port[[:space:]]*=' "$conf" 2>/dev/null \
            | head -n1 | sed -E 's/[^0-9]//g')
    fi
    [[ -z "$p" ]] && echo "-" || echo "$p"
}

install_systemd_template() {
    sudo tee "/etc/systemd/system/${FRPS_SERVICE_PREFIX}@.service" > /dev/null << 'UNIT'
[Unit]
Description=frps server instance - %i
After=network.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frps/%i.toml
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
UNIT
    sudo systemctl daemon-reload
}

# ── Install ──────────────────────────────────────────────────
do_install() {
    clear_screen; show_cursor
    echo -e "${BOLD}${CYAN}══════════════════════════════════════════════════${RST}"
    echo -e "${BOLD}  Install / Update frps${RST}"
    echo -e "${BOLD}${CYAN}══════════════════════════════════════════════════${RST}\n"

    # ── Show current status ──
    if is_installed; then
        echo -e "  Current version: ${GREEN}$(frps_version)${RST}"
    else
        echo -e "  Current version: ${RED}not installed${RST}"
    fi
    echo -e "  Binary path:     ${CYAN}${FRPS_BIN}${RST}"
    echo -e "  Config dir:      ${CYAN}${FRPS_CONFIG_DIR}${RST}"
    echo ""

    # ── Detect arch ──
    local arch
    case "$(uname -m)" in
        x86_64)  arch="amd64";;
        aarch64) arch="arm64";;
        armv7l)  arch="arm";;
        i686)    arch="386";;
        *) echo -e "  ${RED}✖ Unsupported architecture: $(uname -m)${RST}"; read -rp "  Press Enter..."; return;;
    esac
    echo -e "  Architecture:    ${GREEN}${arch}${RST}\n"

    # ── Check download tool ──
    local dl_cmd=""
    if command -v wget &>/dev/null; then
        dl_cmd="wget"
    elif command -v curl &>/dev/null; then
        dl_cmd="curl"
    else
        echo -e "  ${RED}✖ Neither wget nor curl found. Install one first.${RST}"
        echo -e "  ${DIM}  apt install wget  /  yum install wget${RST}"
        read -rp "  Press Enter..."; return
    fi

    # ── Show known versions ──
    echo -e "  ${BOLD}Known stable versions:${RST}"
    echo -e "    ${GREEN}0.61.1${RST}  (latest as of 2025-01)"
    echo -e "    ${DIM}0.61.0  0.60.0  0.59.0  0.58.1  0.57.0  0.56.0${RST}"
    echo -e "    ${DIM}0.55.1  0.54.0  0.53.2  0.52.3  0.51.3${RST}"
    echo -e "  ${DIM}Full list: https://github.com/fatedier/frp/releases${RST}\n"

    # ── Manual install hint ──
    echo -e "  ${YELLOW}Manual install:${RST} place the frps binary at ${CYAN}${FRPS_BIN}${RST}"
    echo -e "  ${DIM}  chmod +x ${FRPS_BIN} && mkdir -p ${FRPS_CONFIG_DIR}${RST}\n"

    read -rp "  Enter frp version to install (e.g. 0.61.1, blank to cancel): " version
    [[ -z "$version" ]] && { echo -e "  ${YELLOW}Cancelled.${RST}"; sleep 1; return; }

    # ── Validate version format ──
    if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        echo -e "  ${RED}✖ Invalid version format. Expected: X.Y.Z (e.g. 0.61.1)${RST}"
        read -rp "  Press Enter..."; return
    fi

    local fname="frp_${version}_linux_${arch}"
    local url="${DOWNLOAD_URL_BASE}/v${version}/${fname}.tar.gz"
    local tmp; tmp=$(mktemp -d)

    echo -e "\n  ${CYAN}↓ Downloading...${RST}"
    echo -e "    ${DIM}${url}${RST}\n"

    local dl_ok=0
    if [[ "$dl_cmd" == "wget" ]]; then
        wget -q --show-progress -O "${tmp}/${fname}.tar.gz" "$url" 2>&1 && dl_ok=1
    else
        curl -fL --progress-bar -o "${tmp}/${fname}.tar.gz" "$url" && dl_ok=1
    fi

    if [[ $dl_ok -ne 1 ]]; then
        echo -e "  ${RED}✖ Download failed.${RST}"
        echo -e "  ${DIM}Possible causes:${RST}"
        echo -e "  ${DIM}  - Version ${version} does not exist${RST}"
        echo -e "  ${DIM}  - Network issue / GitHub unreachable${RST}"
        echo -e "  ${DIM}  Check: https://github.com/fatedier/frp/releases/tag/v${version}${RST}"
        rm -rf "$tmp"; read -rp "  Press Enter..."; return
    fi

    echo -e "  ${CYAN}↻ Extracting...${RST}"
    if ! tar -xzf "${tmp}/${fname}.tar.gz" -C "$tmp" 2>/dev/null; then
        echo -e "  ${RED}✖ Extraction failed — archive may be corrupted.${RST}"
        rm -rf "$tmp"; read -rp "  Press Enter..."; return
    fi

    if [[ ! -f "${tmp}/${fname}/frps" ]]; then
        echo -e "  ${RED}✖ frps binary not found in archive.${RST}"
        rm -rf "$tmp"; read -rp "  Press Enter..."; return
    fi

    sudo cp -f "${tmp}/${fname}/frps" "$FRPS_BIN"; sudo chmod +x "$FRPS_BIN"
    sudo mkdir -p "$FRPS_CONFIG_DIR"
    install_systemd_template

    echo -e "\n  ${GREEN}✔ frps $(frps_version) installed!${RST}"
    echo -e "  ${DIM}Binary:  ${FRPS_BIN}${RST}"
    echo -e "  ${DIM}Configs: ${FRPS_CONFIG_DIR}/${RST}"
    echo -e "  ${DIM}Service: systemctl [start|stop] ${FRPS_SERVICE_PREFIX}@<name>${RST}"
    rm -rf "$tmp"; read -rp "  Press Enter..."
}

# ── Add instance ─────────────────────────────────────────────
do_add_instance() {
    clear_screen; show_cursor
    echo -e "${BOLD}${CYAN}══════════════════════════════════════${RST}"
    echo -e "${BOLD}  Add New FRPS Instance${RST}"
    echo -e "${BOLD}${CYAN}══════════════════════════════════════${RST}\n"
    if ! is_installed; then
        echo -e "  ${RED}✖ frps not installed.${RST}"
        echo -e "  ${DIM}Run 'Install / Update frps' first, or manually place binary at ${FRPS_BIN}${RST}"
        read -rp "  Press Enter..."; return
    fi
    read -rp "  Instance name: " name
    name=$(echo "$name" | tr -cd 'a-zA-Z0-9_-')
    [[ -z "$name" ]] && { echo -e "  ${YELLOW}Cancelled.${RST}"; sleep 1; return; }
    local conf="${FRPS_CONFIG_DIR}/${name}.toml"
    [[ -f "$conf" ]] && { echo -e "  ${YELLOW}⚠ '${name}' exists.${RST}"; read -rp "  Press Enter..."; return; }
    echo ""

    # ── Core ──
    read -rp "  Bind address [0.0.0.0]: " bind_addr; bind_addr=${bind_addr:-0.0.0.0}
    read -rp "  Bind port [7000]: "       bind_port; bind_port=${bind_port:-7000}
    read -rp "  Auth token (blank=none): " auth_token

    # ── HTTP / HTTPS vhost ──
    echo -e "\n  ${CYAN}HTTP/HTTPS vhost (for type=http/https proxies):${RST}"
    read -rp "  vhost HTTP port  (blank=disabled): " vhost_http
    read -rp "  vhost HTTPS port (blank=disabled): " vhost_https
    read -rp "  Subdomain host   (blank=none, e.g. frps.example.com): " sub_host

    # ── Dashboard ──
    echo -e "\n  ${CYAN}Web dashboard:${RST}"
    read -rp "  Enable dashboard? [y/N]: " dash_yn
    local dash_addr="" dash_port="" dash_user="" dash_pwd=""
    if [[ "${dash_yn,,}" == "y" ]]; then
        read -rp "  Dashboard addr [0.0.0.0]: " dash_addr; dash_addr=${dash_addr:-0.0.0.0}
        read -rp "  Dashboard port [7500]: "    dash_port; dash_port=${dash_port:-7500}
        read -rp "  Dashboard user [admin]: "   dash_user; dash_user=${dash_user:-admin}
        read -rp "  Dashboard password: "       dash_pwd
    fi

    # ── Optional: KCP / QUIC ──
    echo -e "\n  ${CYAN}Alternative transports (optional):${RST}"
    read -rp "  KCP bind port  (blank=disabled): " kcp_port
    read -rp "  QUIC bind port (blank=disabled): " quic_port

    # ── Optional: log level ──
    echo -e "\n  ${CYAN}Log:${RST}"
    read -rp "  Log level [info] (trace/debug/info/warn/error): " log_lvl; log_lvl=${log_lvl:-info}

    # ── Build config ──
    local tmp_conf; tmp_conf=$(mktemp)
    {
        echo "bindAddr = \"${bind_addr}\""
        echo "bindPort = ${bind_port}"
        [[ -n "$kcp_port"  ]] && echo "kcpBindPort = ${kcp_port}"
        [[ -n "$quic_port" ]] && echo "quicBindPort = ${quic_port}"
        [[ -n "$vhost_http"  ]] && echo "vhostHTTPPort = ${vhost_http}"
        [[ -n "$vhost_https" ]] && echo "vhostHTTPSPort = ${vhost_https}"
        [[ -n "$sub_host"    ]] && echo "subDomainHost = \"${sub_host}\""
        echo ""
        echo "log.to = \"console\""
        echo "log.level = \"${log_lvl}\""
        echo "log.maxDays = 3"
        if [[ -n "$auth_token" ]]; then
            echo ""
            echo "auth.method = \"token\""
            echo "auth.token = \"${auth_token}\""
        fi
        if [[ "${dash_yn,,}" == "y" ]]; then
            echo ""
            echo "webServer.addr = \"${dash_addr}\""
            echo "webServer.port = ${dash_port}"
            echo "webServer.user = \"${dash_user}\""
            echo "webServer.password = \"${dash_pwd}\""
        fi
    } > "$tmp_conf"

    sudo mv "$tmp_conf" "$conf"
    echo -e "\n  ${GREEN}✔ '${name}' created${RST}  ${DIM}${conf}${RST}"

    # ── Firewall hint ──
    echo -e "  ${DIM}Open ports on your firewall as needed:${RST}"
    echo -e "  ${DIM}  control: ${bind_port}/tcp${RST}"
    [[ -n "$vhost_http"  ]] && echo -e "  ${DIM}  http:    ${vhost_http}/tcp${RST}"
    [[ -n "$vhost_https" ]] && echo -e "  ${DIM}  https:   ${vhost_https}/tcp${RST}"
    [[ -n "$kcp_port"    ]] && echo -e "  ${DIM}  kcp:     ${kcp_port}/udp${RST}"
    [[ -n "$quic_port"   ]] && echo -e "  ${DIM}  quic:    ${quic_port}/udp${RST}"
    [[ "${dash_yn,,}" == "y" ]] && echo -e "  ${DIM}  dash:    ${dash_port}/tcp${RST}"

    read -rp "  Start & enable? [Y/n]: " yn
    if [[ "${yn,,}" != "n" ]]; then
        local svc; svc=$(svc_name "$name")
        if sudo systemctl enable --now "$svc" 2>/dev/null; then
            echo -e "  ${GREEN}✔ started & enabled${RST}"
        else echo -e "  ${RED}✖ failed${RST}"; fi
    fi
    read -rp "  Press Enter..."
}

# ═══════════════════════════════════════════════════════════════
#  Fast partial-redraw arrow menu
#
#  Caller must set:   items=()  types=()
#  Caller passes:     sel variable name, row_start, col, width
#  On return:         MENU_ACTION is set (ENTER/QUIT/ESC/REFRESH)
#  UP/DOWN only redraws the 2 changed rows — no syscalls.
# ═══════════════════════════════════════════════════════════════
_draw_row() {
    local idx="$1" sel="$2" ry="$3" cx="$4" w="$5"
    move_to "$(( ry + idx ))" "$cx"
    clear_line
    # Redraw box borders on this line
    move_to "$(( ry + idx ))" "$(( cx - 1 ))"
    printf "${CYAN}║${RST}"
    move_to "$(( ry + idx ))" "$(( cx + w ))"
    printf "${CYAN}║${RST}"
    # Content
    move_to "$(( ry + idx ))" "$cx"
    if [[ "${types[$idx]}" == "hdr" ]]; then
        printf "${DIM}${BOLD}%-${w}s${RST}" "${items[$idx]}"
    elif [[ "$idx" -eq "$sel" ]]; then
        printf "${REV}${BOLD} ▸ %-$(( w - 3 ))s${RST}" "${items[$idx]}"
    else
        printf "   %-$(( w - 3 ))s" "${items[$idx]}"
    fi
}

_next_sel() {
    # _next_sel <current> <direction +1/-1> <total>
    local cur="$1" dir="$2" total="$3" pos=$1
    while true; do
        pos=$(( pos + dir ))
        if [[ $pos -lt 0 ]] || [[ $pos -ge $total ]]; then
            echo "$cur"; return
        fi
        if [[ "${types[$pos]}" != "hdr" ]]; then
            echo "$pos"; return
        fi
    done
}

# arrow_menu <sel_varname> <row_start> <col> <width>
# Sets MENU_ACTION on exit. Modifies sel in caller's scope via nameref.
arrow_menu() {
    local -n _sel="$1"
    local ry="$2" cx="$3" w="$4"
    local total=${#items[@]}

    # Ensure sel on selectable row
    if [[ "${types[$_sel]}" == "hdr" ]]; then
        _sel=$(_next_sel "$_sel" 1 "$total")
    fi

    # Full draw all rows once
    local i
    for i in "${!items[@]}"; do
        _draw_row "$i" "$_sel" "$ry" "$cx" "$w"
    done

    # Input loop — only redraws changed rows
    while true; do
        local key
        key=$(read_key)
        case "$key" in
            UP)
                local ns
                ns=$(_next_sel "$_sel" -1 "$total")
                if [[ "$ns" -ne "$_sel" ]]; then
                    local old=$_sel
                    _sel=$ns
                    _draw_row "$old"  "$_sel" "$ry" "$cx" "$w"
                    _draw_row "$_sel" "$_sel" "$ry" "$cx" "$w"
                fi
                ;;
            DOWN)
                local ns
                ns=$(_next_sel "$_sel" 1 "$total")
                if [[ "$ns" -ne "$_sel" ]]; then
                    local old=$_sel
                    _sel=$ns
                    _draw_row "$old"  "$_sel" "$ry" "$cx" "$w"
                    _draw_row "$_sel" "$_sel" "$ry" "$cx" "$w"
                fi
                ;;
            ENTER|QUIT|ESC|REFRESH)
                MENU_ACTION="$key"
                return
                ;;
        esac
    done
}

# ═══════════════════════════════════════════════════════════════
#  Instance submenu
# ═══════════════════════════════════════════════════════════════
instance_menu() {
    local inst="$1"
    local svc; svc=$(svc_name "$inst")
    local sel=0
    local need_full=1
    local menu_row=0 menu_col=0 menu_w=0 conf=""

    while true; do
        if [[ $need_full -eq 1 ]]; then
            clear_screen
            hide_cursor

            local w=58 sx=3 sy=2
            local status; status=$(get_status "$inst")
            local enabled="no"
            systemctl is-enabled --quiet "$svc" 2>/dev/null && enabled="yes"
            local pi pid rss cpu etime
            pi=$(get_proc_info "$inst")
            pid=$(echo "$pi"|awk '{print $1}'); rss=$(echo "$pi"|awk '{print $2}')
            cpu=$(echo "$pi"|awk '{print $3}'); etime=$(echo "$pi"|awk '{print $4}')
            conf="${FRPS_CONFIG_DIR}/${inst}.toml"
            [[ -f "$conf" ]] || conf="${FRPS_CONFIG_DIR}/${inst}.ini"
            local bport; bport=$(get_bind_port "$conf")

            items=("Start" "Stop" "Restart" "Enable (boot)" "Disable (boot)"
                   "Status" "View Logs" "Clear Logs" "Edit Config" "View Config"
                   "Rename Instance" "Remove Instance" "← Back")
            types=("act" "act" "act" "act" "act" "act" "act" "act" "act" "act" "act" "act" "act")

            local h=$(( ${#items[@]} + 10 ))
            draw_box $sy $sx $w $h "Instance: ${inst}"

            local row=$(( sy+1 ))
            move_to $row $(( sx+3 ))
            printf "Status : $(status_color "$status")$(status_icon "$status") %-10s${RST}   Boot: %-5s" "$status" "$enabled"
            row=$(( row+1 ))
            move_to $row $(( sx+3 ))
            printf "PID    : %-8s   MEM: %-8s" "$pid" "$rss"
            row=$(( row+1 ))
            move_to $row $(( sx+3 ))
            printf "CPU    : %-8s   Uptime: %-10s   Bind: %s" "$cpu" "$etime" "$bport"
            row=$(( row+1 ))
            move_to $row $(( sx+3 ))
            printf "${DIM}Config : %s${RST}" "$conf"
            row=$(( row+1 ))
            draw_hline $row $sx $w
            row=$(( row+1 ))

            menu_row=$row
            menu_col=$(( sx+2 ))
            menu_w=$(( w-4 ))

            move_to $(( sy+h+1 )) $sx
            printf "${DIM}  ↑↓ Navigate  Enter Select  r Refresh  q Back${RST}"

            need_full=0

            # arrow_menu draws all rows and enters input loop
            arrow_menu sel "$menu_row" "$menu_col" "$menu_w"
        else
            # Re-enter input loop without full redraw
            arrow_menu sel "$menu_row" "$menu_col" "$menu_w"
        fi

        # Handle MENU_ACTION
        case "$MENU_ACTION" in
            QUIT|ESC) return ;;
            REFRESH)  need_full=1; continue ;;
            ENTER)
                case $sel in
                    0) sudo systemctl start   "$svc" 2>/dev/null; need_full=1 ;;
                    1) sudo systemctl stop    "$svc" 2>/dev/null; need_full=1 ;;
                    2) sudo systemctl restart "$svc" 2>/dev/null; need_full=1 ;;
                    3) sudo systemctl enable  "$svc" 2>/dev/null; need_full=1 ;;
                    4) sudo systemctl disable "$svc" 2>/dev/null; need_full=1 ;;
                    5) # Status
                       clear_screen; show_cursor
                       echo -e "${BOLD}${CYAN}  Status: ${inst}${RST}\n"
                       if ! systemctl is-enabled --quiet "$svc" 2>/dev/null; then
                           echo -e "  ${YELLOW}⚠ This instance is not enabled at boot.${RST}"
                           echo -e "  ${DIM}  Use 'Enable (boot)' to start it automatically on reboot.${RST}\n"
                       fi
                       systemctl status "$svc" --no-pager -l 2>/dev/null || echo "  Service not found."
                       echo ""; read -rp "  Press Enter..."
                       hide_cursor; need_full=1 ;;
                    6) # View Logs
                       clear_screen; show_cursor
                       echo -e "${BOLD}${CYAN}  Logs: ${inst}${RST}  ${DIM}(last 50)${RST}\n"
                       journalctl -u "$svc" -n 50 --no-pager -o short-iso 2>/dev/null || echo "  No logs."
                       echo ""; read -rp "  Press Enter..."
                       hide_cursor; need_full=1 ;;
                    7) # Clear Logs
                       clear_screen; show_cursor
                       echo -e "\n  ${BOLD}${CYAN}Clear Logs: ${inst}${RST}\n"
                       local log_size
                       log_size=$(journalctl -u "$svc" --disk-usage 2>/dev/null || echo "  unknown")
                       echo -e "  Current log usage: ${YELLOW}${log_size}${RST}\n"
                       echo -e "  ${BOLD}Options:${RST}"
                       echo "    1) Clear all logs for this instance"
                       echo "    2) Keep last 1 day"
                       echo "    3) Keep last 7 days"
                       echo "    4) Cancel"
                       read -rp "  Choice [4]: " lc; lc=${lc:-4}
                       case "$lc" in
                           1) echo -e "\n  ${CYAN}Rotating & vacuuming...${RST}"
                              sudo journalctl --rotate 2>/dev/null || true
                              sudo journalctl --vacuum-time=1s -u "$svc" 2>/dev/null \
                                  || sudo journalctl --vacuum-time=1s 2>/dev/null || true
                              echo -e "  ${GREEN}✔ Logs cleared.${RST}" ;;
                           2) echo -e "\n  ${CYAN}Clearing logs older than 1 day...${RST}"
                              sudo journalctl --rotate 2>/dev/null || true
                              sudo journalctl --vacuum-time=1d 2>/dev/null || true
                              echo -e "  ${GREEN}✔ Done.${RST}" ;;
                           3) echo -e "\n  ${CYAN}Clearing logs older than 7 days...${RST}"
                              sudo journalctl --rotate 2>/dev/null || true
                              sudo journalctl --vacuum-time=7d 2>/dev/null || true
                              echo -e "  ${GREEN}✔ Done.${RST}" ;;
                           *) echo -e "  ${YELLOW}Cancelled.${RST}" ;;
                       esac
                       echo -e "\n  ${DIM}Note: vacuum-time applies to all journal entries system-wide.${RST}"
                       echo -e "  ${DIM}journalctl does not support per-unit cleanup.${RST}"
                       read -rp "  Press Enter..."
                       hide_cursor; need_full=1 ;;
                    8) # Edit Config
                       show_cursor
                       local editor="${EDITOR:-vim}"
                       command -v "$editor" &>/dev/null || editor="vi"
                       sudo "$editor" "$conf"
                       hide_cursor; need_full=1 ;;
                    9) # View Config
                       clear_screen; show_cursor
                       echo -e "${BOLD}${CYAN}  ${conf}${RST}\n"
                       [[ -f "$conf" ]] && sudo cat -n "$conf" || echo "  Not found."
                       echo ""; read -rp "  Press Enter..."
                       hide_cursor; need_full=1 ;;
                    10) # Rename
                       clear_screen; show_cursor
                       echo -e "\n  ${BOLD}${CYAN}Rename Instance: ${inst}${RST}\n"
                       read -rp "  New name: " new_name
                       new_name=$(echo "$new_name" | tr -cd 'a-zA-Z0-9_-')
                       if [[ -z "$new_name" ]]; then
                           echo -e "  ${YELLOW}Cancelled.${RST}"; sleep 1
                           hide_cursor; need_full=1; continue
                       fi
                       if [[ "$new_name" == "$inst" ]]; then
                           echo -e "  ${YELLOW}Same name, nothing to do.${RST}"; sleep 1
                           hide_cursor; need_full=1; continue
                       fi
                       local new_conf="${FRPS_CONFIG_DIR}/${new_name}.toml"
                       if [[ -f "$new_conf" ]] || [[ -f "${FRPS_CONFIG_DIR}/${new_name}.ini" ]]; then
                           echo -e "  ${RED}✖ '${new_name}' already exists.${RST}"
                           read -rp "  Press Enter..."; hide_cursor; need_full=1; continue
                       fi
                       # Stop & disable old service
                       local was_running=0 was_enabled=0
                       systemctl is-active --quiet "$svc" 2>/dev/null && was_running=1
                       systemctl is-enabled --quiet "$svc" 2>/dev/null && was_enabled=1
                       sudo systemctl disable --now "$svc" 2>/dev/null || true
                       # Rename config file
                       sudo mv -f "$conf" "$new_conf"
                       echo -e "  ${DIM}${conf} → ${new_conf}${RST}"
                       # Re-enable & start new service
                       local new_svc; new_svc=$(svc_name "$new_name")
                       if [[ $was_enabled -eq 1 ]]; then
                           sudo systemctl enable "$new_svc" 2>/dev/null || true
                       fi
                       if [[ $was_running -eq 1 ]]; then
                           sudo systemctl start "$new_svc" 2>/dev/null || true
                       fi
                       echo -e "\n  ${GREEN}✔ Renamed '${inst}' → '${new_name}'${RST}"
                       sleep 1; return ;;  # return to main menu to refresh instance list
                    11) # Remove
                       clear_screen; show_cursor
                       echo -e "\n  ${RED}${BOLD}⚠ Remove '${inst}'?${RST}"
                       echo -e "  ${DIM}Stops service, deletes config.${RST}"
                       read -rp "  Type 'yes': " confirm
                       if [[ "$confirm" == "yes" ]]; then
                           sudo systemctl disable --now "$svc" 2>/dev/null || true
                           sudo rm -f "${FRPS_CONFIG_DIR}/${inst}.toml" "${FRPS_CONFIG_DIR}/${inst}.ini"
                           echo -e "  ${GREEN}✔ Removed.${RST}"; sleep 1; return
                       fi
                       echo -e "  ${YELLOW}Cancelled.${RST}"; sleep 1
                       hide_cursor; need_full=1 ;;
                    12) return ;;
                esac
                ;;
        esac
    done
}

# ═══════════════════════════════════════════════════════════════
#  Main menu
# ═══════════════════════════════════════════════════════════════
main_menu() {
    local sel=0
    local need_full=1
    local instances=()
    local items=()
    local types=()
    local menu_row=0 menu_col=0 menu_w=0

    while true; do
        if [[ $need_full -eq 1 ]]; then
            clear_screen
            hide_cursor

            # ── Gather instances (slow calls, only here) ──
            instances=()
            while IFS= read -r line; do
                [[ -n "$line" ]] && instances+=("$line")
            done < <(get_instances)

            # ── Build rows ──
            items=(); types=()
            items+=("─── Instances ───"); types+=("hdr")

            if [[ ${#instances[@]} -gt 0 ]]; then
                local inst st si pi pid rss etime conf bport label
                for inst in "${instances[@]}"; do
                    st=$(get_status "$inst")
                    si=$(status_icon "$st")
                    conf="${FRPS_CONFIG_DIR}/${inst}.toml"
                    [[ -f "$conf" ]] || conf="${FRPS_CONFIG_DIR}/${inst}.ini"
                    bport=$(get_bind_port "$conf")
                    if [[ "$st" == "running" ]]; then
                        pi=$(get_proc_info "$inst")
                        pid=$(echo "$pi"|awk '{print $1}')
                        rss=$(echo "$pi"|awk '{print $2}')
                        etime=$(echo "$pi"|awk '{print $4}')
                        label=$(printf "%s %-14s :%-5s PID:%-7s MEM:%-6s UP:%s" "$si" "$inst" "$bport" "$pid" "$rss" "$etime")
                    else
                        label=$(printf "%s %-14s :%-5s (%s)" "$si" "$inst" "$bport" "$st")
                    fi
                    items+=("$label"); types+=("inst")
                done
            else
                items+=("  (no instances)"); types+=("hdr")
            fi

            items+=(""); types+=("hdr")
            items+=("─── Actions ───"); types+=("hdr")
            items+=("  ⊕  Add New Instance");     types+=("act_add")
            items+=("  ⤓  Install / Update frps"); types+=("act_install")
            items+=("  ⊘  Uninstall frps");         types+=("act_uninstall")
            items+=(""); types+=("hdr")
            items+=("  ✕  Quit"); types+=("act_quit")

            local total=${#items[@]}
            if [[ $sel -ge $total ]]; then sel=$(( total-1 )); fi
            if [[ $sel -lt 0 ]]; then sel=0; fi

            # ── Banner ──
            move_to 1 3; printf "${BOLD}${CYAN}  ╔═╗╦═╗╔═╗╔═╗  ╔╦╗┌─┐┌┐┌┌─┐┌─┐┌─┐┬─┐${RST}"
            move_to 2 3; printf "${BOLD}${CYAN}  ╠╣ ╠╦╝╠═╝╚═╗  ║║║├─┤│││├─┤│ ┬├┤ ├┬┘${RST}"
            move_to 3 3; printf "${BOLD}${CYAN}  ╚  ╩╚═╩  ╚═╝  ╩ ╩┴ ┴┘└┘┴ ┴└─┘└─┘┴└─${RST}"

            local ver; ver=$(frps_version)
            local vc="${GREEN}"; is_installed || vc="${RED}"
            move_to 4 5
            printf "${DIM}frps: ${vc}%s${RST}    ${DIM}instances: %d${RST}" "$ver" "${#instances[@]}"

            local sy=5 sx=3 w=64 h=$(( total+2 ))
            draw_box $sy $sx $w $h ""

            menu_row=$(( sy+1 ))
            menu_col=$(( sx+2 ))
            menu_w=$(( w-4 ))

            move_to $(( sy+h+1 )) $sx
            printf "${DIM}  ↑↓ Navigate   Enter Select   r Refresh   q Quit${RST}"

            need_full=0

            # arrow_menu draws rows + enters input loop
            arrow_menu sel "$menu_row" "$menu_col" "$menu_w"
        else
            # Re-enter without full redraw
            arrow_menu sel "$menu_row" "$menu_col" "$menu_w"
        fi

        case "$MENU_ACTION" in
            QUIT|ESC) clear_screen; show_cursor; exit 0 ;;
            REFRESH)  need_full=1; continue ;;
            ENTER)
                case "${types[$sel]}" in
                    inst)
                        local idx=0 j
                        for (( j=0; j<sel; j++ )); do
                            [[ "${types[$j]}" == "inst" ]] && idx=$(( idx+1 ))
                        done
                        instance_menu "${instances[$idx]}"
                        need_full=1; sel=0
                        ;;
                    act_add)     do_add_instance; need_full=1 ;;
                    act_install) do_install;       need_full=1 ;;
                    act_uninstall)
                        clear_screen; show_cursor
                        echo -e "\n  ${RED}${BOLD}⚠ Uninstall frps?${RST}"
                        echo -e "  ${DIM}Removes binary only. Configs in ${FRPS_CONFIG_DIR}/ are kept.${RST}"
                        read -rp "  Type 'yes': " confirm
                        if [[ "$confirm" == "yes" ]]; then
                            local i2; for i2 in $(get_instances); do sudo systemctl stop "$(svc_name "$i2")" 2>/dev/null || true; done
                            sudo rm -f "$FRPS_BIN"; echo -e "  ${GREEN}✔ Removed.${RST}"
                        else echo -e "  ${YELLOW}Cancelled.${RST}"; fi
                        sleep 1; hide_cursor; need_full=1
                        ;;
                    act_quit)
                        clear_screen; show_cursor; exit 0 ;;
                esac
                ;;
        esac
    done
}

# ═══════════════════════════════════════════════════════════════
#  CLI
# ═══════════════════════════════════════════════════════════════
cli_usage() {
    cat <<EOF
Usage: $(basename "$0") [command] [instance]

  (no args)              Interactive TUI
  list                   List instances + bind port + process info
  start   <name>         Start instance
  stop    <name>         Stop instance
  restart <name>         Restart instance
  enable  <name>         Enable at boot
  disable <name>         Disable at boot
  status  <name>         Show status
  logs    <name>         Tail logs (-f)
  install                Install/update frps
  add                    Add instance

Paths:
  Binary:  ${FRPS_BIN}
  Configs: ${FRPS_CONFIG_DIR}/
  Service: ${FRPS_SERVICE_PREFIX}@<name>.service
EOF
}

cli_list() {
    printf "${BOLD}%-16s %-10s %-6s %-7s %-8s %-8s %-6s %-12s${RST}\n" \
        "INSTANCE" "STATUS" "BOOT" "PORT" "PID" "MEM" "CPU" "UPTIME"
    printf "%-16s %-10s %-6s %-7s %-8s %-8s %-6s %-12s\n" \
        "────────" "──────" "────" "────" "───" "───" "───" "──────"
    local inst
    for inst in $(get_instances); do
        local st; st=$(get_status "$inst")
        local svc; svc=$(svc_name "$inst")
        local boot="no"; systemctl is-enabled --quiet "$svc" 2>/dev/null && boot="yes"
        local conf="${FRPS_CONFIG_DIR}/${inst}.toml"
        [[ -f "$conf" ]] || conf="${FRPS_CONFIG_DIR}/${inst}.ini"
        local bport; bport=$(get_bind_port "$conf")
        local pi pid rss cpu etime
        pi=$(get_proc_info "$inst")
        pid=$(echo "$pi"|awk '{print $1}'); rss=$(echo "$pi"|awk '{print $2}')
        cpu=$(echo "$pi"|awk '{print $3}'); etime=$(echo "$pi"|awk '{print $4}')
        printf "%-16s $(status_color "$st")%-10s${RST} %-6s %-7s %-8s %-8s %-6s %-12s\n" \
            "$inst" "$st" "$boot" "$bport" "$pid" "$rss" "$cpu" "$etime"
    done
}

main() {
    if [[ $# -eq 0 ]]; then main_menu; exit 0; fi
    local cmd="${1:-}" instance="${2:-}"
    case "$cmd" in
        list|ls) cli_list ;;
        start|stop|restart)
            [[ -z "$instance" ]] && { echo "Usage: $0 $cmd <name>"; exit 1; }
            sudo systemctl "$cmd" "$(svc_name "$instance")"; echo -e "${GREEN}✔ ${cmd} ${instance}${RST}" ;;
        enable|disable)
            [[ -z "$instance" ]] && { echo "Usage: $0 $cmd <name>"; exit 1; }
            sudo systemctl "$cmd" "$(svc_name "$instance")"; echo -e "${GREEN}✔ ${cmd} ${instance}${RST}" ;;
        status)
            [[ -z "$instance" ]] && { echo "Usage: $0 $cmd <name>"; exit 1; }
            systemctl status "$(svc_name "$instance")" --no-pager ;;
        logs|log)
            [[ -z "$instance" ]] && { echo "Usage: $0 $cmd <name>"; exit 1; }
            journalctl -u "$(svc_name "$instance")" -n 100 --no-pager -f ;;
        install) do_install ;;
        add) do_add_instance ;;
        -h|--help|help) cli_usage ;;
        *) echo -e "${RED}Unknown: ${cmd}${RST}"; cli_usage; exit 1 ;;
    esac
}

main "$@"
