#!/bin/sh
# portnews - simple news manager for scratchpkg

# --------------------------------------------------------------------
# Configuration
# --------------------------------------------------------------------

NEWS_BASE=/var/lib/scratchpkg/news
NEWS_SOURCE=$NEWS_BASE/source
NEWS_UNREAD=$NEWS_BASE/unread
NEWS_READ=$NEWS_BASE/read
NEWS_IGNORE=$NEWS_BASE/ignore
PORT_NEWS=/usr/ports/main/NEWS

# --------------------------------------------------------------------
# Helpers
# --------------------------------------------------------------------
msg() {
	printf "==> %s\n" "$1"
}

die() {
    printf "%s\n" "$*" >&2
    exit 1
}

confirm() {
    printf "%s [y/N] " "$1"
    read -r ans || return 1
    case "$ans" in
        y|Y|yes|YES) return 0 ;;
    esac
    return 1
}

clean_news() {
	for dir; do
		rm -f "$dir"/*.news 2>/dev/null
	done
}

ensure_dirs() {
    mkdir -p "$NEWS_SOURCE" "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE" 2>/dev/null || {
        die "Cannot create directories. Run 'portnews sync' as root first."
    }
}

# Read metadata in ONE awk pass
# Output: date|title|severity
news_meta() {
    awk -F': *' -v fname="$(basename "$1")" '
        /^Date:/     { d=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", d) }
        /^Title:/    { t=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", t) }
        /^Severity:/ { s=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", s) }
        END {
            if (!d) d="????-??-??"
            if (!t) t=fname
            if (!s) s=""
            printf "%s|%s|%s\n", d, t, s
        }
    ' "$1"
}

# --------------------------------------------------------------------
# Listing
# --------------------------------------------------------------------

list_dir() {
    dir=$1
    i=1
    found=0

    for f in "$dir"/*.news; do
        [ -f "$f" ] || continue
        found=1

        meta=$(news_meta "$f")
        date=${meta%%|*}
        rest=${meta#*|}
        title=${rest%%|*}
        sev=${rest##*|}

        printf "[%d] %s  %s" "$i" "$date" "$title"
        [ -n "$sev" ] && printf " (%s)" "$sev"
        printf "\n"

        i=$((i + 1))
    done

    if [ "$found" -eq 0 ]; then
        msg "No news found."
    fi
}

get_nth_file() {
    dir=$1
    n=$2
    i=1

    for f in "$dir"/*.news; do
        [ -f "$f" ] || continue
        if [ "$i" -eq "$n" ]; then
            printf "%s\n" "$f"
            return 0
        fi
        i=$((i + 1))
    done
    return 1
}

# --------------------------------------------------------------------
# TUI
# --------------------------------------------------------------------
count_news() {
    _cn_dir=$1
    _cn_count=0
    [ -d "$_cn_dir" ] || { echo 0; return; }
    for _cn_f in "$_cn_dir"/*.news; do
        [ -f "$_cn_f" ] && _cn_count=$((_cn_count + 1))
    done
    echo "$_cn_count"
}

list_news_lines() {
    _lnl_category=$1
    case "$_lnl_category" in
        unread) _lnl_dir=$NEWS_UNREAD ;;
        read)   _lnl_dir=$NEWS_READ   ;;
        ignore) _lnl_dir=$NEWS_IGNORE ;;
        *)     return 1 ;;
    esac
    _lnl_i=1
    for _lnl_f in "$_lnl_dir"/*.news; do
        [ -f "$_lnl_f" ] || continue
        meta=$(news_meta "$_lnl_f")
        date=${meta%%|*}
        rest=${meta#*|}
        title=${rest%%|*}
        sev=${rest##*|}
        printf '[%d] %s  %s' "$_lnl_i" "$date" "$title"
        [ -n "$sev" ] && printf ' (%s)' "$sev"
        printf '\n'
        _lnl_i=$((_lnl_i + 1))
    done
}

save_tty() {
    stty_saved=
    [ -t 0 ] && stty_saved=$(stty -g 2>/dev/null) || true
}
restore_tty() {
    [ -n "$stty_saved" ] && stty "$stty_saved" 2>/dev/null || true
}
enter_raw() {
    [ -t 0 ] && stty raw -echo 2>/dev/null || true
}
leave_raw() {
    [ -t 0 ] && stty sane 2>/dev/null || true
}

read_key() {
    _rk_c1=$(dd bs=1 count=1 2>/dev/null) || true
    [ -z "$_rk_c1" ] && { echo 'quit'; return; }
    _rk_nl=$(printf '\n')
    _rk_cr=$(printf '\r')
    _rk_esc=$(printf '\033')
    if [ "$_rk_c1" = "$_rk_esc" ]; then
        _rk_seq=$(dd bs=1 count=2 2>/dev/null) || true
        case "$_rk_seq" in
            '[A') echo 'up'    ;;
            '[B') echo 'down'  ;;
            '[C') echo 'right' ;;
            '[D') echo 'left'  ;;
            *)    echo 'escape' ;;
        esac
    else
        case "$_rk_c1" in
            '') echo 'enter' ;;
            "$_rk_nl") echo 'enter' ;;
            "$_rk_cr") echo 'enter' ;;
            [qQ]) echo 'quit' ;;
            [jJ]) echo 'down' ;;
            [kK]) echo 'up' ;;
            [hH]) echo 'left' ;;
            [lL]) echo 'right' ;;
            [rR]) echo 'mark_read' ;;
            [iI]) echo 'mark_ignore' ;;
            [uU]) echo 'mark_unread' ;;
            *) echo "key:$_rk_c1" ;;
        esac
    fi
}

draw_list() {
    _dl_category=$1
    _dl_sel=$2
    _dl_list_text=$(list_news_lines "$_dl_category")
    _dl_width=$(tput cols 2>/dev/null) || _dl_width=80
    _dl_height=$(tput lines 2>/dev/null) || _dl_height=24
    tput clear 2>/dev/null
    tput cup 0 0 2>/dev/null
    printf '\033[7m portnews — %s \033[0m\n' "$_dl_category"
    _dl_row=1
    printf '%s\n' "$_dl_list_text" | while IFS= read -r _dl_line; do
        [ "$_dl_row" -gt $(( _dl_height - 3 )) ] && break
        tput cup "$_dl_row" 0 2>/dev/null
        if [ "$_dl_row" -eq "$_dl_sel" ]; then
            tput rev 2>/dev/null
            printf '%-*s' "$_dl_width" "$_dl_line"
            tput sgr0 2>/dev/null
        else
            printf '%-*s' "$_dl_width" "$_dl_line"
        fi
        _dl_row=$((_dl_row + 1))
    done
    if [ -z "$_dl_list_text" ]; then
        tput cup 1 0 2>/dev/null
        printf '(no items)'
    fi
    _dl_unread=$(count_news "$NEWS_UNREAD")
    _dl_read_c=$(count_news "$NEWS_READ")
    _dl_ignore_c=$(count_news "$NEWS_IGNORE")
    _dl_total=$((_dl_unread + _dl_read_c + _dl_ignore_c))
    tput cup $(( _dl_height - 2 )) 0 2>/dev/null
    printf '\033[7m Unread:%s Read:%s Ignore:%s Total:%s \033[0m\n' "$_dl_unread" "$_dl_read_c" "$_dl_ignore_c" "$_dl_total"
    tput cup $(( _dl_height - 1 )) 0 2>/dev/null
    printf ' j/k or Up/Down: Move selection  h/l or Left/Right: Previous/next tab (unread, read, ignore)  Enter:Open selected article r/i/u:Mark as read/ignore/unread q:Quit or back from article'
}

draw_article() {
    _da_file=$1
    _da_width=$(tput cols 2>/dev/null) || _da_width=80
    _da_height=$(tput lines 2>/dev/null) || _da_height=24
    tput clear 2>/dev/null
    tput cup 0 0 2>/dev/null
    printf '\033[7m %s \033[0m\n' "$(basename "$_da_file")"
    cat "$_da_file" 2>/dev/null | head -n $(( _da_height - 3 ))
    tput cup $(( _da_height - 1 )) 0 2>/dev/null
    printf '\033[7m r:read i:ignore u:unread q:back \033[0m'
}

run_tui() {
    category=unread
    sel=1

    ensure_dirs

    refresh_counts() {
        list_text=$(list_news_lines "$category")
        if [ -z "$list_text" ]; then
            item_count=0
        else
            item_count=$(printf '%s\n' "$list_text" | wc -l | tr -d ' ')
        fi
        if [ "$item_count" -eq 0 ]; then
            sel=0
        else
            [ "$sel" -gt "$item_count" ] && sel=$item_count
            [ "$sel" -lt 1 ] && sel=1
        fi
    }

    save_tty
    trap 'restore_tty; leave_raw; tput clear 2>/dev/null; tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
    tput civis 2>/dev/null

    while true; do
        refresh_counts
        draw_list "$category" "$sel"
        enter_raw
        key=$(read_key)
        leave_raw

        case "$key" in
            up)
                [ "$sel" -gt 1 ] && sel=$((sel - 1))
                ;;
            down)
                [ "$sel" -lt "$item_count" ] && sel=$((sel + 1))
                ;;
            left|right)
                if [ "$key" = left ]; then
                    case "$category" in
                        unread) category=ignore ;;
                        read)   category=unread ;;
                        ignore) category=read ;;
                    esac
                else
                    case "$category" in
                        unread) category=read ;;
                        read)   category=ignore ;;
                        ignore) category=unread ;;
                    esac
                fi
                sel=1
                ;;
            enter)
                if [ "$sel" -ge 1 ] && [ "$sel" -le "$item_count" ]; then
                    run_article_view "$category" "$sel"
                fi
                ;;
            mark_read|mark_ignore|mark_unread)
                if [ "$sel" -ge 1 ] && [ "$sel" -le "$item_count" ]; then
                    dest=${key#mark_}
                    [ "$dest" != "$category" ] && { set -- "$category" "$sel" "$dest"; cmd_mv "$@"; }
                fi
                ;;
            quit)
                exit 0
                ;;
        esac
    done
}

run_article_view() {
    _rav_category=$1
    _rav_idx=$2
    case "$_rav_category" in
        unread) _rav_dir=$NEWS_UNREAD ;;
        read)   _rav_dir=$NEWS_READ   ;;
        ignore) _rav_dir=$NEWS_IGNORE ;;
        *)     return 1 ;;
    esac
    _rav_file=$(get_nth_file "$_rav_dir" "$_rav_idx")
    [ -z "$_rav_file" ] && return 1
    [ -f "$_rav_file" ] || return 1

    while true; do
        draw_article "$_rav_file"
        enter_raw
        key=$(read_key)
        leave_raw
        case "$key" in
            quit|escape)
                return 0
                ;;
            mark_read)
                set -- "$_rav_category" "$_rav_idx" read
                cmd_mv "$@"
                return 0
                ;;
            mark_ignore)
                set -- "$_rav_category" "$_rav_idx" ignore
                cmd_mv "$@"
                return 0
                ;;
            mark_unread)
                set -- "$_rav_category" "$_rav_idx" unread
                cmd_mv "$@"
                return 0
                ;;
        esac
    done
}

# --------------------------------------------------------------------
# Commands
# --------------------------------------------------------------------

cmd_list_unread() {
    ensure_dirs
    list_dir "$NEWS_UNREAD"
}

cmd_list_read() {
    ensure_dirs
    list_dir "$NEWS_READ"
}

cmd_list_ignore() {
    ensure_dirs
    list_dir "$NEWS_IGNORE"
}

cmd_cat() {
    ensure_dirs
    
    # Determine category (default: unread)
    category="unread"
    case "$1" in
        read|ignore|unread)
            category="$1"
            shift
            ;;
    esac
    
    # Determine directory based on category
    case "$category" in
        read)   dir=$NEWS_READ ;;
        ignore) dir=$NEWS_IGNORE ;;
        unread) dir=$NEWS_UNREAD ;;
    esac

    # Validate index
    case "$1" in
        ''|*[!0-9]*) die "Invalid news index: must be a number" ;;
    esac

    file=$(get_nth_file "$dir" "$1") || die "Invalid news index: $1"
    
    if [ ! -f "$file" ]; then
        die "News file not found"
    fi
    
    cat "$file"
    printf "\n"
    
    # Only offer to mark as read if in unread category
    if [ "$category" = "unread" ]; then
        if confirm "Mark this news as read?"; then
            mv "$file" "$NEWS_READ"/ || die "Failed to mark news as read"
            msg "News marked as read."
        fi
    fi
}

cmd_mv() {
    ensure_dirs
    
    source=""
    index=""
    dest=""
    
    # Validate minimum arguments
    if [ -z "$1" ] || [ -z "$2" ]; then
        die "Usage: portnews mv [source] {N|all} {read|ignore|unread}"
    fi
    
    # Detect full form (3 args) vs short form (2 args)
    if [ -n "$3" ]; then
        # Full form: mv <source> <index> <destination>
        source=$1
        index=$2
        dest=$3
    else
        # Check for syntax error (e.g., "mv read ignore" without index)
        case "$1" in
            read|ignore|unread)
                die "Missing index. Usage: portnews mv [source] {N|all} {read|ignore|unread}"
                ;;
        esac
        
        # Short form: mv <index> <destination> (source=unread)
        source="unread"
        index=$1
        dest=$2
    fi

    # Validate categories
    case "$source" in
        read)   source_dir=$NEWS_READ ;;
        ignore) source_dir=$NEWS_IGNORE ;;
        unread) source_dir=$NEWS_UNREAD ;;
        *) die "Invalid source: $source (use 'read', 'ignore', or 'unread')" ;;
    esac

    case "$dest" in
        read)   dest_dir=$NEWS_READ ;;
        ignore) dest_dir=$NEWS_IGNORE ;;
        unread) dest_dir=$NEWS_UNREAD ;;
        *) die "Invalid destination: $dest (use 'read', 'ignore', or 'unread')" ;;
    esac

    # Validate that source and destination are different
    if [ "$source" = "$dest" ]; then
        die "Cannot move from $source to $dest (same category)"
    fi

    # Process "all"
    if [ "$index" = "all" ]; then
        moved=0
        for f in "$source_dir"/*.news; do
            [ -f "$f" ] || continue
            mv "$f" "$dest_dir"/ || die "Failed to move $f"
            moved=$((moved + 1))
        done
        if [ "$moved" -eq 0 ]; then
            msg "No $source news to move to $dest."
        else
            msg "Moved $moved news from $source to $dest."
        fi
        return 0
    fi

    # Validate numeric index
    case "$index" in
        ''|*[!0-9]*) die "Invalid news index: must be a number or 'all'" ;;
    esac

    # Get file
    file=$(get_nth_file "$source_dir" "$index") || die "Invalid news index: $index in $source"
    
    if [ ! -f "$file" ]; then
        die "News file not found"
    fi
    
    mv "$file" "$dest_dir"/ || die "Failed to move news file"
    msg "Moved news from $source to $dest."
}

cmd_rm() {
    ensure_dirs
    category=$1
    target=$2

    if [ -z "$category" ] || [ -z "$target" ]; then
        die "Usage: portnews rm {read|ignore|unread} {N|all}"
    fi

    case "$category" in
        read)   dir=$NEWS_READ ;;
        ignore) dir=$NEWS_IGNORE ;;
        unread) dir=$NEWS_UNREAD ;;
        *) die "Unknown category: $category (use 'read', 'ignore', or 'unread')" ;;
    esac

    if [ "$target" = "all" ]; then
        count=0
        for f in "$dir"/*.news; do
            [ -f "$f" ] && count=$((count + 1))
        done

        if [ "$count" -eq 0 ]; then
            msg "No $category news to remove."
            return 0
        fi

        confirm "Remove all $count $category news?" || return 0

        removed=0
        for f in "$dir"/*.news; do
            [ -f "$f" ] || continue
            rm -f "$f" && removed=$((removed + 1))
        done
        msg "Removed $removed $category news."
        return 0
    fi

    case "$target" in
        ''|*[!0-9]*) die "Invalid news index: must be a number" ;;
    esac

    file=$(get_nth_file "$dir" "$target") || die "Invalid news index: $target"
    
    if [ ! -f "$file" ]; then
        die "News file not found"
    fi

    basename_file=$(basename "$file")
    confirm "Remove '$basename_file'?" || return 0
    
    rm -f "$file" || die "Failed to remove news file"
    msg "Removed: $basename_file"
}

cmd_restore() {
    ensure_dirs

    confirm "This will restore all news to unread and remove read/ignore." || exit 0

    if [ ! -d "$NEWS_SOURCE" ]; then
        die "Error: source directory not found at $NEWS_SOURCE"
    fi

    count=0
    for f in "$NEWS_SOURCE"/*.news; do
        [ -f "$f" ] && count=$((count + 1))
    done

    if [ "$count" -eq 0 ]; then
        die "Error: no news found in source directory"
    fi

    # Remove all .news files from the directories instead of removing directories
    clean_news "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE"

    for f in "$NEWS_SOURCE"/*.news; do
        [ -f "$f" ] || continue
        cp "$f" "$NEWS_UNREAD"/ || die "Failed to copy $f"
        chmod 666 "$NEWS_UNREAD/$(basename "$f")" 2>/dev/null
    done

    msg "Restored $count news items to unread from source."
}

cmd_status() {
    ensure_dirs

    # Helper function to count news in a directory
    count_news() {
        dir=$1
        count=0
        [ -d "$dir" ] && {
            for f in "$dir"/*.news; do
                [ -f "$f" ] && count=$((count + 1))
            done
        }
        printf "%d" "$count"
    }

#    source_count=$(count_news "$NEWS_SOURCE")
    unread_count=$(count_news "$NEWS_UNREAD")
    read_count=$(count_news "$NEWS_READ")
    ignore_count=$(count_news "$NEWS_IGNORE")
    total_local=$((unread_count + read_count + ignore_count))

#    printf " Source:   %d\n" "$source_count"
    printf " Unread:   %d\n" "$unread_count"
    printf " Read:     %d\n" "$read_count"
    printf " Ignore:   %d\n" "$ignore_count"
    printf " TOTAL:    %d\n" "$total_local"
}

cmd_sync() {
    # Must run as root to create directories and set permissions
    if [ "$(id -u)" -ne 0 ]; then
        die "sync command requires root privileges"
    fi

    # Create base directory structure
    mkdir -p "$NEWS_BASE" "$NEWS_SOURCE" "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE" || \
        die "Failed to create news directories"

    # Set permissions so regular users can manage news
    # 755 for base, 777 for subdirectories (world-writable with sticky bit would be better but simpler this way)
    chmod 755 "$NEWS_BASE" || die "Failed to set permissions on $NEWS_BASE"
    chmod 777 "$NEWS_SOURCE" "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE" || \
        die "Failed to set permissions on news subdirectories"

    if [ ! -d "$PORT_NEWS" ]; then
        msg "Ports news directory not found at $PORT_NEWS"
        msg "News directory structure created."
        return 0
    fi

    # Count news in ports
    ports_count=0
    for f in "$PORT_NEWS"/*.news; do
        [ -f "$f" ] && ports_count=$((ports_count + 1))
    done

    if [ "$ports_count" -eq 0 ]; then
        msg "No news found in $PORT_NEWS"
        rm -f "$NEWS_SOURCE"/*.news 2>/dev/null
        msg "News directory structure created."
        return 0
    fi

    # Full sync: remove all and copy again
    # This ensures deleted news from repo are also removed from source
    rm -f "$NEWS_SOURCE"/*.news 2>/dev/null

    # Copy all news from ports
    copied=0
    for f in "$PORT_NEWS"/*.news; do
        [ -f "$f" ] || continue
        cp "$f" "$NEWS_SOURCE"/ || die "Failed to copy $f"
        chmod 666 "$NEWS_SOURCE/$(basename "$f")" 2>/dev/null
        copied=$((copied + 1))
    done

    # Detect new news (not in unread, read, or ignore)
    new_count=0
    for f in "$NEWS_SOURCE"/*.news; do
        [ -f "$f" ] || continue
        base=$(basename "$f")
        
        if [ ! -f "$NEWS_UNREAD/$base" ] && \
           [ ! -f "$NEWS_READ/$base" ] && \
           [ ! -f "$NEWS_IGNORE/$base" ]; then
            cp "$f" "$NEWS_UNREAD"/ || die "Failed to copy $f to unread"
            chmod 666 "$NEWS_UNREAD/$base" 2>/dev/null
            new_count=$((new_count + 1))
        fi
    done

    # Remove orphaned news (no longer in source)
    orphan_count=0
    for d in "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE"; do
        [ -d "$d" ] || continue
        for f in "$d"/*.news; do
            [ -f "$f" ] || continue
            base=$(basename "$f")
            if [ ! -f "$NEWS_SOURCE/$base" ]; then
                rm -f "$f" && orphan_count=$((orphan_count + 1))
            fi
        done
    done

    msg "Synced $copied news from ports." 
    [ "$new_count" -gt 0 ] && msg "Added $new_count new news to unread."
    [ "$orphan_count" -gt 0 ] && msg "Removed $orphan_count orphaned news."
    [ "$new_count" -eq 0 ] && [ "$orphan_count" -eq 0 ] && msg "No changes detected."
    
    return 0
}

cmd_check() {
    ensure_dirs

    if [ ! -d "$PORT_NEWS" ]; then
        return 1
    fi

    # Detect new news without modifying anything
    new_count=0
    for f in "$PORT_NEWS"/*.news; do
        [ -f "$f" ] || continue
        base=$(basename "$f")
        
        if [ ! -f "$NEWS_UNREAD/$base" ] && \
           [ ! -f "$NEWS_READ/$base" ] && \
           [ ! -f "$NEWS_IGNORE/$base" ]; then
            new_count=$((new_count + 1))
        fi
    done

    return "$new_count"
}

cmd_purge() {
    ensure_dirs

    case "$1" in
        orphan)
            removed=0
            for d in "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE"; do
                [ -d "$d" ] || continue
                for f in "$d"/*.news; do
                    [ -f "$f" ] || continue
                    base=$(basename "$f")
                    if [ ! -f "$NEWS_SOURCE/$base" ]; then
                        rm -f "$f" && removed=$((removed + 1))
                    fi
                done
            done
            msg "Removed $removed orphaned news items."
            ;;
        all)
            # Count total news
            total=0
            for d in "$NEWS_SOURCE" "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE"; do
                [ -d "$d" ] || continue
                for f in "$d"/*.news; do
                    [ -f "$f" ] && total=$((total + 1))
                done
            done

            if [ "$total" -eq 0 ]; then
                msg "No news to purge."
                return 0
            fi

            confirm "This will delete ALL news data ($total files). Continue?" || return 0

            # Remove all .news files
	    clean_news "$NEWS_SOURCE" "$NEWS_UNREAD" "$NEWS_READ" "$NEWS_IGNORE"

            msg "Purged all news data ($total files removed)."
            msg "Run 'doas portnews sync' to re-sync from ports."
            ;;
        *)
            die "Unknown purge target: $1 (use 'orphan' or 'all')"
            ;;
    esac
}

cmd_create() {
    name=$1
    [ -n "$name" ] || die "Usage: portnews create NAME"
    
    # Validate name (only letters, numbers, hyphens and underscores)
    case "$name" in
        *[!/a-zA-Z0-9_-]*) 
            die "Invalid name: use only letters, numbers, hyphens and underscores"
            ;;
    esac

    # Generate filename: YYYY-MM-DD-name.news
    date_str=$(date +%Y-%m-%d)
    filename="${date_str}-${name}.news"
    
    # Check if file already exists
    if [ -f "$filename" ]; then
        die "File already exists: $filename"
    fi
    
    # Convert name to title case for display
    title=$(echo "$name" | tr '-' ' ' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')

    # Create the file
    cat > "$filename" <<EOF
Title: $title
Date: $date_str
Author: Venom Linux Team 

Severity: low | medium | high
Summary: Brief description here

Details:
Write detailed information here.

Action Required:
- List any actions users need to take
- Include commands if applicable

References:
- Link to documentation or related resources
EOF

    msg "News file created: $filename"
    msg "Edit it and copy to /usr/ports/main/NEWS/ when ready."
}

help() {
    cat <<EOF
portnews - system news manager for scratchpkg

Usage: run 'portnews' or 'scratch news' wrapper
  portnews                     Interactive news manager
  portnews status              Show news statistics
  portnews unread              List unread news
  portnews read                List read news
  portnews ignore              List ignore news
  portnews cat N               Show unread news item N
  portnews cat read N          Show read news item N
  portnews cat ignore N        Show ignore news item N
  portnews N                   Show unread news item N (shortcut)
  portnews mv N <dst>          Move unread news N to destination category
  portnews mv all <dst>        Move all unread news to destination
  portnews mv <src> N <dst>    Move news N from source to destination category
  portnews mv <src> all <dst>  Move all news from source to destination
  portnews rm <cat> N          Remove news N from read|ignore|unread
  portnews rm <cat> all        Remove all news from read|ignore|unread
  portnews sync                Sync news from ports repository (requires root)
  portnews restore             Restore all news to unread from source
  portnews purge orphan        Remove local news not in source
  portnews purge all           Remove all news data (source, unread, read, ignore)
  portnews create NAME         Create news template file
  portnews help                Show this help

Interactive TUI:
  j / k or Up/Down    Move selection
  h / l or Left/Right Previous / next tab (unread, read, ignore)
  Enter               Open selected article
  r / i / u           Mark as read / ignore / unread
  q                   Quit or back from article

Directories:
  $PORT_NEWS     Ports repository news (from git)
  $NEWS_SOURCE   Local synced copy (read-only)
  $NEWS_UNREAD   Unread news (user writable)
  $NEWS_READ     Read news (user writable)
  $NEWS_IGNORE   Ignore news (user writable)

Workflow:
  1. News are maintained in git at $PORT_NEWS
  2. Run 'doas portnews sync' to sync from ports (first time as root)
  3. Regular users can read and manage news without root
  4. 'restore' resets all from source dir news if needed

Examples:
  doas portnews sync             # Sync from ports repo (first time setup)
  portnews                       # Start news manager
  portnews unread                # List all unread news
  portnews 1                     # Show first unread news item
  portnews cat 1                 # Show first unread news item
  portnews cat read 1            # Show first read news item
  portnews cat ignore 2          # Show second ignored news item
  portnews mv 1 read             # Move unread #1 to read (short form)
  portnews mv all ignore         # Ignore all unread news (short form)
  portnews mv read 2 unread      # Move read #2 back to unread (full form)
  portnews mv ignore all unread  # Move all ignored back to unread (full form)
  portnews rm read 1             # Remove first read news
  portnews rm read all           # Remove all read news
  portnews read                  # List read news
  portnews ignore                # List ignore news
  portnews restore               # Restore all news from source
  portnews purge all             # Remove all news data
  portnews create icu-update     # Create news template
EOF
}

# --------------------------------------------------------------------
# Main
# --------------------------------------------------------------------
# No arguments run TUI mode
if [ "$#" -eq 0 ]; then
    if [ -t 0 ] && [ -t 1 ]; then
        run_tui
        exit 0
    fi
    # List unread when not a TTY
fi

cmd=$1
if [ "$#" -gt 0 ]; then
    shift
fi

case "$cmd" in
    ""|unread)
        cmd_list_unread
        ;;
    read)
        cmd_list_read
        ;;
    ignore)
        cmd_list_ignore
        ;;
    cat)
        if [ -z "$1" ]; then
            die "Usage: portnews cat [read|ignore|unread] N"
        else
            cmd_cat "$@"
        fi
        ;;
    mv|move)
        cmd_mv "$@"
        ;;
    rm|remove)
        cmd_rm "$@"
        ;;
    status)
        cmd_status
        ;;
    sync)
        cmd_sync
        ;;
    restore)
        cmd_restore
        ;;
    purge)
        cmd_purge "$@"
        ;;
    create)
        cmd_create "$@"
        ;;
    help|-h|--help)
        help
        ;;
    *)
        # numeric shortcut: portnews 3 == portnews cat 3
        case "$cmd" in
            *[!0-9]*)
                die "Unknown command: $cmd (try 'portnews help')"
                ;;
            *)
                cmd_cat "$cmd"
                ;;
        esac
        ;;
esac
