#!/bin/sh
# =============================================================================
# ftpull — download files/folders from a remote site (ftpsuite). The mirror
# image of ftpush: same sites, same pairs, same config, opposite direction.
#
# Usage:
#   ftpull [-s N] [-p N] REMOTE [REMOTE...]  download remote path(s) from site N
#                                            (default if -s omitted). A relative
#                                            REMOTE is a path INSIDE the mirror —
#                                            pair 1's remote root, or -p N — and
#                                            lands in the same subtree locally.
#                                            Run from ANY directory. An ABSOLUTE
#                                            remote path matches a pair by prefix.
#   ftpull --mirror [-s N] [-p N]            mirror a whole pair down (remote
#                                            root -> local root). With -p N,
#                                            just that pair; without, every
#                                            pair on the site.
#   ftpull --sites | --add-site | --edit-site [-s N] | --del-site [-s N]
#   ftpull --pairs [-s N] | --add-pair [-s N] | --del-pair [-s N] [-p N]
#          | --edit-pair [-s N] [-p N]
#   ftpull --export [FILE] | --import FILE
#   ftpull --version | -v | --help | -h
#
# REMOTE paths are absolute server paths (as shown by --pairs' right side).
# Config is shared with the whole suite; managing it here == managing it from
# ftpush.
# =============================================================================

_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 "ftpull: cannot find ftps_common.sh (set FTPS_LIB)" >&2; exit 2; }

set -eu

usage() { sed -n '3,23p' "$0" | grep -v '^# =====' | sed 's/^# \{0,1\}//'; }

# ── --mirror: pull an entire pair (or all pairs) remote -> local ────────────
cmd_mirror() {
    site_num="$1"; pair_num="$2"
    require_config; load_all_sites; pick_site "$site_num"
    eval "PAIRS=\$SITE_${site}_pairs; _H=\$SITE_${site}_host; _U=\$SITE_${site}_user"
    if [ -z "$_H" ] || [ -z "$_U" ]; then echo "Site '$site' has no host/user — run ftpull --edit-site." >&2; exit 1; fi
    pc=$(pairs_count "$PAIRS")
    [ "$pc" -eq 0 ] && { echo "Site '$site' has no pairs." >&2; exit 1; }

    if [ -n "$pair_num" ]; then
        case "$pair_num" in ''|*[!0-9]*) echo "Not a valid pair number: $pair_num" >&2; exit 1 ;; esac
        { [ "$pair_num" -lt 1 ] || [ "$pair_num" -gt "$pc" ]; } && { echo "Pair -p $pair_num out of range (have $pc)" >&2; exit 1; }
        SELECTED=$(echo "$PAIRS" | sed '/^$/d' | awk -v n="$pair_num" 'NR==n')
    else
        SELECTED=$(echo "$PAIRS" | sed '/^$/d')
    fi

    echo; echo "Connecting to $site (mirror down)..."
    CMDS=""
    while IFS='|' read -r lp rp; do
        [ -z "$lp" ] && continue
        echo "Mirror:  $rp -> $lp"
        # local target must exist for mirror to write into it
        mkdir -p "$lp"
        CMDS="${CMDS}mirror --verbose \"$rp\" \"$lp\"; "
    done <<EOF
$SELECTED
EOF
    out="${TMPDIR:-/tmp}/.ftpull_out.$$"
    ftps_run_lftp "$site" "$CMDS" > "$out" 2>&1; st=$?
    cat "$out"; rm -f "$out"
    [ "$st" -ne 0 ] && exit "$st"
    echo "Done."
}

# ── download by explicit remote path(s) ──────────────────────────────────────
cmd_download() {
    site_num="$1"; force_pair="$2"; shift 2
    require_config; load_all_sites; pick_site "$site_num"
    eval "PAIRS=\$SITE_${site}_pairs; _H=\$SITE_${site}_host; _U=\$SITE_${site}_user"
    if [ -z "$_H" ] || [ -z "$_U" ]; then echo "Site '$site' has no host/user — run ftpull --edit-site." >&2; exit 1; fi

    # With -p N, remote paths are taken RELATIVE TO THAT PAIR'S REMOTE ROOT (and
    # they land under the pair's local root, same subtree) — so you can run
    # ftpull from ANY directory. Without -p, a path resolves against the pairs
    # by prefix as before. Absolute remote paths always win as given.
    force_local_root=""; force_remote_root=""
    if [ -n "$force_pair" ]; then
        pc=$(pairs_count "$PAIRS")
        case "$force_pair" in ''|*[!0-9]*) echo "Not a valid pair number: $force_pair" >&2; exit 1 ;; esac
        { [ "$force_pair" -lt 1 ] || [ "$force_pair" -gt "$pc" ]; } && { echo "Pair -p $force_pair out of range (have $pc, see --pairs)" >&2; exit 1; }
        pair_line=$(echo "$PAIRS" | sed '/^$/d' | awk -v n="$force_pair" 'NR==n')
        force_local_root=$(printf '%s' "$pair_line"  | sed 's/|.*//; s:/*$::')
        force_remote_root=$(printf '%s' "$pair_line" | sed 's/.*|//; s:/*$::')
    fi
    if [ -n "${GP_LROOT:-}" ]; then force_local_root="$GP_LROOT"; force_remote_root="$GP_RROOT"; fi

    # Resolve each REMOTE path -> local: "remote|local" per line.
    # A RELATIVE remote path in pair-root mode is taken relative to the pair's
    # remote root and lands in the same subtree locally (run from anywhere). An
    # ABSOLUTE remote path is always matched to a pair by longest remote prefix.
    RESOLVED=""
    for rp_in in "$@"; do
        case "$rp_in" in
            /*)                                            # absolute remote → match a pair by prefix
                rp="$rp_in"; lp=$(resolve_local "$rp") || lp="" ;;
            *)                                             # relative → path inside the mirror (pair remote root)
                rp="$force_remote_root/$rp_in"
                lp="$force_local_root/$rp_in" ;;
        esac
        [ -z "$lp" ] && { echo "Skipping — no matching pair on site '$site' for remote: $rp" >&2; continue; }
        RESOLVED="${RESOLVED}
${rp}|${lp}"
    done
    [ -z "$(printf '%s' "$RESOLVED" | sed '/^$/d')" ] && { echo "Nothing to download." >&2; exit 1; }

    echo; echo "Connecting to $site..."

    # Pass 1: probe each remote path — is it a dir or a file? (one session)
    PROBE=""; idx=0
    while IFS='|' read -r rp lp; do
        [ -z "$rp" ] && continue
        idx=$((idx+1))
        PROBE="${PROBE}echo FT_P${idx}S; cls -dl \"$rp\" 2>&1; echo FT_P${idx}E; "
    done <<EOF
$RESOLVED
EOF
    probe_out=$(ftps_run_lftp "$site" "$PROBE" 2>&1) || true

    # Pass 2: build download commands using probe results.
    CMDS=""; idx=0; KINDS=""
    while IFS='|' read -r rp lp; do
        [ -z "$rp" ] && continue
        idx=$((idx+1))
        blk=$(printf '%s\n' "$probe_out" | sed -n "/FT_P${idx}S/,/FT_P${idx}E/p")
        if printf '%s' "$blk" | grep -qi 'no such file\|not found\|550'; then
            echo "Skipping — not on server: $rp" >&2
            KINDS="${KINDS}
${rp}|missing"
            continue
        fi
        # a leading 'd' in the cls -dl long listing = directory
        if printf '%s' "$blk" | grep -qE '^\s*d'; then kind=dir; else kind=file; fi
        KINDS="${KINDS}
${rp}|${kind}"
        if [ "$kind" = dir ]; then
            echo "Folder:  $rp -> $lp"
            mkdir -p "$lp"
            CMDS="${CMDS}mirror --verbose \"$rp\" \"$lp\"; "
        else
            echo "File:    $rp -> $lp"
            ld=$(dirname "$lp"); mkdir -p "$ld"
            CMDS="${CMDS}get \"$rp\" -o \"$lp\"; "
        fi
    done <<EOF
$RESOLVED
EOF
    [ -z "$CMDS" ] && { echo "Nothing to download." >&2; exit 1; }

    out="${TMPDIR:-/tmp}/.ftpull_out.$$"
    ftps_run_lftp "$site" "$CMDS" > "$out" 2>&1; st=$?
    cat "$out"; rm -f "$out"
    [ "$st" -ne 0 ] && exit "$st"

    # Summary: local name + (size if downloaded) + Fetched/Skipped.
    while IFS='|' read -r rp lp; do
        [ -z "$rp" ] && continue
        k=$(printf '%s\n' "$KINDS" | awk -F'|' -v r="$rp" '$1==r{print $2}')
        [ "$k" = missing ] && { echo "$(basename "$rp") — Skipped (not on server)"; continue; }
        if [ -e "$lp" ]; then
            if [ -f "$lp" ]; then sz=$(wc -c < "$lp" 2>/dev/null | tr -d ' '); echo "$(basename "$lp") — ${sz} bytes — Fetched";
            else echo "$(basename "$lp") — Mirrored"; fi
        else
            echo "$(basename "$lp") — (no local file after fetch?)"
        fi
    done <<EOF
$RESOLVED
EOF
    echo "Done."
}

# ── Arg parsing ──────────────────────────────────────────────────────────────
case "${1:-}" in
    --help|-h|"") usage; exit 0 ;;
    --version|-v) echo "ftpull (ftpsuite) $FTPS_VERSION"; exit 0 ;;
    --mirror)
        shift; parse_site_and_extra "$@"
        cmd_mirror "$opt_site" "$opt_extra"; exit 0 ;;
esac
ftps_dispatch_config "$@" || true

SITE=""; FORCE=""
while true; do
    case "${1:-}" in
        -s)   SITE="${2:-}"; shift 2 2>/dev/null || { echo "Usage: ftpull [-s N] [-p N] REMOTE..." >&2; exit 1; } ;;
        -s?*) SITE="${1#-s}"; shift ;;
        -p)   FORCE="${2:-}"; shift 2 2>/dev/null || { echo "Usage: ftpull [-s N] [-p N] REMOTE..." >&2; exit 1; } ;;
        -p?*) FORCE="${1#-p}"; shift ;;
        *) break ;;
    esac
done
# The mirror is the anchor. -p N is a FLAT number across ALL sites (ftpull
# --pairs lists them) when -s is omitted; with -s it's the pair index within the
# site. Default -p1. Absolute remote paths match a pair by prefix.
GP_LROOT=""; GP_RROOT=""
if [ -z "$SITE" ]; then
    require_config; load_all_sites
    [ -z "$FORCE" ] && FORCE=1
    _gp=$(ftps_global_pair "$FORCE")
    [ -z "$_gp" ] && { echo "No pair -p$FORCE — see: ftpull --pairs" >&2; exit 1; }
    _gsite=${_gp%%|*}; _grest=${_gp#*|}
    GP_LROOT=$(printf '%s' "${_grest%%|*}" | sed 's:/*$::')
    GP_RROOT=$(printf '%s' "${_grest#*|}"  | sed 's:/*$::')
    SITE=$(ftps_site_number "$_gsite")
    FORCE=""
else
    [ -z "$FORCE" ] && FORCE=1
fi
cmd_download "$SITE" "$FORCE" "$@"
