#!/usr/bin/env bash
set -euo pipefail
# POS: media sync — Incremental Music → USB sync (mp3/mp4, add/update only)
# POS_FLAGS: --mp3 --mp4 --source --dry-run
# POS_SUBCMDS: menu
# POS_DEPS: lsblk jq
# POS_EXAMPLES: pos media sync --mp3 | Sync only MP3 files to USB
# POS_EXAMPLES: pos media sync --mp4 --dry-run | Preview MP4 sync without copying

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/usb-lib.sh" 2>/dev/null || source "$(dirname "$0")/usb-lib.sh"
source "$(dirname "$0")/../lib/menu-lib.sh" 2>/dev/null || source "$(dirname "$0")/menu-lib.sh"

load_system_env

# Deps guards sit before -h|--help (help also errors on a box missing the deps).
command -v lsblk &>/dev/null || err "lsblk not found (util-linux) — needed to detect USB storage"
command -v jq    &>/dev/null || err "jq not found — needed to detect USB storage (sudo apt install jq)"

SRC="${MEDIA_SYNC_SOURCE:-$HOME/Music}"
DEST_DIR="${MEDIA_SYNC_DEST:-Music}"
MP3=0
MP4=0
DRY_RUN=0

usage() {
    cat <<EOF
Usage: pos media sync [options]

Incrementally copy your music onto a USB stick. Add/update only — files on
the stick that are no longer in the source are left alone, never deleted.
USB storage is detected like \`pos system backup\` (lsblk TRAN + lsusb/by-id
corroboration; unmounted sticks get a mount offer first) and you pick which
one to sync to. The source tree is mirrored under <usb>/$DEST_DIR.

Bare \`pos media sync\` on a terminal (or \`pos media sync menu\`) opens an
interactive menu wrapping these same actions; flags stay scriptable.

Options:
  --mp3               Sync only *.mp3 files
  --mp4               Sync only *.mp4 files
  --source <dir>      Source folder (default: $SRC)
  --dry-run           Preview what would be copied (copies nothing)
  -h, --help          This help

Examples:
  pos media sync
  pos media sync --mp3
  pos media sync --mp4 --dry-run
  pos media sync --source /data/Music

Environment:
  MEDIA_SYNC_SOURCE   Source folder (default: \$HOME/Music)
  MEDIA_SYNC_DEST     Subfolder on the USB stick (default: Music)
  USB_MOUNT_BASE      Where to mount an unmounted stick (default: /media)
  USB_BYID            by-id dir used to corroborate USB detection
                      (default: /dev/disk/by-id)
  (loaded from ~/.config/linux_post_install/system.env unless exported)
EOF
    exit 0
}

# ── Interactive menu (opt-in front door, Pattern B via lib/menu-lib.sh) ──
menu_change_source() {
    local val
    val="$(menu_ask_value "Source folder [current: $SRC]")" || return 0
    if [ ! -d "$val" ]; then
        warn "Not a folder: $val"
        return 0
    fi
    SRC="$val"
    log "Source set to $SRC"
}

run_menu() {
    menu_guard || exit 1
    while true; do
        local choice
        choice="$(menu_run "Music sync" \
            "Sync now — mp3 + mp4" \
            "Preview only (--dry-run)" \
            "Sync mp3 only" \
            "Sync mp4 only" \
            "Change source folder (current: $SRC)")" || return 0
        case "$choice" in
            1) MP3=1; MP4=1; DRY_RUN=0; cmd_sync ;;
            2) MP3=1; MP4=1; DRY_RUN=1; cmd_sync ;;
            3) MP3=1; MP4=0; DRY_RUN=0; cmd_sync ;;
            4) MP3=0; MP4=1; DRY_RUN=0; cmd_sync ;;
            5) menu_change_source ;;
        esac
    done
}

cmd_sync() {
    [ -d "$SRC" ] || err "Source not found: $SRC"

    trap 'notify_send "Music sync FAILED"' ERR

    section "Music sync"
    echo "Source : $SRC"
    if [ "$MP3" -eq 1 ] && [ "$MP4" -eq 1 ]; then
        echo "Filter : mp3 + mp4"
        find_expr=(-type f \( -iname '*.mp3' -o -iname '*.mp4' \))
    elif [ "$MP3" -eq 1 ]; then
        echo "Filter : mp3 only"
        find_expr=(-type f -iname '*.mp3')
    else
        echo "Filter : mp4 only"
        find_expr=(-type f -iname '*.mp4')
    fi

    usb_pick_root "Sync to" "$DEST_DIR" "no sync performed" || {
        log "Skipped — no sync performed"
        return 0
    }
    dest_root="${USB_ROOT%/}/$DEST_DIR"
    echo "Target : $dest_root"

    # Does the destination need this file copied? Missing, or size/mtime differs.
    needs_copy() {
        local src="$1" dst="$2" ss="" ds="" sm="" dm=""
        [ -f "$dst" ] || return 0
        ss="$(stat -c %s "$src" 2>/dev/null || printf 0)"
        ds="$(stat -c %s "$dst" 2>/dev/null || printf 0)"
        sm="$(stat -c %Y "$src" 2>/dev/null || printf 0)"
        dm="$(stat -c %Y "$dst" 2>/dev/null || printf 0)"
        [ "$ss" = "$ds" ] && [ "$sm" -le "$dm" ] && return 1
        return 0
    }

    # Pre-flight: fail early on "won't fit" instead of a mid-copy ENOSPC that
    # leaves a half-written target. Measures exactly what needs_copy would copy.
    space_path="$USB_ROOT"
    if [ "$DRY_RUN" -eq 0 ]; then
        mkdir -p "$dest_root"
        space_path="$dest_root"
    fi
    need_kb=0
    while IFS= read -r f; do
        rel="${f#"$SRC/"}"
        if needs_copy "$f" "$dest_root/$rel"; then
            sz="$(stat -c %s "$f" 2>/dev/null || printf 0)"
            need_kb=$((need_kb + (sz + 1023) / 1024))
        fi
    done < <(find -H "$SRC" "${find_expr[@]}")
    have_kb="$(df -Pk "$space_path" 2>/dev/null | awk 'NR==2 {print $4}')"
    have_kb="${have_kb:-0}"
    if [ "$need_kb" -gt "$have_kb" ]; then
        msg="Not enough free space on ${USB_ROOT%/}: need ~${need_kb}K, have ${have_kb}K"
        if [ "$DRY_RUN" -eq 1 ]; then
            warn "$msg"
        else
            err "$msg"
        fi
    fi

    added=0
    updated=0
    unchanged=0
    while IFS= read -r f; do
        rel="${f#"$SRC/"}"
        dest="$dest_root/$rel"

        if needs_copy "$f" "$dest"; then
            if [ -f "$dest" ]; then
                updated=$((updated + 1))
            else
                added=$((added + 1))
            fi
            if [ "$DRY_RUN" -eq 1 ]; then
                log "would copy  $rel"
            else
                mkdir -p "$(dirname "$dest")"
                cp --preserve=timestamps "$f" "$dest"
                echo "  + $rel"
            fi
        else
            unchanged=$((unchanged + 1))
        fi
    done < <(find -H "$SRC" "${find_expr[@]}" | sort)

    if [ "$DRY_RUN" -eq 1 ]; then
        echo "DRY RUN — nothing copied. Would sync: ${added} new, ${updated} updated, ${unchanged} unchanged → $dest_root"
    else
        ok "Sync complete: ${added} added, ${updated} updated, ${unchanged} unchanged → $dest_root"
        notify_send "Music sync completed: ${added} added, ${updated} updated → $dest_root"
    fi
}

# Menu door: explicit verb, or zero args on a terminal. Everything below —
# including zero args without a terminal — stays byte-compatible with the
# pre-menu CLI.
if [ "${1:-}" = "menu" ]; then
    run_menu
    exit 0
fi
if [ $# -eq 0 ] && [ -t 0 ]; then
    run_menu
    exit 0
fi

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help) usage ;;
        --mp3) MP3=1; shift ;;
        --mp4) MP4=1; shift ;;
        --dry-run) DRY_RUN=1; shift ;;
        --source)
            [ $# -ge 2 ] || err "--source needs a value: pos media sync --source <dir>"
            SRC="$2"; shift 2 ;;
        --source=*) SRC="${1#--source=}"; shift ;;
        -*) err "Unknown option: $1 (see --help)" ;;
        *) err "Unexpected argument: $1 (see --help)" ;;
    esac
done
[ "$MP3" -eq 1 ] || [ "$MP4" -eq 1 ] || { MP3=1; MP4=1; }
cmd_sync
