#!/usr/bin/env bash
set -euo pipefail
# POS: share smb-client — Mount SMB/CIFS shares (ephemeral or persistent systemd mount units)
# POS_SUBCMDS: mount unmount list persist unpersist menu

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

SMB_CREDS_DIR="${SMB_CREDS_DIR:-/etc/samba/credentials}"
UNIT_DIR="${UNIT_DIR:-/etc/systemd/system}"
SMB_PORT="${SMB_PORT:-445}"
SMB_CONF="${SMB_CONF:-/etc/samba/smb.conf}"
command -v mount.cifs &>/dev/null || err "mount.cifs not found (install cifs-utils)"
command -v systemd-escape &>/dev/null || err "systemd-escape not found"

usage() {
    cat <<EOF
Usage: pos share smb-client <command> [args]

Mount and manage SMB/CIFS shares from remote servers (cifs-utils).

Commands:
  mount <//server/share> <local-dir> [user]   One-shot mount (creates local-dir if needed)
  unmount <local-dir>                          Unmount the share (idempotent: rc 0 when
                                               nothing is mounted)
  list                                         Show active + persistent SMB mounts
  persist <//server/share> <local-dir> [user]  Persistent mount via systemd .mount + .automount
                                               units (mounts on first access — never blocks boot)
  unpersist <local-dir>                        Stop, disable and remove the mount/automount units

Safety net before anything is mounted:
  - server reachability on port 445 is probed first (fast fail with hints,
    before any password is asked)
  - an already-mounted target refuses a second mount instead of stacking
  - mounting over a non-empty directory asks for confirmation first
  - every successful mount is read-tested immediately; a mount that cannot
    be listed is rolled back automatically
  - mount failures are translated into targeted fixes (password, chmod o+x
    traversal, wrong share name, firewall/down server) instead of raw
    kernel errors

With no user, guest access is attempted. With a user, you are prompted for
the Samba password — one-shot mounts use a throwaway chmod-600 credentials
file; persistent mounts keep one at /etc/samba/credentials/.

Examples:
  pos share smb-client mount //100.100.100.1/media /mnt/smb/media
  pos share smb-client persist //100.100.100.1/media /mnt/smb/media bob
  pos share smb-client list
  pos share smb-client unmount /mnt/smb/media
EOF
    exit 0
}

cmd="${1:-}"
case "$cmd" in
    -h|--help) usage ;;
    ""|menu|mount|unmount|list|persist|unpersist) ;;
    *) err "Unknown command '$cmd' (see --help)" ;;
esac

validate_share() {
    case "$1" in
        //*/*) ;;
        //*) err "Invalid share '$1' — missing share name: expected <//server/share> (e.g. //10.0.0.5/media)" ;;
        *) err "Invalid share '$1' — expected <//server/share> (e.g. //10.0.0.5/media)" ;;
    esac
}

validate_dir() {
    case "$1" in
        /*) ;;
        *) err "Mount point must be an absolute path: $1" ;;
    esac
}

# Split //server/share into server + share; normalizes trailing slashes.
split_share() {
    local rest="$1"
    rest="${rest#//}"
    SHARE_PATH="/${rest#*/}"
    SERVER="${rest%%/*}"
    SHARE_PATH="${SHARE_PATH%/}"
    [ -n "$SERVER" ] || err "No server in share '$1'"
    [ -n "${SHARE_PATH#/}" ] || err "No share name in '$1'"
}

# TCP connect probe against the server before touching passwords or mounts.
probe_server() {
    local host="$1"
    if timeout 3 bash -c "exec 3<>/dev/tcp/${host}/${SMB_PORT}" 2>/dev/null; then
        return 0
    fi
    err "Server ${host} not reachable on port ${SMB_PORT} (SMB) — nothing was mounted.
  Checks:
    - is the address right? ping ${host}
    - is the server up and sharing? on the server: pos share smb-server status
    - port filtered? from here: pos network checkport ${host}:${SMB_PORT}
    - Tailscale host? make sure 'tailscale status' lists it (up + recent)"
}

# Source currently cifs-mounted at $1 (empty string when none).
mounted_src() {
    findmnt -rnf -t cifs -o SOURCE,TARGET 2>/dev/null | awk -v t="$1" '$2 == t {print $1; exit}'
}

# Persisted Type=cifs unit declaring Where=<path>? An idle automount never
# appears in findmnt, so this is the only way to tell "idle automount" apart
# from "nothing configured". Sets PERSISTED_UNIT to the unit file basename.
persisted_smb_at() { # <path> — rc 0 persisted · rc 1 not persisted
    local uf
    PERSISTED_UNIT=""
    for uf in "${UNIT_DIR}"/*.mount; do
        [ -f "$uf" ] || continue
        grep -q '^Type=cifs$' "$uf" || continue
        if [ "$(sed -n 's/^Where=//p' "$uf")" = "$1" ]; then
            PERSISTED_UNIT="$(basename "$uf")"
            return 0
        fi
    done
    return 1
}

# Create the mountpoint if needed; refuse to silently shadow a non-empty dir.
ensure_mountpoint() {
    local where="$1"
    if [ -e "$where" ] && [ ! -d "$where" ]; then
        err "$where exists and is not a directory — pick another mount point"
    fi
    if [ -d "$where" ] && [ -n "$(ls -A "$where" 2>/dev/null)" ]; then
        warn "$where is not empty — its contents are hidden while the share is mounted."
        confirm "Mount over it anyway?" n || err "Aborted — nothing was mounted"
    elif [ ! -d "$where" ]; then
        run sudo mkdir -p "$where"
        log "Created mount point $where"
    fi
}

# Prompt for the Samba password (no echo) and write a fresh chmod-600
# credentials file; prints its path, caller removes it. Test seam:
# SMB_PW_FILE redirects the password read (never advertised).
make_creds() {
    local user="$1" pw tmp pw_in="${SMB_PW_FILE:-/dev/tty}"
    read -rsp "Samba password for $user: " pw <"$pw_in" || true
    echo >&2
    [ -n "$pw" ] || err "empty password"
    tmp="$(mktemp)"
    chmod 600 "$tmp"
    printf 'username=%s\npassword=%s\n' "$user" "$pw" > "$tmp"
    printf '%s' "$tmp"
}

mount_opts() {
    printf 'uid=%s,gid=%s' "$(id -u)" "$(id -g)"
}

# Translate a failed mount attempt into the fix the human actually needs.
diagnose_mount_failure() {
    local msg="$1"
    case "$msg" in
        *LOGON_FAILURE*|*WRONG_PASSWORD*)
            err "Server rejected the login (wrong user/password).
  - retry with the correct Samba user: pos share smb-client mount $what $where <user>
  - does the account exist at all? on the server: pos share smb-server status" ;;
        *ACCESS_DENIED*|*"error(13)"*|*"Permission denied"*|*STATUS_CANNOT_DELETE*)
            err "Access denied by the server (login worked, share said no).
  On the server side, in order:
    1. traversal: every parent dir of the share needs o+x — the server tool
       prints the exact dirs when you re-share; quick fix:
       chmod o+x <parent-dir>
    2. user missing from the Samba passdb: pos share smb-server adduser <user>
    3. share restricted: 'valid users' must contain your user (pos share smb-server list)" ;;
        *BAD_NETWORK_NAME*)
            err "The server has no share named '${SHARE_PATH#/}'.
  - typo in the share name? on the server: pos share smb-server list
  - correct syntax: //$SERVER/<share>" ;;
        *CONNECTION_REFUSED*|*"error(111)"*)
            err "${SERVER} refused the connection — smbd is down or a firewall rejects port ${SMB_PORT}.
  on the server: pos share smb-server enable   /   sudo ufw allow Samba" ;;
        *HOST_UNREACHABLE*|*"error(101)"*|*"error(113)"*|*"No route to host"*|*"Network is unreachable"*)
            err "${SERVER} is unreachable (routing/host down).
  - Tailscale host? 'tailscale status' must list it as online
  - probe from here: pos network checkport ${SERVER}:${SMB_PORT}" ;;
        *IO_TIMEOUT*|*"Host is down"*|*"error(112)"*|*"timed out"*)
            err "${SERVER} accepted TCP but never answered the SMB handshake (usually a dropping firewall).
  on the server: sudo ufw allow Samba   /   pos share smb-server status" ;;
        *)
            err "Mount failed:
$msg
  More causes + fixes: DOC/howto/share.md (SMB troubleshooting)" ;;
    esac
}

# Mount attempt with captured stderr (so failures can be diagnosed).
try_mount() {
    local what="$1" where="$2" opts="$3"
    if [ "${DRY_RUN:-0}" -eq 1 ]; then
        log "(dry-run) mount -t cifs $what $where -o $opts"
        return 0
    fi
    MOUNT_ERR="$(sudo mount -t cifs "$what" "$where" -o "$opts" 2>&1)" || return 1
}

# Post-mount proof: the mount must actually list. A mount that cannot be
# read is worse than no mount — roll it back.
read_test_or_rollback() {
    local where="$1" what="$2"
    if [ "${DRY_RUN:-0}" -eq 1 ]; then return 0; fi
    if timeout 5 ls -A "$where" >/dev/null 2>&1; then
        return 0
    fi
    warn "Mounted, but $where cannot be listed — rolling back."
    sudo umount "$where" 2>/dev/null || sudo umount -l "$where" 2>/dev/null || true
    err "Share '${what}' mounted but reading it failed, so it was unmounted again.
  Login works but file permissions don't — on the server:
    - ls -la <shared-path> as root: do others/the owner have r-x?
    - the connecting user must have unix-level access to the folder itself
  Details: DOC/howto/share.md (SMB troubleshooting)"
}

cmd_mount() { # <//server/share> <local-dir> [user]
    local what="$1" where="$2" user="${3:-}"
    local src creds opts
    validate_share "$what"
    validate_dir "$where"
    split_share "$what"

    probe_server "$SERVER"

    if src="$(mounted_src "$where")"; [ -n "$src" ]; then
        err "$where is already mounted (source: ${src:-unknown}) — nothing done.
  Unmount it first: pos share smb-client unmount $where"
    fi

    ensure_mountpoint "$where"

    if [ -n "$user" ]; then
        creds="$(make_creds "$user")"
        trap 'rm -f "$creds"' EXIT
        opts="credentials=$creds,$(mount_opts)"
        try_mount "$what" "$where" "$opts" || {
            rm -f "$creds"
            trap - EXIT
            diagnose_mount_failure "${MOUNT_ERR:-}"
        }
        rm -f "$creds"
        trap - EXIT
    else
        warn "No user — attempting guest mount (works only if the server allows guest access)"
        try_mount "$what" "$where" "guest,$(mount_opts)" || diagnose_mount_failure "${MOUNT_ERR:-}"
    fi

    read_test_or_rollback "$where" "$what"
    log "Mounted $what at $where (read test passed)"
    notify_send "SMB mounted: $what → $where"
}

cmd_unmount() { # <local-dir>
    local where="$1" src out
    validate_dir "$where"

    src="$(mounted_src "$where")"
    if [ -z "$src" ]; then
        # Idempotent no-op — rc 0 whether or not anything is configured.
        if persisted_smb_at "$where"; then
            warn "$where is a persisted automount — not currently mounted."
            log "Access it once (e.g.: ls $where) to auto-mount it, or remove the persistence first: menu option 5 (pos share smb-client unpersist $where)"
        else
            log "Nothing mounted at $where"
        fi
        return 0
    fi
    if ! out="$(sudo umount "$where" 2>&1)"; then
        if grep -qE "busy|in use" <<<"$out"; then
            warn "$where is busy (${src})"
            confirm "Force a lazy unmount now?" y && { run sudo umount -l "$where"; log "Lazy-unmounted $where"; return 0; }
            err "Still mounted. Find the blocker: sudo lsof +D $where  (or fuser -vm $where)"
        fi
        err "Unmount failed: $out"
    fi
    log "Unmounted $where"
    notify_send "SMB unmounted: $where"
}

cmd_list() {
    local found=0 active_mounts unit what where
    local -a persistent=()
    active_mounts="$(findmnt -t cifs 2>/dev/null || true)"
    if [ -n "$active_mounts" ]; then
        printf 'Active mounts:\n'
        printf '%s\n' "$active_mounts"
        found=1
    fi
    for unit in "${UNIT_DIR}"/*.mount; do
        [ -e "$unit" ] || continue
        grep -q '^Type=cifs$' "$unit" || continue
        what="$(sed -n 's/^What=//p' "$unit")"
        where="$(sed -n 's/^Where=//p' "$unit")"
        [ -n "$what" ] && [ -n "$where" ] || continue
        persistent+=("$where|$what")
    done
    if [ "${#persistent[@]}" -gt 0 ]; then
        found=1
        printf 'Persistent (automount):\n'
        for entry in "${persistent[@]}"; do
            printf '  %-44s %s\n' "${entry%%|*}" "${entry#*|}"
        done
    fi
    [ "$found" -eq 1 ] || echo "No SMB mounts"
}

cmd_persist() { # <//server/share> <local-dir> [user]
    local what="$1" where="$2" user="${3:-}"
    local src unit auto_unit unit_file auto_file opts creds_file tmp out verified
    validate_share "$what"
    validate_dir "$where"
    split_share "$what"

    probe_server "$SERVER"

    if src="$(mounted_src "$where")"; [ -n "$src" ]; then
        err "$where is already mounted (source: ${src:-unknown}) — nothing done.
  Active mount + automount units conflict; unmount first:
    pos share smb-client unmount $where"
    fi

    unit="$(systemd-escape --path --suffix=mount "$where")"
    auto_unit="${unit%.mount}.automount"
    unit_file="${UNIT_DIR}/${unit}"
    auto_file="${UNIT_DIR}/${auto_unit}"

    if [ -e "$unit_file" ] || [ -e "$auto_file" ]; then
        warn "Units for $where already exist — they will be REPLACED:"
        if [ -e "$unit_file" ]; then warn "  ${unit_file}"; fi
        if [ -e "$auto_file" ]; then warn "  ${auto_file}"; fi
        confirm "Replace them?" n || err "Aborted — units left untouched"
    fi

    opts="$(mount_opts),_netdev,noexec"
    creds_file=""
    if [ -n "$user" ]; then
        creds_file="$SMB_CREDS_DIR/$(basename "$where")"
        sudo mkdir -p "$SMB_CREDS_DIR"
        tmp="$(make_creds "$user")"
        sudo install -m 600 "$tmp" "$creds_file"
        rm -f "$tmp"
        opts="credentials=$creds_file,$opts"
    else
        warn "No user — persisting a guest mount (works only if the server allows guest access)"
        opts="guest,$opts"
    fi

    rollback_persist() {
        warn "Rolling back everything this command created…"
        sudo systemctl disable "$auto_unit" 2>/dev/null || true
        sudo systemctl stop "$auto_unit" 2>/dev/null || true
        sudo systemctl disable "$unit" 2>/dev/null || true
        sudo systemctl stop "$unit" 2>/dev/null || true
        sudo rm -f "$unit_file" "$auto_file"
        if [ -n "$creds_file" ]; then sudo rm -f "$creds_file"; fi
        sudo systemctl daemon-reload 2>/dev/null || true
    }

    if [ "${DRY_RUN:-0}" -eq 1 ]; then
        log "(dry-run) write $unit_file + $auto_file (Type=cifs, Options=$opts)"
        log "(dry-run) daemon-reload + enable --now $auto_unit"
        exit 0
    fi

    cat <<UNIT | sudo tee "$unit_file" >/dev/null
[Unit]
Description=SMB mount of ${what} at ${where}
After=network-online.target
Wants=network-online.target

[Mount]
What=${what}
Where=${where}
Type=cifs
Options=${opts}
UNIT
    cat <<UNIT | sudo tee "$auto_file" >/dev/null
[Unit]
Description=Automount of SMB share ${what} at ${where}

[Automount]
Where=${where}

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

    # Enable, then prove the automount actually serves the share before
    # declaring victory — a broken unit here would bite months later.
    if ! out="$(sudo systemctl enable --now "$auto_unit" 2>&1)"; then
        rollback_persist
        err "Could not enable $auto_unit: $out"
    fi

    ls "$where" >/dev/null 2>&1 || true   # poke the automount
    verified=0
    for _ in 1 2 3 4 5 6 7 8 9 10; do
        if [ -n "$(mounted_src "$where")" ]; then verified=1; break; fi
        sleep 0.5
    done
    if [ "$verified" -ne 1 ]; then
        rollback_persist
        err "Automount did not trigger for $where — units removed again.
  Check: systemctl status $auto_unit"
    fi

    # Same read-proof as one-shot mounts: catch permission problems now.
    if ! timeout 5 ls -A "$where" >/dev/null 2>&1; then
        rollback_persist
        err "Automount triggered but $where is not readable — units + credentials removed.
  Login works but file permissions don't — fix unix perms on the server
  (the shared folder itself needs r-x for the connecting user).
  Details: DOC/howto/share.md (SMB troubleshooting)"
    fi

    log "Persistent SMB mount (automount): ${what} → ${where} (${auto_unit})"
    log "Verified: automount triggers and the share is readable"
    notify_send "SMB mount persisted: ${what} → ${where}"
}

cmd_unpersist() { # <local-dir>
    local where="$1" unit auto_unit unit_file auto_file
    validate_dir "$where"

    unit="$(systemd-escape --path --suffix=mount "$where")"
    auto_unit="${unit%.mount}.automount"
    unit_file="${UNIT_DIR}/${unit}"
    auto_file="${UNIT_DIR}/${auto_unit}"

    if [ ! -f "$unit_file" ] && [ ! -f "$auto_file" ]; then
        # Idempotent no-op — rc 0 whether or not anything is configured.
        # Return, not exit: a bogus typed path reached from the menu must
        # not kill the whole session. A persisted unit can also live under
        # a non-escape-derived filename (Where= still matches) — point at
        # it instead of a bare nothing-to-do.
        if persisted_smb_at "$where"; then
            warn "$where has a persistent SMB mount unit (${PERSISTED_UNIT}) under a non-standard unit name."
            log "Check it: systemctl status ${PERSISTED_UNIT%.mount} — or find it in the list: pos share smb-client list"
        else
            log "No persistent SMB mount for $where — nothing to do"
        fi
        return 0
    fi

    sudo systemctl disable "$auto_unit" 2>/dev/null || true
    sudo systemctl stop "$auto_unit" 2>/dev/null || true
    sudo systemctl disable "$unit" 2>/dev/null || true
    sudo systemctl stop "$unit" 2>/dev/null || true
    sudo rm -f "$unit_file" "$auto_file"
    sudo rm -f "$SMB_CREDS_DIR/$(basename "$where")"
    sudo rmdir "$SMB_CREDS_DIR" 2>/dev/null || true
    sudo systemctl daemon-reload

    if [ -n "$(mounted_src "$where")" ]; then
        warn "$where is still mounted (something holds it open)"
        if confirm "Force a lazy unmount now?" y; then run sudo umount -l "$where"; fi
    fi
    log "Removed persistent SMB mount: $where"
    notify_send "SMB persistent mount removed: $where"
}

# ── Interactive menu flows ─────────────────────────────────────

# Best-effort resolution of the directory BEHIND share <$2> on host <$1>.
# The underlying path of a remote SMB share is not remotely discoverable;
# it is only locally knowable when THIS machine is the server (testparm
# answers from the local config). Anything else stays unresolved (rc 1)
# and the mountpoint picker silently skips the "(as on server)" suggestion.
smb_server_path() { # <host> <share> — stdout: server-side dir · rc 1 = unresolved
    local host="${1,,}" share="$2" name p ips=""
    local -a names=("localhost" "127.0.0.1" "::1" "$(hostname)")
    names+=("$(hostname -f 2>/dev/null || true)")
    ips="$(hostname -I 2>/dev/null)" || true
    # shellcheck disable=SC2086 — $ips is an intentional space-split IP list
    for name in "${names[@]}" ${ips}; do
        if [ "$name" = "$host" ]; then
            share_require_bin testparm "" || return 1
            [ -f "$SMB_CONF" ] || return 1
            p="$(testparm -s --parameter-name=path --section-name="$share" "$SMB_CONF" 2>/dev/null)" || return 1
            [ -n "$p" ] || return 1
            printf '%s\n' "$p"
            return 0
        fi
    done
    return 1
}

# Mountpoint picker — local port of the share_pick primitive with exactly
# two deltas: the hint line offers `n=new`, and typing n runs the
# create-new-dir flow below. share_pick cannot intercept `n` (it filters on
# it) and lib/menu-lib.sh is shared, so the fork lives here. Rendering of
# numbered picks / text filter / 0=back is byte-identical to menu_pick.
# stdout: chosen item text (or the freshly created dir) · rc 1 = back/cancel.
pick_mountpoint() {
    local prompt="$1"; shift
    local -a items=("$@")
    if [ "${#items[@]}" -eq 0 ]; then
        return 1
    fi
    if ! [ -t 0 ]; then
        printf '[!] Interactive picker needs a terminal.\n' >&2
        return 1
    fi
    local filter="" ans i n total=${#items[@]} made
    local -a shown=()
    while true; do
        shown=()
        for ((i = 0; i < total; i++)); do
            if [ -z "$filter" ] || [[ "${items[$i],,}" == *"${filter,,}"* ]]; then
                shown+=("${items[$i]}")
            fi
        done
        n=${#shown[@]}
        {
            echo
            if [ -n "$filter" ]; then
                printf -- "-- %d of %d match '%s' --\n" "$n" "$total" "$filter"
            else
                printf -- "-- %d available --\n" "$total"
            fi
            if [ "$n" -eq 0 ]; then
                printf '[!] no matches — enter nothing or / to clear the filter\n' >&2
            else
                for ((i = 0; i < n; i++)); do
                    printf ' %2d) %s\n' $((i + 1)) "${shown[$i]}"
                done
            fi
        } >&2
        if ! read -rp "${prompt} [1-${n}], n=new, text=filter, 0=back " ans; then
            return 1                     # EOF — cancel
        fi
        case "$ans" in
            "")  [ -z "$filter" ] || filter="" ; continue ;;
            "/") filter="" ; continue ;;
            0 | q | Q | b | B) return 1 ;;
            n | N)
                made="$(ask_new_mountpoint)" && { echo "$made"; return 0; }
                continue                 # declined/invalid/mkdir-failed → redraw
                ;;
            *[!0-9]*)
                filter="$ans"
                continue
                ;;
            *)
                if (( ans >= 1 && ans <= n )); then
                    echo "${shown[$((ans - 1))]}"
                    return 0
                fi
                echo "Unknown choice." >&2
                ;;
        esac
    done
}

# Create-new-dir flow behind the picker's `n` key. Validates the shape
# (absolute, no trailing slash), confirm-gates the creation, then mkdir -p.
# Any decline, invalid input, EOF or mkdir failure is a warning + rc 1 —
# the picker redraws, the tool never aborts.
ask_new_mountpoint() { # stdout: created dir · rc 1 = cancelled/failed
    # NOTE: runs inside $( ) from the picker — every display line MUST go to
    # stderr (menu-lib contract: display → stderr, result → stdout).
    local dir
    dir="$(share_ask_value "New mountpoint (absolute path)")" || return 1
    case "$dir" in
        /*) ;;
        *) warn "'$dir' is not an absolute path — must start with /" >&2; return 1 ;;
    esac
    case "$dir" in
        */) warn "'$dir' must not end with a slash" >&2; return 1 ;;
    esac
    case "$dir" in
        /etc|/boot|/bin|/sbin|/lib|/lib64|/usr|/var|/root|/home/*/.ssh*)
            warn "Refusing system path as mountpoint" >&2
            return 1
            ;;
    esac
    confirm "Create mountpoint ${dir}?" n || return 1
    if ! run sudo mkdir -p "$dir"; then
        warn "Could not create ${dir}" >&2
        return 1
    fi
    log "Created mount point $dir" >&2
    echo "$dir"
}

menu_ask_mountpoint() { # [server_path] — stdout: absolute path · rc 1 cancelled
    local srv="${1:-}" idx res cand dir known=0
    local -a cands=() dirs=()
    mapfile -t cands < <(share_folder_candidates)
    for cand in "${cands[@]}"; do
        dirs+=("${cand%% (*}")     # bare path (strip "(mounted fstype)" note)
    done
    # Same-as-server suggestion: unless the server-side path already exists
    # among the local candidates, append it as a synthetic pick so mounting
    # at a mirrored path is a normal selection.
    if [ -n "$srv" ] && [ "${#dirs[@]}" -gt 0 ] &&
        printf '%s\n' "${dirs[@]}" | grep -qxF -- "$srv"; then
        known=1
    fi
    if [ -n "$srv" ] && [ "$known" -eq 0 ]; then
        cands+=("${srv}   (as on server)")
    fi
    if [ "${#cands[@]}" -gt 0 ]; then
        if res="$(pick_mountpoint "Mountpoint" "${cands[@]}")"; then
            dir="${res%% (*}"              # strip "(as on server)"/mount note
            case "$dir" in
                /etc|/boot|/bin|/sbin|/lib|/lib64|/usr|/var|/root|/home/*/.ssh*)
                    warn "Refusing system path as mountpoint"
                    return 1
                    ;;
                *)
                    echo "$dir"
                    return 0
                    ;;
            esac
        fi
    fi
    dir="$(share_ask_value "Mountpoint (absolute path)")" || return 1
    [ -n "$dir" ] || { warn "No mountpoint given"; return 1; }
    echo "$dir"
}

menu_mount() { # ephemeral|persist
    local mode="$1" host idx share what where
    host="$(share_ask_value "SMB server (host or IP)")" || return 1
    [ -n "$host" ] || { warn "No server given"; return 1; }

    # Empty answer = guest enumeration (with an interactive auth retry inside
    # the helper); a named user authenticates right away. SMB_AUTH_USER tells
    # us which account ended up being used so the mount reuses it.
    local user=""
    read -rp "Samba user (empty = try guest): " user || return 1
    SMB_AUTH_USER=""

    local -a shares=()
    if mapfile -t shares < <(share_smb_shares "$host" "$user") && [ "${#shares[@]}" -gt 0 ]; then
        idx="$(share_pick "Pick share on ${host}" "${shares[@]}")" || return 1
        share="${shares[$((idx - 1))]}"
    else
        share="$(share_ask_value "Share name on ${host} (e.g. media)")" || return 1
        [ -n "$share" ] || { warn "No share name given"; return 1; }
    fi
    user="${SMB_AUTH_USER:-$user}"

    what="//${host}/${share}"
    # Underlying server-side dir, when knowable (this machine is the server);
    # empty → picker silently skips the "(as on server)" suggestion.
    local srv_path=""
    srv_path="$(smb_server_path "$host" "$share")" || srv_path=""
    where="$(menu_ask_mountpoint "$srv_path")" || return 1

    if [ "$mode" = "persist" ]; then
        cmd_persist "$what" "$where" "$user"
    else
        cmd_mount "$what" "$where" "$user"
    fi
}

menu_unmount() {
    local idx row src where i
    local -a tgts=() srcs=() items=()
    # Same enumeration source as the `list` view's active section
    # (findmnt -t cifs), reduced to TARGET|SOURCE rows.
    while IFS= read -r row; do
        [ -n "$row" ] || continue
        tgts+=("${row%%|*}")
        srcs+=("${row#*|}")
    done < <(findmnt -rn -o TARGET,SOURCE -t cifs 2>/dev/null |
        awk '{ src=$NF; $NF=""; sub(/[ \t]+$/, ""); print $0 "|" src }')
    if [ "${#tgts[@]}" -eq 0 ]; then
        log "No active SMB mounts"
        where="$(share_ask_value "Local mountpoint to unmount")" || return 1
        [ -n "$where" ] || return 1
        cmd_unmount "$where"
        return 0
    fi
    for ((i = 0; i < ${#tgts[@]}; i++)); do
        items+=("${tgts[$i]}  ← ${srcs[$i]}")
    done
    idx="$(share_pick "Unmount which SMB mount?" "${items[@]}")" || return 1
    where="${tgts[$((idx - 1))]}"
    src="${srcs[$((idx - 1))]}"
    confirm "Unmount ${where} (from ${src})?" n || { log "Cancelled"; return 1; }
    cmd_unmount "$where"
}

menu_unpersist() {
    local idx uf where unit_w
    local -a paths=()
    for uf in "${UNIT_DIR}"/*.mount; do
        grep -q '^Type=cifs$' "$uf" 2>/dev/null || continue
        unit_w="$(sed -n 's/^Where=//p' "$uf")"
        [ -n "$unit_w" ] || continue
        paths+=("$unit_w")
    done
    if [ "${#paths[@]}" -gt 0 ]; then
        idx="$(share_pick "Remove which persistent SMB mount?" "${paths[@]}")" || return 1
        where="${paths[$((idx - 1))]}"
    else
        where="$(share_ask_value "Local mountpoint whose units to remove")" || return 1
        [ -n "$where" ] || return 1
    fi
    cmd_unpersist "$where"
}

run_menu() {
    share_menu_guard || exit 1
    while true; do
        local choice
        choice="$(share_menu_run "SMB client" \
            "Mount a share (one-shot)" \
            "Persist a share (automount units)" \
            "List active + persistent mounts" \
            "Unmount a mounted share" \
            "Remove a persistent mount")" || return 0
        case "$choice" in
            # Handlers return nonzero on cancel/back — normalized here so a
            # cancel can never reach set -e and kill the whole session.
            1) menu_mount ephemeral || true ;;
            2) menu_mount persist || true ;;
            3) cmd_list || true ;;
            4) menu_unmount || true ;;
            5) menu_unpersist || true ;;
        esac
    done
}

case "$cmd" in
    ""|menu)
        run_menu
        exit 0
        ;;

    mount)
        [ $# -ge 3 ] || err "Usage: pos share smb-client mount <//server/share> <local-dir> [user]"
        cmd_mount "$2" "$3" "${4:-}"
        ;;

    unmount)
        [ $# -ge 2 ] || err "Usage: pos share smb-client unmount <local-dir>"
        cmd_unmount "$2"
        ;;

    list)
        cmd_list
        ;;

    persist)
        [ $# -ge 3 ] || err "Usage: pos share smb-client persist <//server/share> <local-dir> [user]"
        cmd_persist "$2" "$3" "${4:-}"
        ;;

    unpersist)
        [ $# -ge 2 ] || err "Usage: pos share smb-client unpersist <local-dir>"
        cmd_unpersist "$2"
        ;;
esac
