#!/usr/bin/env bash
###############################################################################
# dnotes -- browse and create md-roam notes in ~/lit/slipbox with dmenu
###############################################################################

set -euo pipefail

NOTES_DIR="${NOTES_DIR:-$HOME/lit/slipbox}"
DMENU="${DMENU:-dmenu -i}"

NOTE_ICON="󱉟"
CREATE_ICON="+"

NOTE_TYPES=("evergreen" "paper" "person" "question" "letter" "lecture" "company")
NOTE_STATES=("active" "hidden")
NOTE_STATUSES=("rough" "growing" "evergreen" "abandoned")
NOTE_CERTAINTIES=("certain" "highly likely" "likely" "possible" "unlikely" "highly unlikely" "remote" "impossible")

trim() {
    sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
}

notify() {
    if command -v notify-send >/dev/null 2>&1; then
        notify-send "dnotes" "$1"
    fi
}

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

yaml_quote() {
    local value="$1"
    value="${value//\\/\\\\}"
    value="${value//\"/\\\"}"
    printf '"%s"' "$value"
}

inline_list() {
    local input="$1"
    input=$(printf '%s' "$input" | trim)
    if [ -z "$input" ]; then
        printf '[]'
    else
        printf '[%s]' "$input"
    fi
}

prompt_text() {
    local prompt="$1"
    local default="${2:-}"
    local value

    value=$(printf '%s' "$default" | $DMENU -p "$prompt") || exit 0
    printf '%s' "$value" | trim
}

choose_one() {
    local prompt="$1"
    local default="$2"
    shift 2

    local value
    value=$(printf '%s\n' "$@" | $DMENU -l "$#" -p "$prompt [$default]") || exit 0
    if [ -z "$value" ]; then
        printf '%s' "$default"
    else
        printf '%s' "$value"
    fi
}

prompt_int_range() {
    local prompt="$1"
    local min="$2"
    local max="$3"
    local default="$4"
    local value

    while true; do
        value=$(prompt_text "$prompt [$default]" "$default")
        [ -z "$value" ] && value="$default"
        if [[ "$value" =~ ^[0-9]+$ ]] && [ "$value" -ge "$min" ] && [ "$value" -le "$max" ]; then
            printf '%s' "$value"
            return
        fi
        notify "$prompt must be between $min and $max"
    done
}

prompt_timestamp() {
    local prefix="$1"
    local year month day hour minute second

    year=$(prompt_int_range "$prefix year" 1 9999 "$(date -u +%Y)")
    month=$(prompt_int_range "$prefix month" 1 12 "$(date -u +%m)")
    day=$(prompt_int_range "$prefix day" 1 31 "$(date -u +%d)")
    hour=$(prompt_int_range "$prefix hour UTC" 0 23 "$(date -u +%H)")
    minute=$(prompt_int_range "$prefix minute UTC" 0 59 "$(date -u +%M)")
    second=$(prompt_int_range "$prefix second UTC" 0 59 "$(date -u +%S)")

    if ! date -u -d "$year-$month-$day $hour:$minute:$second" >/dev/null 2>&1; then
        notify "Invalid date"
        prompt_timestamp "$prefix"
        return
    fi

    printf '%04d-%02d-%02dT%02d:%02d:%02dZ' "$year" "$month" "$day" "$hour" "$minute" "$second"
}

open_in_emacs() {
    local file="$1"
    local escaped="${file//\\/\\\\}"
    escaped="${escaped//\"/\\\"}"

    if emacsclient -e "(window-system)" 2>/dev/null | grep -q "x"; then
        emacsclient -e "(progn
            (split-window-right 80)
            (other-window 1)
            (find-file \"$escaped\")
            (when (fboundp 'kris/prose-wrap-mode) (kris/prose-wrap-mode))
            (raise-frame)
            (select-frame-set-input-focus (selected-frame))
            (when (fboundp 'evil-normal-state) (evil-normal-state)))" >/dev/null 2>&1 || emacsclient -n "$file"
    else
        emacs "$file" &
    fi
}

sync_note_in_emacs() {
    local file="$1"
    local escaped="${file//\\/\\\\}"
    escaped="${escaped//\"/\\\"}"

    emacsclient -e "(when (fboundp 'org-roam-db-update-file)
        (org-roam-db-update-file \"$escaped\"))" >/dev/null 2>&1 || true
}

extract_title() {
    local file="$1"
    awk '
        NR == 1 && $0 == "---" { fm = 1; next }
        fm && $0 == "---" { exit }
        fm && $0 ~ /^title:[[:space:]]*/ {
            sub(/^title:[[:space:]]*/, "")
            gsub(/^["'\'']|["'\'']$/, "")
            print
            exit
        }
    ' "$file"
}

create_note() {
    mkdir -p "$NOTES_DIR"

    local title slug default_slug type stime etime state status certainty importance preview linked_notes tags file

    title=$(prompt_text "Title")
    [ -z "$title" ] && exit 0

    default_slug=$(slugify "$title")
    slug=$(prompt_text "Slug" "$default_slug")
    [ -z "$slug" ] && slug="$default_slug"

    type=$(choose_one "Type" "evergreen" "${NOTE_TYPES[@]}")
    stime=$(prompt_timestamp "Created")

    if [ "$(choose_one "Set completion time?" "no" "no" "yes")" = "yes" ]; then
        etime=$(prompt_timestamp "Completed")
    else
        etime=""
    fi

    state=$(choose_one "State" "active" "${NOTE_STATES[@]}")
    status=$(choose_one "Status" "rough" "${NOTE_STATUSES[@]}")
    certainty=$(choose_one "Certainty" "possible" "${NOTE_CERTAINTIES[@]}")
    importance=$(prompt_int_range "Importance" 0 10 5)
    preview=$(prompt_text "Preview")
    linked_notes=$(prompt_text "Linked note slugs, comma-separated")
    tags=$(prompt_text "Tags, comma-separated")

    file="$NOTES_DIR/$slug.md"
    if [ -e "$file" ]; then
        notify "Note already exists: $slug.md"
        exit 1
    fi

    {
        printf '%s\n' '---'
        printf 'title: %s\n' "$(yaml_quote "$title")"
        printf 'slug: %s\n' "$slug"
        printf 'type: %s\n' "$type"
        printf 'stime: %s\n' "$stime"
        printf 'etime: %s\n' "$etime"
        printf 'state: %s\n' "$state"
        printf 'status: %s\n' "$status"
        printf 'certainty: %s\n' "$certainty"
        printf 'importance: %s\n' "$importance"
        printf 'preview: %s\n' "$(yaml_quote "$preview")"
        printf 'backlinks: 0\n'
        printf 'linkedNotes: %s\n' "$(inline_list "$linked_notes")"
        printf 'tags: %s\n' "$(inline_list "$tags")"
        printf '%s\n\n' '---'
    } > "$file"

    sync_note_in_emacs "$file"
    open_in_emacs "$file"
    notify "Created: $slug.md"
}

open_note() {
    local files menu choice slug file title

    files=$(find "$NOTES_DIR" -maxdepth 1 -type f -name '*.md' | sort)
    if [ -z "$files" ]; then
        notify "No notes in $NOTES_DIR"
        exit 1
    fi

    menu=""
    while IFS= read -r file; do
        slug=$(basename "$file" .md)
        title=$(extract_title "$file")
        [ -z "$title" ] && title="$slug"
        menu+="$title  [$slug]"$'\n'
    done <<< "$files"

    choice=$(printf '%s' "$menu" | $DMENU -l 20 -p "$NOTE_ICON  Open note") || exit 0
    [ -z "$choice" ] && exit 0

    slug=$(printf '%s' "$choice" | sed 's/.*\[//; s/\]$//')
    file="$NOTES_DIR/$slug.md"

    if [ -f "$file" ]; then
        open_in_emacs "$file"
    else
        notify "File not found: $slug.md"
        exit 1
    fi
}

main() {
    local choice

    choice=$(printf 'open\ncreate\n' | $DMENU -l 2 -p "$NOTE_ICON  Notes") || exit 0
    case "$choice" in
        open) open_note ;;
        create) create_note ;;
        *) exit 0 ;;
    esac
}

main "$@"
