#!/bin/sh
# =============================================================================
# ftps-cli — terminal (ncurses) front-end for the ftpsuite. Build a queue of
# transfers across ANY sites and ANY pairs, in BOTH directions, then run them
# all in one go. Uses the same shared config + library as ftpush / ftpull.
#
# Uses `dialog` (or `whiptail` as a fallback) for the UI — no Python. Every
# transfer ultimately runs through the same lftp logic the CLI tools use.
#
# Usage: ftps-cli            (just launch it; everything is menu-driven)
#        ftps-cli --help | --version
# =============================================================================

_self_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
for _cand in "${FTPS_LIB:-}" "$_self_dir/ftps_common.sh" \
             "$HOME/.local/lib/ftpsuite/ftps_common.sh" \
             /usr/local/lib/ftpsuite/ftps_common.sh; do
    [ -n "$_cand" ] && [ -f "$_cand" ] && { . "$_cand"; _lib_ok=1; break; }
done
[ "${_lib_ok:-}" = 1 ] || { echo "ftps-cli: cannot find ftps_common.sh (set FTPS_LIB)" >&2; exit 2; }

case "${1:-}" in
    --version|-v) echo "ftps-cli (ftpsuite) $FTPS_VERSION"; exit 0 ;;
    --help|-h) sed -n '3,13p' "$0" | grep -v '^# =====' | sed 's/^# \{0,1\}//'; exit 0 ;;
esac

# ── UI backend: dialog preferred, whiptail fallback ─────────────────────────
if command -v dialog >/dev/null 2>&1; then UI=dialog
elif command -v whiptail >/dev/null 2>&1; then UI=whiptail
else echo "ftps-cli needs 'dialog' or 'whiptail' installed." >&2; exit 2; fi

BACKTITLE="ftpsuite — terminal front-end"
QUEUE="${TMPDIR:-/tmp}/ftps_cli_queue.$$"   # lines: site|dir|local|remote|kind
: > "$QUEUE"

# ── Theme (dark default) — daltonic-safe dialog color schemes ────────────────
# Uses only WHITE / BLACK / CYAN / YELLOW / BLUE — never red or green as the
# sole cue, so it stays legible with red-green color blindness. In particular
# the menu HOTKEY letters (dialog's default is red, the worst case) are recast
# as bright yellow / high-contrast reverse. Themes are dialog .dialogrc files
# selected via $DIALOGRC; whiptail (fallback) ignores them.
RC_DARK="${TMPDIR:-/tmp}/ftps_rc_dark.$$"
RC_LIGHT="${TMPDIR:-/tmp}/ftps_rc_light.$$"
THEME_FILE="$CONFIG_DIR/ftps-cli.theme"
trap 'rm -f "$QUEUE" "$RC_DARK" "$RC_LIGHT" "${TMPDIR:-/tmp}/.ftpscli.$$" 2>/dev/null; clear 2>/dev/null' EXIT INT TERM

write_rc_dark() {
    cat > "$RC_DARK" <<'RC'
use_colors = ON
use_shadow = OFF
screen_color = (CYAN,BLACK,ON)
shadow_color = (BLACK,BLACK,OFF)
dialog_color = (WHITE,BLACK,OFF)
title_color = (YELLOW,BLACK,ON)
border_color = (CYAN,BLACK,ON)
button_active_color = (BLACK,CYAN,ON)
button_inactive_color = (WHITE,BLACK,OFF)
button_key_active_color = (BLACK,CYAN,ON)
button_key_inactive_color = (YELLOW,BLACK,ON)
button_label_active_color = (BLACK,CYAN,ON)
button_label_inactive_color = (WHITE,BLACK,OFF)
menubox_color = (WHITE,BLACK,OFF)
menubox_border_color = (CYAN,BLACK,ON)
item_color = (WHITE,BLACK,OFF)
item_selected_color = (BLACK,CYAN,ON)
tag_color = (YELLOW,BLACK,ON)
tag_selected_color = (BLACK,CYAN,ON)
tag_key_color = (YELLOW,BLACK,ON)
tag_key_selected_color = (BLACK,CYAN,ON)
check_color = (WHITE,BLACK,OFF)
check_selected_color = (BLACK,CYAN,ON)
inputbox_color = (WHITE,BLACK,OFF)
inputbox_border_color = (CYAN,BLACK,ON)
searchbox_color = (WHITE,BLACK,OFF)
position_indicator_color = (YELLOW,BLACK,ON)
uarrow_color = (YELLOW,BLACK,ON)
darrow_color = (YELLOW,BLACK,ON)
RC
}
write_rc_light() {
    cat > "$RC_LIGHT" <<'RC'
use_colors = ON
use_shadow = OFF
screen_color = (BLUE,WHITE,ON)
shadow_color = (BLACK,WHITE,OFF)
dialog_color = (BLACK,WHITE,OFF)
title_color = (BLUE,WHITE,ON)
border_color = (BLUE,WHITE,ON)
button_active_color = (WHITE,BLUE,ON)
button_inactive_color = (BLACK,WHITE,OFF)
button_key_active_color = (WHITE,BLUE,ON)
button_key_inactive_color = (BLUE,WHITE,ON)
button_label_active_color = (WHITE,BLUE,ON)
button_label_inactive_color = (BLACK,WHITE,OFF)
menubox_color = (BLACK,WHITE,OFF)
menubox_border_color = (BLUE,WHITE,ON)
item_color = (BLACK,WHITE,OFF)
item_selected_color = (WHITE,BLUE,ON)
tag_color = (BLUE,WHITE,ON)
tag_selected_color = (WHITE,BLUE,ON)
tag_key_color = (BLUE,WHITE,ON)
tag_key_selected_color = (WHITE,BLUE,ON)
check_color = (BLACK,WHITE,OFF)
check_selected_color = (WHITE,BLUE,ON)
inputbox_color = (BLACK,WHITE,OFF)
inputbox_border_color = (BLUE,WHITE,ON)
searchbox_color = (BLACK,WHITE,OFF)
position_indicator_color = (BLUE,WHITE,ON)
uarrow_color = (BLUE,WHITE,ON)
darrow_color = (BLUE,WHITE,ON)
RC
}
apply_theme() {  # uses $THEME
    case "$THEME" in
        light) DIALOGRC="$RC_LIGHT" ;;
        *)     DIALOGRC="$RC_DARK"  ;;
    esac
    export DIALOGRC
}
toggle_theme() {
    THEME=$([ "$THEME" = dark ] && echo light || echo dark)
    apply_theme
    [ -d "$CONFIG_DIR" ] && printf '%s\n' "$THEME" > "$THEME_FILE" 2>/dev/null
}
# load persisted choice; default dark
THEME=dark
[ -r "$THEME_FILE" ] && case "$(cat "$THEME_FILE" 2>/dev/null)" in
    light) THEME=light ;;
esac
write_rc_dark; write_rc_light; apply_theme

# dialog/whiptail wrappers. Widgets that take a dynamic list of menu/checklist
# entries receive it as a single shell-quoted string ($3) which we re-parse
# with `eval set --` so entries containing spaces stay intact — passing the
# string unquoted would word-split on those spaces (the classic dialog-in-sh
# bug). The selection comes back on the widget's stderr, captured to stdout.
# dialog/whiptail draw their ncurses UI to STDOUT and write the RESULT to
# STDERR. Callers use `x=$(d_menu ...)`, which makes stdout a pipe — so the UI
# would render into the pipe (invisible) and the screen stays blank. Force the
# UI onto the real terminal (/dev/tty) and capture only the result.
_capture() {
    _out="${TMPDIR:-/tmp}/.ftpscli.$$"
    "$@" 2>"$_out" >/dev/tty
    _rc=$?
    cat "$_out"; rm -f "$_out"
    return $_rc
}

d_menu() {  # $1 title  $2 text  $3 quoted "tag item tag item ..."
    _t="$1"; _m="$2"; eval "set -- $3"
    _capture $UI --backtitle "$BACKTITLE" --title "$_t" --menu "$_m" 20 76 12 "$@"
}
d_check() { # $1 title  $2 text  $3 quoted "tag item status ..."; returns one tag/line
    _t="$1"; _m="$2"; eval "set -- $3"
    _capture $UI --backtitle "$BACKTITLE" --separate-output --title "$_t" --checklist "$_m" 22 78 14 "$@"
}
d_fselect()   { _capture $UI --backtitle "$BACKTITLE" --title "$1" --fselect "$2" 16 76; }
d_input()     { _capture $UI --backtitle "$BACKTITLE" --title "$1" --inputbox "$2" 10 70 "$3"; }
d_msg()       { $UI --backtitle "$BACKTITLE" --title "$1" --msgbox "$2" 18 76; }
d_yesno()     { $UI --backtitle "$BACKTITLE" --title "$1" --yesno "$2" 12 70; }

require_config
[ -f "$CONFIG_FILE" ] || { d_msg "No config" "No config found. Create a site first with:  ftpush --add-site"; exit 1; }
load_all_sites
[ -z "$ALL_SITES" ] && { d_msg "No sites" "No sites configured. Add one with:  ftpush --add-site"; exit 1; }

# ── helpers ─────────────────────────────────────────────────────────────────
site_menu_items() {  # emits: 1 "name (default)" 2 "name" ...
    n=0; for s in $ALL_SITES; do n=$((n+1))
        if [ "$s" = "$DEFAULT_SITE" ]; then printf '%s "%s (default)" ' "$n" "$s"; else printf '%s "%s" ' "$n" "$s"; fi
    done
}
pair_menu_items() {  # $1=site — emits: 1 "local -> remote" ... ; sets PAIRS
    eval "PAIRS=\$SITE_${1}_pairs"
    echo "$PAIRS" | sed '/^$/d' | awk -F'|' '{ printf "%d \"%s -> %s\" ", NR, $1, $2 }'
}
queue_count() { c=$(grep -cv '^$' "$QUEUE" 2>/dev/null); printf '%s' "${c:-0}"; }

queue_add() { # site|dir|local|remote|kind
    printf '%s|%s|%s|%s|%s\n' "$1" "$2" "$3" "$4" "$5" >> "$QUEUE"
}

# Shell-quote one string into a token safe for `eval set --` (handles spaces,
# quotes, $, backtick, backslash in remote filenames).
q() { printf '"%s"' "$(printf '%s' "$1" | sed 's/[\\"$`]/\\&/g')"; }

# ── remote browser (for pull) — navigate + checklist-select ─────────────────
# $1=site  $2=start remote dir. Adds selected entries to the queue (pull).
# PAIRS must be set to the site's pairs (resolve_local uses it).
remote_browse() {
    _site="$1"; _cur="$2"
    while :; do
        # cls -1F: one name per line, dirs suffixed '/'. Drop lftp chatter.
        listing=$(ftps_run_lftp "$_site" "cls -1F \"$_cur\"; " 2>/dev/null \
                  | grep -vE 'verify-certificate|^bye$' | sed '/^$/d')

        # Build a navigation menu: UP, each dir (to descend), plus a "select
        # here" action. Files aren't in this menu — they're chosen via the
        # checklist under "* select files/dirs here".
        menu='UP "..  (up one level)" PICK "*  select files/dirs in this folder"'
        oldifs=$IFS; IFS='
'
        for e in $listing; do
            case "$e" in
                */) nm="${e%/}"; menu="$menu $(q "DIR:$nm") $(q "[dir]  enter $nm")" ;;
            esac
        done
        IFS=$oldifs

        act=$(d_menu "Remote: $_cur" "Descend into a folder, or pick items here:" "$menu") || return 0
        case "$act" in
            "") return 0 ;;
            UP)   _cur=$(dirname "${_cur%/}"); [ -z "$_cur" ] && _cur=/ ;;
            DIR:*) _cur="${_cur%/}/${act#DIR:}" ;;
            PICK)
                # checklist of EVERYTHING in the current dir (files + dirs)
                chk=""
                oldifs=$IFS; IFS='
'
                for e in $listing; do
                    case "$e" in
                        */) nm="${e%/}"; chk="$chk $(q "d:$nm") $(q "[dir] $nm") off" ;;
                        *)  chk="$chk $(q "f:$e") $(q "$e") off" ;;
                    esac
                done
                IFS=$oldifs
                [ -z "$chk" ] && { d_msg "Empty" "Nothing to select in $_cur."; continue; }
                sel=$(d_check "Select in $_cur" "SPACE toggles, ENTER queues the checked items" "$chk") || continue
                [ -z "$sel" ] && continue
                added=0
                # --separate-output: one selected tag per line (safe for spaces)
                printf '%s\n' "$sel" | while IFS= read -r tok; do
                    [ -z "$tok" ] && continue
                    case "$tok" in
                        d:*) name="${tok#d:}"; kind=dir ;;
                        f:*) name="${tok#f:}"; kind=file ;;
                        *)   continue ;;
                    esac
                    rp="${_cur%/}/$name"
                    lp=$(resolve_local "$rp") || lp=""
                    [ -z "$lp" ] && continue
                    queue_add "$_site" pull "$lp" "$rp" "$kind"
                    added=$((added+1))
                done
                d_msg "Added" "Queued the checked item(s) from $_cur."
                ;;
        esac
    done
}

# ── flows ────────────────────────────────────────────────────────────────────
flow_push() {
    sn=$(d_menu "Push — choose site" "Upload to which site?" "$(site_menu_items)") || return 0
    [ -z "$sn" ] && return 0
    site=$(site_name_at "$sn"); eval "PAIRS=\$SITE_${site}_pairs"
    while :; do
        path=$(d_fselect "Push from '$site' — pick a file or folder" "$HOME/") || return 0
        [ -z "$path" ] && return 0
        [ -e "$path" ] || { d_msg "Not found" "No such path:\n$path"; continue; }
        rp=$(resolve_remote "$path") || rp=""
        if [ -z "$rp" ]; then
            d_msg "No matching pair" "'$path'\ndoesn't fall under any of site '$site' local pair roots.\nAdd/adjust a pair, or use ftpush -p N."
            continue
        fi
        [ -d "$path" ] && kind=dir || kind=file
        queue_add "$site" push "$path" "$rp" "$kind"
        d_yesno "Added" "Queued:\n$path\n  -> $rp\n\nAdd another from '$site'?" || return 0
    done
}

flow_pull() {
    sn=$(d_menu "Pull — choose site" "Download from which site?" "$(site_menu_items)") || return 0
    [ -z "$sn" ] && return 0
    site=$(site_name_at "$sn"); eval "SITE_PAIRS_CUR=\$SITE_${site}_pairs; PAIRS=\$SITE_${site}_pairs"
    pn=$(d_menu "Pull — choose pair" "Browse which pair's remote root?" "$(pair_menu_items "$site")") || return 0
    [ -z "$pn" ] && return 0
    start=$(echo "$PAIRS" | sed '/^$/d' | awk -v n="$pn" 'NR==n' | sed 's/.*|//')
    remote_browse "$site" "$start"
}

flow_review() {
    n=$(queue_count)
    [ "$n" -eq 0 ] && { d_msg "Queue empty" "Nothing queued yet. Add push/pull transfers first."; return 0; }
    body=$(awk -F'|' '{ printf "%s  [%s]  %s  ->  %s\n", NR, $2, ($2=="push"?$3:$4), ($2=="push"?$4:$3) }' "$QUEUE")
    d_msg "Queue ($n item(s))" "$body"
}

flow_run() {
    n=$(queue_count)
    [ "$n" -eq 0 ] && { d_msg "Queue empty" "Nothing to run."; return 0; }
    d_yesno "Run queue" "Execute all $n queued transfer(s) now?" || return 0
    clear
    echo "Running $n transfer(s)..."; echo
    rc_all=0
    # group by site|dir so each site+direction is one lftp session
    groups=$(awk -F'|' '{print $1"|"$2}' "$QUEUE" | sort -u)
    oldifs=$IFS; IFS='
'
    for g in $groups; do
        gsite="${g%%|*}"; gdir="${g##*|}"
        eval "_H=\$SITE_${gsite}_host"
        echo "== $gdir @ $gsite =="
        CMDS=""
        while IFS='|' read -r s d local remote kind; do
            [ "$s" = "$gsite" ] && [ "$d" = "$gdir" ] || continue
            if [ "$gdir" = push ]; then
                rd=$(dirname "$remote")
                if [ "$kind" = dir ]; then CMDS="${CMDS}mkdir -p \"$remote\" 2>/dev/null; mirror -R --verbose \"$local\" \"$remote\"; "
                else CMDS="${CMDS}mkdir -p \"$rd\" 2>/dev/null; put -O \"$rd\" \"$local\" -o \"$(basename "$remote")\"; "; fi
                echo "  push $local -> $remote"
            else
                if [ "$kind" = dir ]; then mkdir -p "$local"; CMDS="${CMDS}mirror --verbose \"$remote\" \"$local\"; "
                else ld=$(dirname "$local"); mkdir -p "$ld"; CMDS="${CMDS}get \"$remote\" -o \"$local\"; "; fi
                echo "  pull $remote -> $local"
            fi
        done < "$QUEUE"
        ftps_run_lftp "$gsite" "$CMDS" 2>&1 | ftps_filter_mkdir_noise || rc_all=1
    done
    IFS=$oldifs
    : > "$QUEUE"
    echo; echo "Queue finished (cleared)."
    echo "Press ENTER to return to the menu."; read -r _
}

# ── main menu ────────────────────────────────────────────────────────────────
while :; do
    qn=$(queue_count)
    choice=$(d_menu "ftpsuite" "Build a batch of transfers, then run them all.  Queue: $qn item(s)" \
        "push \"Add upload(s) — pick local files/folders\" \
         pull \"Add download(s) — browse a site's remote\" \
         review \"Review the queue ($qn)\" \
         run \"Run the queue now ($qn)\" \
         theme \"Switch color theme (now: $THEME)\" \
         quit \"Quit\"") || break
    case "$choice" in
        push)   flow_push ;;
        pull)   flow_pull ;;
        review) flow_review ;;
        run)    flow_run ;;
        theme)  toggle_theme ;;
        quit|"") break ;;
    esac
done
clear 2>/dev/null || true
