#!/usr/bin/env bash
set -euo pipefail

###############################################################################
# TOME -- Content manager for krisyotam.com
#
# Maintainer:   Kris Yotam <krisyotam@pm.me>
# License:      MIT
# Created:      2026-05-18
# Description:  Unified content creator, editor, and metadata manager.
#               Three actions (create, edit, data) with two interfaces
#               (nnn/fzf TUI or dmenu).
#
# Usage:
#   tome -nnn -create      Create new content entry (TUI)
#   tome -dmenu -create    Create new content entry (dmenu)
#   tome -nnn -edit        Browse content, open in write (TUI)
#   tome -dmenu -edit      Browse content, open in write (dmenu)
#   tome -nnn -data        Edit content metadata (TUI)
#   tome -dmenu -data      Edit content metadata (dmenu)
###############################################################################

###############################################################################
# config
###############################################################################

DB="$HOME/dev/krisyotam.com/data/content.db"
CONTENT_DIR="$HOME/.corpus/content"
TIL_DIR="$HOME/.corpus/til"

# Menu entries (order matters -- displayed as-is)
# Indented entries are news publications
MENU_ENTRIES=(
    "papers"
    "essays"
    "blog"
    "diary"
    "reviews"
    "verse"
    "news"
    "  the-soapbox"
    "  field-notes"
    "  off-the-record"
    "til"
    "fiction"
    "ocs"
    "prayers"
    "progymnasmata"
)

# Types that are actual content directories
ALL_TYPES=(papers essays blog diary reviews verse news til fiction ocs prayers progymnasmata)

# News publications
NEWS_PUBS=("the-soapbox" "field-notes" "off-the-record")

# Diary has no status/confidence/importance columns
DIARY_FIELDS=(title preview category_slug state)
STANDARD_FIELDS=(title preview category_slug status confidence importance state)

VALID_STATUSES=("Notes" "Draft" "In Progress" "Finished" "Abandoned")
VALID_CONFIDENCES=(
    "certain" "highly likely" "likely" "possible"
    "unlikely" "highly unlikely" "remote" "impossible"
)
VALID_STATES=("active" "hidden")

# nerd font icon map
declare -A TYPE_ICONS=(
    [papers]="󰈙"    [essays]="󰬝"   [blog]="󰬞"
    [diary]="󰃮"     [reviews]="󰓥"  [verse]="󱗝"
    [news]="󰎕"      [fiction]="󰂺"  [ocs]="󰏫"
    [prayers]="󰥍"   [progymnasmata]="󰑴"  [til]="󰛄"
    [the-soapbox]="󰎕" [field-notes]="󰎕" [off-the-record]="󰎕"
)

###############################################################################
# helpers
###############################################################################

have_cmd() { command -v "$1" >/dev/null 2>&1; }
die()      { echo "ERROR: $1" >&2; exit 1; }
sql()      { sqlite3 "$DB" "$1"; }
notify()   { notify-send "tome" "$1" 2>/dev/null || true; }

slugify() {
    echo "$1" |
        tr '[:upper:]' '[:lower:]' |
        sed 's/[^a-z0-9 -]//g' |
        sed 's/  */ /g; s/ /-/g; s/--*/-/g; s/^-//; s/-$//'
}

check_slug_globally() {
    local slug="$1"
    for dir in "$CONTENT_DIR"/*/; do
        [ -d "$dir" ] || continue
        if [ -f "$dir/${slug}.mdx" ]; then
            echo "$(basename "$dir"):${slug}.mdx"
            return
        fi
    done
    # Also check til
    for subdir in "$TIL_DIR"/*/; do
        [ -d "$subdir" ] || continue
        if [ -f "$subdir/${slug}.mdx" ]; then
            echo "til/$(basename "$subdir"):${slug}.mdx"
            return
        fi
    done
}

# Get content directory for a type (or news publication)
content_dir_for() {
    local selection="$1"
    case "$selection" in
        the-soapbox|field-notes|off-the-record)
            echo "$CONTENT_DIR/news"
            ;;
        til)
            echo "$TIL_DIR"
            ;;
        *)
            echo "$CONTENT_DIR/$selection"
            ;;
    esac
}

# Resolve a menu selection to its content type (for DB operations)
db_type_for() {
    local selection="$1"
    case "$selection" in
        the-soapbox|field-notes|off-the-record) echo "news" ;;
        *) echo "$selection" ;;
    esac
}

today() { date +%Y-%m-%d; }

###############################################################################
# argument parsing
###############################################################################

UI_MODE=""
ACTION_MODE=""

for arg in "$@"; do
    case "$arg" in
        -nnn)    UI_MODE="nnn" ;;
        -dmenu)  UI_MODE="dmenu" ;;
        -create) ACTION_MODE="create" ;;
        -edit)   ACTION_MODE="edit" ;;
        -data)   ACTION_MODE="data" ;;
        *)
            echo "Unknown flag: $arg" >&2
            echo "Usage: tome -nnn|-dmenu -create|-edit|-data" >&2
            exit 1
            ;;
    esac
done

if [[ -z "$UI_MODE" || -z "$ACTION_MODE" ]]; then
    echo "Usage: tome -nnn|-dmenu -create|-edit|-data" >&2
    exit 1
fi

###############################################################################
# dependency checks
###############################################################################

check_deps_nnn() {
    have_cmd fzf     || die "fzf not found"
    have_cmd nnn     || die "nnn not found"
}

check_deps_dmenu() {
    have_cmd dmenu   || die "dmenu not found"
}

check_deps_data() {
    have_cmd sqlite3 || die "sqlite3 not found"
    [[ -f "$DB" ]]   || die "content.db not found at $DB"
}

###############################################################################
# menu rendering
###############################################################################

# Build the fzf menu string
fzf_menu() {
    for entry in "${MENU_ENTRIES[@]}"; do
        echo "$entry"
    done
    echo "quit"
}

# Build the dmenu menu string (with icons)
dmenu_menu() {
    for entry in "${MENU_ENTRIES[@]}"; do
        local trimmed="${entry## }"
        local indent="${entry%%[! ]*}"
        local icon="${TYPE_ICONS[$trimmed]:-󰈙}"
        echo "${indent}${icon}  ${trimmed}"
    done
}

# Parse a menu selection back to a clean type/publication name
parse_selection() {
    echo "$1" | sed 's/^[[:space:]]*//' | sed 's/^[^ ]* *//'
}

###############################################################################
# banners
###############################################################################

print_banner_main() {
    local cols
    cols="$(tput cols 2>/dev/null || echo 80)"

    if have_cmd figlet; then
        figlet -w "$cols" -f big "KRISYOTAM.COM" 2>/dev/null \
            || figlet -w "$cols" "KRISYOTAM.COM"
    else
        echo "KRISYOTAM.COM"
    fi

    echo
    echo "  est. 2025"
    echo "  author: Kris Yotam"
    echo "  script: tome"
    echo
    echo "  \"Do I contradict myself? Very well then I contradict myself,"
    echo "   (I am large, I contain multitudes.)\""
    echo "  -- Walt Whitman"
    echo
}

print_banner_data() {
    local cols
    cols="$(tput cols 2>/dev/null || echo 80)"

    if have_cmd figlet; then
        figlet -w "$cols" -f big "DATA" 2>/dev/null \
            || figlet -w "$cols" "DATA"
    else
        echo "=== DATA ==="
    fi

    echo
    echo "  Content metadata editor for krisyotam.com"
    echo
}

###############################################################################
# create: write frontmatter + .mdx file (pure shell)
###############################################################################

# Collect metadata and write the .mdx file
write_new_entry() {
    local selection="$1"
    local type
    type="$(db_type_for "$selection")"
    local target_dir
    target_dir="$(content_dir_for "$selection")"

    mkdir -p "$target_dir"

    # For news publications browsing into subdirectory is not needed
    # since all news lives flat in content/news/

    local title slug preview category status confidence importance
    local verse_type="" prayer_type="" form="" publication=""

    # Title
    if [[ "$UI_MODE" == "dmenu" ]]; then
        title="$(echo "" | dmenu -p "title:")"
    else
        read -rp "  Title: " title
    fi
    [[ -z "$title" ]] && return

    # Slug
    slug="$(slugify "$title")"
    local collision
    collision="$(check_slug_globally "$slug")"
    if [[ -n "$collision" ]]; then
        if [[ "$UI_MODE" == "dmenu" ]]; then
            notify "Slug collision: $slug ($collision)"
            slug="$(echo "$slug" | dmenu -p "collision! alt slug:")"
        else
            echo "  Slug collision: $slug ($collision)"
            read -rp "  Alt slug: " slug
        fi
        [[ -z "$slug" ]] && return
        slug="$(slugify "$slug")"
        collision="$(check_slug_globally "$slug")"
        if [[ -n "$collision" ]]; then
            if [[ "$UI_MODE" == "dmenu" ]]; then
                notify "Still colliding: $slug. Aborting."
            else
                echo "  Still colliding: $slug. Aborting."
            fi
            return
        fi
    fi

    # Preview
    if [[ "$UI_MODE" == "dmenu" ]]; then
        preview="$(echo "" | dmenu -p "preview:")"
    else
        read -rp "  Preview: " preview
    fi

    # Category
    check_deps_data
    if [[ "$UI_MODE" == "dmenu" ]]; then
        category="$(sql "SELECT slug FROM categories ORDER BY slug" | dmenu -i -l 20 -p "category:")"
    else
        category="$(sql "SELECT slug FROM categories ORDER BY slug" | fzf --prompt="category> " --height=20 --reverse)" || category=""
    fi
    [[ -z "$category" ]] && return

    # Status (skip for diary)
    if [[ "$type" == "diary" ]]; then
        status="" confidence="" importance=""
    else
        if [[ "$UI_MODE" == "dmenu" ]]; then
            status="$(printf "%s\n" "${VALID_STATUSES[@]}" | dmenu -i -l 5 -p "status:")"
            confidence="$(printf "%s\n" "${VALID_CONFIDENCES[@]}" | dmenu -i -l 8 -p "confidence:")"
            importance="$(seq 1 10 | dmenu -i -l 10 -p "importance:")"
        else
            status="$(printf "%s\n" "${VALID_STATUSES[@]}" | fzf --prompt="status> " --height=8 --reverse)" || status="Draft"
            confidence="$(printf "%s\n" "${VALID_CONFIDENCES[@]}" | fzf --prompt="confidence> " --height=10 --reverse)" || confidence="possible"
            read -rp "  Importance (1-10): " importance
        fi
        [[ -z "$status" ]] && status="Draft"
        [[ -z "$confidence" ]] && confidence="possible"
        [[ -z "$importance" ]] && importance="5"
    fi

    # Type-specific fields
    case "$type" in
        verse)
            if [[ "$UI_MODE" == "dmenu" ]]; then
                verse_type="$(printf 'haiku\nsonnet\nfree-verse\node\nlimerick\nvillanelle\nother' | dmenu -i -l 7 -p "verse type:")"
            else
                verse_type="$(printf 'haiku\nsonnet\nfree-verse\node\nlimerick\nvillanelle\nother' | fzf --prompt="verse type> " --height=10 --reverse)" || verse_type=""
            fi
            ;;
        prayers)
            if [[ "$UI_MODE" == "dmenu" ]]; then
                prayer_type="$(echo "" | dmenu -p "prayer type:")"
                form="$(echo "" | dmenu -p "form:")"
            else
                read -rp "  Prayer type: " prayer_type
                read -rp "  Form: " form
            fi
            ;;
        news)
            # Publication is determined by the menu selection
            case "$selection" in
                the-soapbox|field-notes|off-the-record)
                    publication="$selection"
                    ;;
                news)
                    # Selected the parent "news" -- ask which publication
                    if [[ "$UI_MODE" == "dmenu" ]]; then
                        publication="$(printf '%s\n' "${NEWS_PUBS[@]}" | dmenu -i -l 3 -p "publication:")"
                    else
                        publication="$(printf '%s\n' "${NEWS_PUBS[@]}" | fzf --prompt="publication> " --height=5 --reverse)" || publication="the-soapbox"
                    fi
                    [[ -z "$publication" ]] && publication="the-soapbox"
                    ;;
            esac
            ;;
    esac

    # Tags
    local tags=""
    if [[ "$UI_MODE" == "dmenu" ]]; then
        # Collect up to 5 tags
        local tag_list=()
        for _ in 1 2 3 4 5; do
            local tag
            tag="$(sql "SELECT slug FROM tags ORDER BY slug" | dmenu -i -l 20 -p "tag (empty=done):")" || break
            [[ -z "$tag" ]] && break
            tag_list+=("$tag")
        done
        tags="$(printf '%s\n' "${tag_list[@]}" 2>/dev/null | paste -sd',' -)"
    else
        echo "  Tags (comma-separated, or empty):"
        read -rp "  > " tags
    fi

    # Build the .mdx file
    local target_file="$target_dir/${slug}.mdx"
    local date
    date="$(today)"

    {
        echo "---"
        echo "title: \"$title\""
        echo "slug: $slug"
        echo "type: $type"
        echo "category: $category"
        echo "start_date: '$date'"
        echo "end_date: ''"
        echo "state: active"
        [[ -n "$preview" ]] && echo "preview: \"$preview\""

        if [[ "$type" != "diary" ]]; then
            echo "status: $status"
            echo "confidence: $confidence"
            echo "importance: $importance"
        fi

        # Type-specific fields
        [[ -n "$verse_type" ]] && echo "verse_type: $verse_type"
        [[ -n "$prayer_type" ]] && echo "prayer_type: $prayer_type"
        [[ -n "$form" ]] && echo "form: $form"
        [[ -n "$publication" ]] && echo "publication: $publication"

        # Tags as inline YAML array
        if [[ -n "$tags" ]]; then
            local tag_arr
            tag_arr="$(echo "$tags" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | awk '{printf "%s\"%s\"", (NR>1 ? ", " : ""), $0}')"
            echo "tags: [$tag_arr]"
        else
            echo "tags: []"
        fi

        echo "---"
        echo ""
    } > "$target_file"

    if [[ "$UI_MODE" == "dmenu" ]]; then
        notify "Created: $title ($slug)"
        # Open in write
        write "$target_file" &
        disown
    else
        echo
        echo "  Created: $target_file"
        echo
        read -rp "  Open in write? [Y/n] " yn
        case "$yn" in
            n|N) ;;
            *) write "$target_file" ;;
        esac
    fi
}

###############################################################################
# mode: -nnn -create
###############################################################################

run_nnn_create() {
    check_deps_nnn

    clear
    print_banner_main

    local selection
    selection="$(fzf_menu | fzf --prompt="create> " --height=20 --reverse)" || exit 0

    [[ "$selection" == "quit" ]] && exit 0

    # Trim whitespace (for indented news publications)
    selection="${selection## }"

    # If they selected "news" (parent), let them pick publication or create generic
    write_new_entry "$selection"
}

###############################################################################
# mode: -dmenu -create
###############################################################################

run_dmenu_create() {
    check_deps_dmenu

    local raw
    raw="$(dmenu_menu | dmenu -i -l 16 -p "create:")"
    [[ -z "$raw" ]] && exit 0

    local selection
    selection="$(parse_selection "$raw")"
    [[ -z "$selection" ]] && exit 0

    write_new_entry "$selection"
}

###############################################################################
# mode: -nnn -edit  (browse and open existing content in write)
###############################################################################

run_nnn_edit() {
    check_deps_nnn

    local nnn_write_helper="$HOME/.local/bin/nnn-write"
    [[ ! -x "$nnn_write_helper" ]] && die "nnn-write helper not found at $nnn_write_helper"

    clear
    print_banner_main

    while true; do
        local selection
        selection="$(fzf_menu | fzf --prompt="edit> " --height=20 --reverse)" || exit 0

        [[ "$selection" == "quit" ]] && exit 0

        selection="${selection## }"
        local target_dir
        target_dir="$(content_dir_for "$selection")"

        if [[ ! -d "$target_dir" ]]; then
            echo "Directory not found: $target_dir"
            read -rp "Press Enter..." _
            continue
        fi

        # For news publications, filter to only show matching files
        case "$selection" in
            the-soapbox|field-notes|off-the-record)
                # Show only files matching this publication
                local tmpdir
                tmpdir="$(mktemp -d)"
                # Symlink matching files into tmpdir for nnn browsing
                for f in "$target_dir"/*.mdx; do
                    [[ -f "$f" ]] || continue
                    if grep -q "^publication: $selection" "$f" 2>/dev/null; then
                        ln -s "$f" "$tmpdir/$(basename "$f")"
                    fi
                done
                NNN_OPENER="$nnn_write_helper" \
                NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \
                    nnn "$tmpdir"
                rm -rf "$tmpdir"
                ;;
            til)
                # TIL has subdirectories
                NNN_OPENER="$nnn_write_helper" \
                NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \
                    nnn "$target_dir"
                ;;
            *)
                NNN_OPENER="$nnn_write_helper" \
                NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \
                    nnn "$target_dir"
                ;;
        esac

        clear
        print_banner_main
    done
}

###############################################################################
# mode: -dmenu -edit
###############################################################################

run_dmenu_edit() {
    check_deps_dmenu

    local raw
    raw="$(dmenu_menu | dmenu -i -l 16 -p "edit:")"
    [[ -z "$raw" ]] && exit 0

    local selection
    selection="$(parse_selection "$raw")"
    [[ -z "$selection" ]] && exit 0

    local target_dir
    target_dir="$(content_dir_for "$selection")"
    [[ ! -d "$target_dir" ]] && { notify "Directory not found: $target_dir"; exit 1; }

    local file

    case "$selection" in
        the-soapbox|field-notes|off-the-record)
            # Filter news files by publication
            file="$(
                for f in "$target_dir"/*.mdx; do
                    [[ -f "$f" ]] || continue
                    if grep -q "^publication: $selection" "$f" 2>/dev/null; then
                        basename "$f" .mdx
                    fi
                done | sort | dmenu -i -l 20 -p "$selection:"
            )"
            [[ -z "$file" ]] && exit 0
            write "$target_dir/$file.mdx"
            ;;
        til)
            # TIL has subdirs -- first pick subdir, then file
            local subdir
            subdir="$(ls "$target_dir" | dmenu -i -l 10 -p "til topic:")"
            [[ -z "$subdir" ]] && exit 0
            file="$(ls "$target_dir/$subdir"/*.mdx 2>/dev/null | xargs -I{} basename {} .mdx | sort | dmenu -i -l 20 -p "$subdir:")"
            [[ -z "$file" ]] && exit 0
            write "$target_dir/$subdir/$file.mdx"
            ;;
        *)
            file="$(
                ls "$target_dir"/*.mdx 2>/dev/null |
                    xargs -I{} basename {} .mdx |
                    sort |
                    dmenu -i -l 20 -p "$selection:"
            )"
            [[ -z "$file" ]] && exit 0
            write "$target_dir/$file.mdx"
            ;;
    esac
}

###############################################################################
# metadata display (for -data mode)
###############################################################################

show_metadata() {
    local type="$1" slug="$2"

    local title preview category state tags id

    title="$(sql "SELECT title FROM $type WHERE slug='$slug'")"
    preview="$(sql "SELECT preview FROM $type WHERE slug='$slug'")"
    category="$(sql "SELECT category_slug FROM $type WHERE slug='$slug'")"
    state="$(sql "SELECT state FROM $type WHERE slug='$slug'")"

    id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"
    tags="$(sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" | paste -sd', ' -)"

    echo
    echo "  ┌─ $slug ─────────────────────────────────"
    printf "  │ %-14s %s\n" "Title:" "$title"
    printf "  │ %-14s %s\n" "Preview:" "${preview:0:60}"
    printf "  │ %-14s %s\n" "Category:" "$category"
    printf "  │ %-14s %s\n" "Tags:" "$tags"

    if [[ "$type" != "diary" ]]; then
        local status confidence importance
        status="$(sql "SELECT status FROM $type WHERE slug='$slug'")"
        confidence="$(sql "SELECT confidence FROM $type WHERE slug='$slug'")"
        importance="$(sql "SELECT importance FROM $type WHERE slug='$slug'")"
        printf "  │ %-14s %s\n" "Status:" "$status"
        printf "  │ %-14s %s\n" "Confidence:" "$confidence"
        printf "  │ %-14s %s\n" "Importance:" "$importance"
    fi

    if [[ "$type" == "news" ]]; then
        local pub
        pub="$(sql "SELECT publication FROM $type WHERE slug='$slug'")"
        printf "  │ %-14s %s\n" "Publication:" "$pub"
    fi

    printf "  │ %-14s %s\n" "State:" "$state"
    echo "  └──────────────────────────────────────────"
    echo
}

###############################################################################
# field editing -- nnn/fzf mode (for -data)
###############################################################################

edit_tags_nnn() {
    local type="$1" slug="$2"

    local id
    id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"

    echo
    echo "  Current tags:"
    sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" | while read -r tag; do
        echo "    - $tag"
    done

    echo
    echo "  Actions: [a]dd tag, [r]emove tag, [d]one"
    while true; do
        read -rp "  tag> " action
        case "$action" in
            a|add)
                local new_tag
                new_tag="$(
                    sql "SELECT slug FROM tags ORDER BY slug" |
                    fzf --prompt="add tag> " --height=20 --reverse
                )" || continue

                local existing
                existing="$(sql "SELECT COUNT(*) FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$new_tag')")"
                if [[ "$existing" -gt 0 ]]; then
                    echo "  Tag '$new_tag' already linked."
                    continue
                fi

                local tag_id
                tag_id="$(sql "SELECT id FROM tags WHERE slug='$new_tag'")"
                sql "INSERT INTO content_tags (content_type, content_id, tag_id) VALUES ('$type', $id, $tag_id)"
                echo "  Added tag: $new_tag"
                ;;
            r|remove)
                local rm_tag
                rm_tag="$(
                    sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" |
                    fzf --prompt="remove tag> " --height=10 --reverse
                )" || continue

                sql "DELETE FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$rm_tag')"
                echo "  Removed tag: $rm_tag"
                ;;
            d|done|q|quit)
                break
                ;;
            *)
                echo "  Unknown action. Use [a]dd, [r]emove, or [d]one."
                ;;
        esac
    done
}

edit_field_nnn() {
    local type="$1" slug="$2" field="$3"
    local new_value=""

    case "$field" in
        title|preview)
            local current
            current="$(sql "SELECT $field FROM $type WHERE slug='$slug'")"
            echo "  Current $field: $current"
            read -rp "  New $field: " new_value
            [[ -z "$new_value" ]] && return
            new_value="${new_value//\'/\'\'}"
            sql "UPDATE $type SET $field='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated $field."
            ;;
        category_slug)
            new_value="$(
                sql "SELECT slug FROM categories ORDER BY slug" |
                fzf --prompt="category> " --height=20 --reverse
            )" || return
            sql "UPDATE $type SET category_slug='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated category to: $new_value"
            ;;
        tags)
            edit_tags_nnn "$type" "$slug"
            ;;
        status)
            new_value="$(
                printf "%s\n" "${VALID_STATUSES[@]}" |
                fzf --prompt="status> " --height=10 --reverse
            )" || return
            sql "UPDATE $type SET status='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated status to: $new_value"
            ;;
        confidence)
            new_value="$(
                printf "%s\n" "${VALID_CONFIDENCES[@]}" |
                fzf --prompt="confidence> " --height=12 --reverse
            )" || return
            sql "UPDATE $type SET confidence='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated confidence to: $new_value"
            ;;
        importance)
            read -rp "  Importance (1-10): " new_value
            if [[ "$new_value" =~ ^[0-9]+$ ]] && (( new_value >= 1 && new_value <= 10 )); then
                sql "UPDATE $type SET importance=$new_value, updated_at=datetime('now') WHERE slug='$slug'"
                echo "  Updated importance to: $new_value"
            else
                echo "  Invalid. Must be 1-10."
            fi
            ;;
        publication)
            new_value="$(
                printf "%s\n" "${NEWS_PUBS[@]}" |
                fzf --prompt="publication> " --height=5 --reverse
            )" || return
            sql "UPDATE $type SET publication='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated publication to: $new_value"
            ;;
        state)
            new_value="$(
                printf "%s\n" "${VALID_STATES[@]}" |
                fzf --prompt="state> " --height=5 --reverse
            )" || return
            sql "UPDATE $type SET state='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated state to: $new_value"
            ;;
        *)
            echo "  Unknown field: $field"
            ;;
    esac
}

###############################################################################
# field editing -- dmenu mode (for -data)
###############################################################################

edit_field_dmenu() {
    local type="$1" slug="$2" field="$3"
    local current new_value

    case "$field" in
        title|preview)
            current="$(sql "SELECT $field FROM $type WHERE slug='$slug'")"
            new_value="$(echo "$current" | dmenu -p "$field:")"
            [[ -z "$new_value" ]] && return
            new_value="${new_value//\'/\'\'}"
            sql "UPDATE $type SET $field='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            notify "Updated $field"
            ;;
        category_slug)
            new_value="$(sql "SELECT slug FROM categories ORDER BY slug" | dmenu -i -l 15 -p "category:")"
            [[ -z "$new_value" ]] && return
            sql "UPDATE $type SET category_slug='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            notify "Category: $new_value"
            ;;
        tags)
            local id
            id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"
            local action
            action="$(printf "add\nremove" | dmenu -i -p "tags:")"
            [[ -z "$action" ]] && return

            if [[ "$action" == "add" ]]; then
                local tag
                tag="$(sql "SELECT slug FROM tags ORDER BY slug" | dmenu -i -l 20 -p "add tag:")"
                [[ -z "$tag" ]] && return
                local tag_id
                tag_id="$(sql "SELECT id FROM tags WHERE slug='$tag'")"
                [[ -z "$tag_id" ]] && return
                sql "INSERT OR IGNORE INTO content_tags (content_type, content_id, tag_id) VALUES ('$type', $id, $tag_id)"
                notify "Added tag: $tag"
            else
                local tag
                tag="$(sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id=t.id WHERE ct.content_type='$type' AND ct.content_id=$id ORDER BY t.slug" | dmenu -i -l 10 -p "remove:")"
                [[ -z "$tag" ]] && return
                sql "DELETE FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$tag')"
                notify "Removed tag: $tag"
            fi
            ;;
        status)
            new_value="$(printf "Notes\nDraft\nIn Progress\nFinished\nAbandoned" | dmenu -i -l 5 -p "status:")"
            [[ -z "$new_value" ]] && return
            sql "UPDATE $type SET status='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            notify "Status: $new_value"
            ;;
        confidence)
            new_value="$(printf "certain\nhighly likely\nlikely\npossible\nunlikely\nhighly unlikely\nremote\nimpossible" | dmenu -i -l 8 -p "confidence:")"
            [[ -z "$new_value" ]] && return
            sql "UPDATE $type SET confidence='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            notify "Confidence: $new_value"
            ;;
        importance)
            new_value="$(seq 1 10 | dmenu -i -l 10 -p "importance:")"
            [[ -z "$new_value" ]] && return
            sql "UPDATE $type SET importance=$new_value, updated_at=datetime('now') WHERE slug='$slug'"
            notify "Importance: $new_value"
            ;;
        publication)
            new_value="$(printf '%s\n' "${NEWS_PUBS[@]}" | dmenu -i -l 3 -p "publication:")"
            [[ -z "$new_value" ]] && return
            sql "UPDATE $type SET publication='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            notify "Publication: $new_value"
            ;;
        state)
            new_value="$(printf "active\nhidden" | dmenu -i -l 2 -p "state:")"
            [[ -z "$new_value" ]] && return
            sql "UPDATE $type SET state='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            notify "State: $new_value"
            ;;
    esac
}

###############################################################################
# entry selection helpers
###############################################################################

select_entry_fzf() {
    local type="$1" filter_pub="${2:-}"

    local query="SELECT title || '  (' || slug || ')' || char(9) || slug FROM $type WHERE state != '' "
    [[ -n "$filter_pub" ]] && query+="AND publication = '$filter_pub' "
    query+="ORDER BY title"

    sql "$query" |
    fzf --prompt="$type> " --delimiter=$'\t' --with-nth=1 \
        --preview="sqlite3 '$DB' \"SELECT 'Title: ' || title || char(10) || 'Preview: ' || COALESCE(preview,'') || char(10) || 'Category: ' || COALESCE(category_slug,'') || char(10) || 'State: ' || COALESCE(state,'active') FROM $type WHERE slug='{2}'\"" \
        --height=80% --reverse |
    awk -F'\t' '{print $2}'
}

select_entry_nnn_browse() {
    local type="$1"
    local content_path="$CONTENT_DIR/$type"

    [[ ! -d "$content_path" ]] && { echo ""; return; }

    local tmpfile
    tmpfile="$(mktemp)"

    NNN_TMPFILE="$tmpfile" nnn -p "$tmpfile" "$content_path"

    if [[ -s "$tmpfile" ]]; then
        local selected
        selected="$(cat "$tmpfile")"
        rm -f "$tmpfile"
        basename "$selected" .mdx
    else
        rm -f "$tmpfile"
        echo ""
    fi
}

###############################################################################
# mode: -nnn -data
###############################################################################

run_nnn_data() {
    check_deps_nnn
    check_deps_data

    clear
    print_banner_data

    while true; do
        local selection
        selection="$(fzf_menu | fzf --prompt="data> " --height=20 --reverse)" || exit 0

        [[ "$selection" == "quit" ]] && exit 0

        selection="${selection## }"
        local type
        type="$(db_type_for "$selection")"

        # Determine publication filter for news
        local pub_filter=""
        case "$selection" in
            the-soapbox|field-notes|off-the-record) pub_filter="$selection" ;;
        esac

        local mode
        mode="$(
            printf "%s\n" "fzf (search by title)" "nnn (browse files)" "back" |
            fzf --prompt="select via> " --height=5 --reverse
        )" || continue

        [[ "$mode" == "back" ]] && continue

        local slug=""
        if [[ "$mode" == *"fzf"* ]]; then
            slug="$(select_entry_fzf "$type" "$pub_filter")" || continue
        else
            slug="$(select_entry_nnn_browse "$type")" || continue
        fi

        [[ -z "$slug" ]] && continue

        local count
        count="$(sql "SELECT COUNT(*) FROM $type WHERE slug='$slug'")"
        if [[ "$count" -eq 0 ]]; then
            echo "  Slug '$slug' not found in $type table."
            read -rp "  Press Enter..." _
            continue
        fi

        while true; do
            clear
            print_banner_data
            show_metadata "$type" "$slug"

            local fields
            if [[ "$type" == "diary" ]]; then
                fields=("${DIARY_FIELDS[@]}" "tags" "done")
            elif [[ "$type" == "news" ]]; then
                fields=("${STANDARD_FIELDS[@]}" "publication" "tags" "done")
            else
                fields=("${STANDARD_FIELDS[@]}" "tags" "done")
            fi

            local field
            field="$(
                printf "%s\n" "${fields[@]}" |
                fzf --prompt="edit field> " --height=14 --reverse
            )" || break

            [[ "$field" == "done" ]] && break

            edit_field_nnn "$type" "$slug" "$field"
            read -rp "  Press Enter..." _
        done
    done
}

###############################################################################
# mode: -dmenu -data
###############################################################################

run_dmenu_data() {
    check_deps_dmenu
    check_deps_data

    local raw
    raw="$(dmenu_menu | dmenu -i -l 16 -p "data:")"
    [[ -z "$raw" ]] && exit 0

    local selection
    selection="$(parse_selection "$raw")"
    [[ -z "$selection" ]] && exit 0

    local type
    type="$(db_type_for "$selection")"

    # Publication filter for news subs
    local pub_filter=""
    case "$selection" in
        the-soapbox|field-notes|off-the-record) pub_filter="$selection" ;;
    esac

    # Select entry
    local query="SELECT title || '  [' || slug || ']' FROM $type "
    [[ -n "$pub_filter" ]] && query+="WHERE publication = '$pub_filter' "
    query+="ORDER BY title"

    local slug
    slug="$(
        sql "$query" |
            dmenu -i -l 20 -p "$type:" |
            sed 's/.*\[\(.*\)\]/\1/'
    )"
    [[ -z "$slug" ]] && exit 0

    # Edit loop
    while true; do
        local fields="title
preview
category_slug
tags
state"

        if [[ "$type" != "diary" ]]; then
            fields+="
status
confidence
importance"
        fi

        if [[ "$type" == "news" ]]; then
            fields+="
publication"
        fi

        local field
        field="$(echo "$fields" | dmenu -i -l 12 -p "field:")"
        [[ -z "$field" ]] && break
        edit_field_dmenu "$type" "$slug" "$field"
    done
}

###############################################################################
# dispatch
###############################################################################

case "${UI_MODE}-${ACTION_MODE}" in
    nnn-create)   run_nnn_create ;;
    dmenu-create) run_dmenu_create ;;
    nnn-edit)     run_nnn_edit ;;
    dmenu-edit)   run_dmenu_edit ;;
    nnn-data)     run_nnn_data ;;
    dmenu-data)   run_dmenu_data ;;
esac
