#!/bin/sh
# =============================================================================
# ftpush — upload files/folders to a remote site (ftpsuite). Part of the
# ftpsuite; shares its config and library with ftpull, ftps-cli, ftps-gui.
#
# Usage:
#   ftpush [-s N] [-p N] PATH [PATH...]   upload to site N (default if -s
#                                         omitted). A relative PATH is a path
#                                         INSIDE the mirror — pair 1's local root,
#                                         or -p N — and mirrors to the same
#                                         subtree on the server. Run from ANY
#                                         directory; the current dir is never
#                                         used. An ABSOLUTE path matches a pair by
#                                         longest prefix (for a file outside the
#                                         mirror).
#   ftpush --sites | --add-site | --edit-site [-s N] | --del-site [-s N]
#   ftpush --pairs [-s N] | --add-pair [-s N] | --del-pair [-s N] [-p N]
#          | --edit-pair [-s N] [-p N]
#   ftpush --export [FILE] | --import FILE
#   ftpush --version | -v | --help | -h
#
# Sites are selected by NUMBER everywhere (see --sites). Config is shared with
# the whole suite; managing it here is the same as managing it from ftpull.
# =============================================================================

# ── Locate + source the shared library ──────────────────────────────────────
_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 "ftpush: cannot find ftps_common.sh (set FTPS_LIB)" >&2; exit 2; }

set -eu

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

# ── upload ───────────────────────────────────────────────────────────────────
cmd_upload() {
    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 ftpush --edit-site." >&2; exit 1; fi

    # With -p N, paths are taken RELATIVE TO THAT PAIR'S LOCAL ROOT (and the
    # remote mirrors the same subtree), so you can run ftpush from ANY directory
    # without cd-ing into the site tree first. Without -p, relative paths still
    # resolve against the current directory as before. Absolute 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
    # Global flat -p mode passes the pair roots in directly (site already picked).
    if [ -n "${GP_LROOT:-}" ]; then force_local_root="$GP_LROOT"; force_remote_root="$GP_RROOT"; fi

    # Resolve every path first: "local|remote|kind" per line (kind = file|dir).
    # A RELATIVE path in pair-root mode is taken relative to the pair's local
    # root and mirrored to the same subtree remotely (run from anywhere). An
    # ABSOLUTE path is always matched to a pair by longest local prefix, so a
    # full path to any pair's tree still goes to the right place.
    RESOLVED=""; fcount=0
    for raw in "$@"; do
        # Glob (* ? [..]) in a relative path is expanded against the mirror root,
        # so `ftpush 'a*'` or `ftpush 'tools/*.html'` works from any directory.
        # Quote the pattern in the shell so ftpush (not your shell) expands it.
        case "$raw" in
            /*) : ;;                                       # absolute → handled below
            *[*?[]*)
                _hit=0
                for _m in "$force_local_root"/$raw; do
                    [ -e "$_m" ] || continue               # no-match → skip (nullglob)
                    _hit=1
                    _rel="${_m#"$force_local_root"/}"
                    _rp="$force_remote_root/$_rel"
                    if [ -d "$_m" ]; then _k=dir; else _k=file; fcount=$((fcount+1)); fi
                    RESOLVED="${RESOLVED}
${_m}|${_rp}|${_k}"
                done
                [ "$_hit" = 0 ] && echo "Skipping — no match: $raw" >&2
                continue ;;
        esac
        case "$raw" in
            /*)                                            # absolute → match a pair by prefix
                lp="$raw"; rp=$(resolve_remote "$lp") || rp="" ;;
            *)                                             # relative → path inside the mirror (pair root)
                lp="$force_local_root/$raw"
                rp="$force_remote_root/$raw" ;;
        esac
        [ -e "$lp" ] || { echo "Skipping — not found: $lp" >&2; continue; }
        [ -z "$rp" ] && { echo "Skipping — no matching pair on site '$site': $lp" >&2; continue; }
        if [ -d "$lp" ]; then kind=dir; else kind=file; fcount=$((fcount+1)); fi
        RESOLVED="${RESOLVED}
${lp}|${rp}|${kind}"
    done
    [ -z "$(printf '%s' "$RESOLVED" | sed '/^$/d')" ] && { echo "Nothing to upload." >&2; exit 1; }

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

    # Pass 1: which target files already exist remotely (Created vs Overwritten).
    # Heredoc-fed while loops run in the current shell (no subshell), so the
    # accumulating vars survive — unlike a pipe into while.
    EXISTED=""
    if [ "$fcount" -gt 0 ]; then
        CHK=""; idx=0
        while IFS='|' read -r lp rp kind; do
            [ -z "$lp" ] && continue; [ "$kind" = file ] || continue
            idx=$((idx+1))
            CHK="${CHK}echo FT_M${idx}S; cls -1 \"$rp\" 2>&1; echo FT_M${idx}E; "
        done <<EOF
$RESOLVED
EOF
        chk_out=$(ftps_run_lftp "$site" "$CHK" 2>&1) || true
        idx=0
        while IFS='|' read -r lp rp kind; do
            [ -z "$lp" ] && continue; [ "$kind" = file ] || continue
            idx=$((idx+1))
            blk=$(printf '%s\n' "$chk_out" | sed -n "/FT_M${idx}S/,/FT_M${idx}E/p")
            if printf '%s' "$blk" | grep -qi 'no such file\|not found\|550'; then ex=0; else ex=1; fi
            EXISTED="${EXISTED}
${rp}|${ex}"
        done <<EOF
$RESOLVED
EOF
    fi

    # Pass 2: upload, one connection.
    CMDS=""
    while IFS='|' read -r lp rp kind; do
        [ -z "$lp" ] && continue
        rd=$(dirname "$rp")
        if [ "$kind" = dir ]; then
            echo "Folder:  $lp -> $rp"
            CMDS="${CMDS}mkdir -p \"$rp\" 2>/dev/null; mirror -R --verbose \"$lp\" \"$rp\"; "
        else
            echo "File:    $lp -> $rp"
            CMDS="${CMDS}mkdir -p \"$rd\" 2>/dev/null; put -O \"$rd\" \"$lp\" -o \"$(basename "$rp")\"; "
        fi
    done <<EOF
$RESOLVED
EOF

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

    # Summary: name + size + Created/Overwritten (files) or Mirrored (dirs).
    printf '%s\n' "$RESOLVED" | while IFS='|' read -r lp rp kind; do
        [ -z "$lp" ] && continue
        if [ "$kind" = dir ]; then echo "$(basename "$lp") — Mirrored"; continue; fi
        sz=$(wc -c < "$lp" 2>/dev/null | tr -d ' ')
        ex=$(printf '%s\n' "$EXISTED" | awk -F'|' -v r="$rp" '$1==r{print $2}')
        [ "$ex" = 1 ] && lbl=Overwritten || lbl=Created
        echo "$(basename "$lp") — ${sz} bytes — ${lbl}"
    done
    echo "Done."
}

# ── Arg parsing ──────────────────────────────────────────────────────────────
case "${1:-}" in
    --help|-h|"") usage; exit 0 ;;
    --version|-v) echo "ftpush (ftpsuite) $FTPS_VERSION"; exit 0 ;;
esac
ftps_dispatch_config "$@" || true   # exits if it was a config command

SITE=""; FORCE=""
while true; do
    case "${1:-}" in
        -s)   SITE="${2:-}"; shift 2 2>/dev/null || { echo "Usage: ftpush [-s N] [-p N] PATH..." >&2; exit 1; } ;;
        -s?*) SITE="${1#-s}"; shift ;;                # joined form: -s2
        -p)   FORCE="${2:-}"; shift 2 2>/dev/null || { echo "Usage: ftpush [-s N] [-p N] PATH..." >&2; exit 1; } ;;
        -p?*) FORCE="${1#-p}"; shift ;;               # joined form: -p1
        *) break ;;
    esac
done
# The mirror is the anchor: a relative PATH is always a path inside a pair's
# local root, so ftpush runs from any directory. -p N is a FLAT number across
# ALL sites (ftpush --pairs lists them) when -s is omitted; with -s it's the
# pair index within that site. Default is -p1. Absolute paths match 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: ftpush --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_upload "$SITE" "$FORCE" "$@"
