#!/usr/bin/env bash
# anet-gallery.sh — terminal image gallery viewer for ANetBBS.
#
# Legacy-terminal fallback for the web gallery at /gallery/. Reads the
# same gallery-config.json the web admin UI writes, renders images with
# chafa (Unicode-block art, works in any terminal) or img2sixel
# (sixel-capable terminals: SyncTERM, foot, mlterm, modern xterm).
#
# This file is generated by install.sh/update.sh with __INSTALL_DIR__
# substituted for the real install path, then placed at the install
# root (NOT under version control there, so future updates never
# overwrite a sysop's edits to it). It is not runnable directly by BBS
# callers — wire it up as a menu hotkey via Admin -> BBS Menus, action
# type "exec", e.g. {"name":"Image Gallery","cmd":"__INSTALL_DIR__/anet-gallery.sh"}
# (the "Add sample items" button on that page does this for you).
set -u

INSTALL_DIR="__INSTALL_DIR__"
CONFIG="$INSTALL_DIR/gallery-config.json"

# Renderer picked once per run; 'c' for chafa, 's' for sixel.
RENDER_MODE="c"
HAVE_CHAFA=0
HAVE_SIXEL=0
command -v chafa >/dev/null 2>&1 && HAVE_CHAFA=1
command -v img2sixel >/dev/null 2>&1 && HAVE_SIXEL=1

if [[ "$HAVE_CHAFA" -eq 0 && "$HAVE_SIXEL" -eq 0 ]]; then
    echo "Neither chafa nor img2sixel is installed on this server."
    echo "Ask your sysop to re-run install.sh/update.sh and say yes to"
    echo "the terminal image viewer prompt."
    exit 1
fi
[[ "$HAVE_CHAFA" -eq 0 ]] && RENDER_MODE="s"

clear_screen() { printf '\033[2J\033[H'; }
pause() { read -r -p "  [Press Enter to continue] " _ || true; }

# Load active galleries from gallery-config.json as slug\tlabel\tpath\tdescription
# lines, sorted by (sort_order, label) — mirrors _get_active_galleries()
# in anetbbs/web/gallery.py exactly.
load_galleries() {
    python3 - "$CONFIG" <<'PYEOF'
import json, sys
path = sys.argv[1]
try:
    with open(path) as f:
        data = json.load(f)
except Exception:
    data = []
if not isinstance(data, list):
    data = []
rows = [g for g in data if isinstance(g, dict) and g.get('is_active')]
rows.sort(key=lambda g: (g.get('sort_order', 0), g.get('label', '')))
for g in rows:
    slug = str(g.get('slug', '')).replace('\t', ' ')
    label = str(g.get('label', '')).replace('\t', ' ')
    gpath = str(g.get('path', '')).replace('\t', ' ')
    desc = str(g.get('description', '') or '').replace('\t', ' ')
    print(f"{slug}\t{label}\t{gpath}\t{desc}")
PYEOF
}

# List gallery entries (images + .zip archives) in a directory, one per
# line, sorted -- mirrors _list_images() in anetbbs/web/gallery.py,
# including its zip-archive support (Digital Showroom-style one-photo-
# per-zip TIC feeds).
list_images() {
    local dir="$1"
    find "$dir" -maxdepth 1 -type f \
        \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.gif' \
           -o -iname '*.png' -o -iname '*.bmp' -o -iname '*.webp' \
           -o -iname '*.zip' \) \
        2>/dev/null | sort
}

# Extract the first image-looking member from a zip to a fresh temp
# file and print its path (nothing on failure) -- mirrors
# _first_image_in_zip() in anetbbs/web/gallery.py exactly (same skip
# rules for __MACOSX/dotfiles, same "first by name" convention).
# Caller removes the temp file after rendering it.
extract_zip_image() {
    local zip_file="$1"
    python3 - "$zip_file" <<'PYEOF'
import sys, zipfile, tempfile, os
from pathlib import Path

IMAGE_EXTS = {'.jpg', '.jpeg', '.gif', '.png', '.bmp', '.webp'}

zip_path = sys.argv[1]
try:
    with zipfile.ZipFile(zip_path) as zf:
        candidates = sorted(
            n for n in zf.namelist()
            if not n.endswith('/')
            and '__MACOSX' not in n
            and not Path(n).name.startswith('.')
            and Path(n).suffix.lower() in IMAGE_EXTS
        )
        if not candidates:
            sys.exit(1)
        name = candidates[0]
        data = zf.read(name)
except Exception:
    sys.exit(1)

suffix = Path(name).suffix.lower()
fd, tmp_path = tempfile.mkstemp(suffix=suffix, prefix='anet-gallery-')
with os.fdopen(fd, 'wb') as f:
    f.write(data)
print(tmp_path)
PYEOF
}

render_image() {
    local file="$1"
    local cols lines
    cols=$(tput cols 2>/dev/null || echo 80)
    lines=$(tput lines 2>/dev/null || echo 24)
    clear_screen
    echo "  $(basename "$file")"
    echo ""

    local render_file="$file" extracted=""
    if [[ "$file" == *.[Zz][Ii][Pp] ]]; then
        extracted=$(extract_zip_image "$file")
        if [[ -z "$extracted" || ! -f "$extracted" ]]; then
            echo "  (no image found inside this archive)"
            echo ""
            pause
            return
        fi
        render_file="$extracted"
    fi

    if [[ "$RENDER_MODE" == "s" && "$HAVE_SIXEL" -eq 1 ]]; then
        img2sixel -w "$((cols * 9))" "$render_file" 2>/dev/null \
            || echo "  (img2sixel failed to render this file)"
    elif [[ "$HAVE_CHAFA" -eq 1 ]]; then
        chafa --size="${cols}x$((lines - 4))" "$render_file" 2>/dev/null \
            || echo "  (chafa failed to render this file)"
    else
        echo "  (no working renderer for this mode)"
    fi
    [[ -n "$extracted" ]] && rm -f "$extracted"
    echo ""
    pause
}

toggle_renderer() {
    if [[ "$HAVE_CHAFA" -eq 1 && "$HAVE_SIXEL" -eq 1 ]]; then
        if [[ "$RENDER_MODE" == "c" ]]; then
            RENDER_MODE="s"
            echo "  Renderer: img2sixel (sixel terminals only)"
        else
            RENDER_MODE="c"
            echo "  Renderer: chafa (any terminal)"
        fi
        sleep 1
    fi
}

browse_gallery() {
    local label="$1" dir="$2"
    local images
    mapfile -t images < <(list_images "$dir")
    if [[ "${#images[@]}" -eq 0 ]]; then
        clear_screen
        echo "  $label — no images found."
        echo "  (looked in: $dir)"
        pause
        return
    fi

    local per_page=20 page=0 total="${#images[@]}"
    local total_pages=$(( (total + per_page - 1) / per_page ))
    while true; do
        clear_screen
        echo "  === $label ($total images) ==="
        echo ""
        local start=$(( page * per_page ))
        local end=$(( start + per_page ))
        [[ "$end" -gt "$total" ]] && end="$total"
        local i
        for (( i = start; i < end; i++ )); do
            printf '  %3d) %s\n' "$((i + 1))" "$(basename "${images[$i]}")"
        done
        echo ""
        echo "  Page $((page + 1))/$total_pages"
        echo "  [number]=view  N=next page  P=prev page  R=toggle renderer  Q=back"
        read -r -p "  > " choice || choice="q"
        case "$choice" in
            [Nn]) [[ "$((page + 1))" -lt "$total_pages" ]] && page=$((page + 1)) ;;
            [Pp]) [[ "$page" -gt 0 ]] && page=$((page - 1)) ;;
            [Rr]) toggle_renderer ;;
            [Qq]) return ;;
            ''|*[!0-9]*) : ;;
            *)
                if [[ "$choice" -ge 1 && "$choice" -le "$total" ]]; then
                    render_image "${images[$((choice - 1))]}"
                fi
                ;;
        esac
    done
}

main() {
    if [[ ! -f "$CONFIG" ]]; then
        clear_screen
        echo "  No galleries configured yet."
        echo "  (ask your sysop to add one under Admin -> Subsystems -> Galleries)"
        pause
        exit 0
    fi

    while true; do
        mapfile -t rows < <(load_galleries)
        clear_screen
        echo "  === Image Galleries ==="
        echo ""
        if [[ "${#rows[@]}" -eq 0 ]]; then
            echo "  No active galleries yet."
            echo ""
            echo "  Q=quit"
            read -r -p "  > " choice || choice="q"
            [[ "$choice" =~ ^[Qq]$ ]] && exit 0
            continue
        fi
        local i=1
        for row in "${rows[@]}"; do
            IFS=$'\t' read -r slug label gpath desc <<< "$row"
            if [[ -n "$desc" ]]; then
                printf '  %2d) %-28s  %s\n' "$i" "$label" "$desc"
            else
                printf '  %2d) %s\n' "$i" "$label"
            fi
            i=$((i + 1))
        done
        echo ""
        echo "  [number]=open  Q=quit"
        read -r -p "  > " choice || choice="q"
        if [[ "$choice" =~ ^[Qq]$ ]]; then
            exit 0
        elif [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#rows[@]} )); then
            IFS=$'\t' read -r slug label gpath desc <<< "${rows[$((choice - 1))]}"
            browse_gallery "$label" "$gpath"
        fi
    done
}

main
