#!/usr/bin/env bash
###############################################################################
# dclip — Clipboard History Manager via dmenu
#
# Maintainer:   Kris Yotam <krisyotam@pm.me>
# License:      MIT
# Created:      2026-02-15
# Based on:     BreadOnPenguins/scripts (img-text-clipboard-history)
#
# Refactored improvements:
#   - Text clips stored IN files (not as filenames)
#   - dmenu interface instead of fzf/st
#   - Proper multiline text handling
#   - Configurable max history size
#   - Image previews via notify-send
#   - Deduplication of text clips
#
# Usage:
#   dclip              # Browse and re-copy from history
#   dclip add          # Capture current selection to history
#   dclip copy-last    # Re-copy most recent clip
#   dclip out <cmd>    # Pipe command output to clipboard + history
#   dclip link <url>   # Download image URL to history
#   dclip clear        # Clear all history
#   dclip wipe <n>     # Delete entry by number
#
# Bind suggestions (dwm/sxhkd):
#   Super+v         → dclip          (browse history)
#   Super+Shift+v   → dclip add      (capture selection)
###############################################################################

HIST_DIR="${HOME}/.cache/dclip"
TEXT_DIR="${HIST_DIR}/text"
IMG_DIR="${HIST_DIR}/img"
INDEX_FILE="${HIST_DIR}/index"
MAX_HISTORY=200

mkdir -p "$TEXT_DIR" "$IMG_DIR"
touch "$INDEX_FILE"

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

timestamp() {
    date '+%s'
}

human_date() {
    date -d "@$1" '+%b %d %H:%M' 2>/dev/null || date '+%b %d %H:%M'
}

# Truncate text for dmenu display (single line, max 80 chars)
truncate_text() {
    echo "$1" | tr '\n' ' ' | cut -c1-80
}

# Prune history to MAX_HISTORY entries
prune() {
    local count
    count=$(wc -l < "$INDEX_FILE")
    if [ "$count" -gt "$MAX_HISTORY" ]; then
        local excess=$((count - MAX_HISTORY))
        # Get entries to remove (oldest first)
        head -n "$excess" "$INDEX_FILE" | while IFS='|' read -r ts type path; do
            [ -f "$path" ] && rm -f "$path"
        done
        # Keep only recent entries
        tail -n "$MAX_HISTORY" "$INDEX_FILE" > "${INDEX_FILE}.tmp"
        mv "${INDEX_FILE}.tmp" "$INDEX_FILE"
    fi
}

###############################################################################
# add — capture clipboard/selection to history
###############################################################################

add() {
    # Check for image first
    local targets
    targets=$(xclip -selection clipboard -o -t TARGETS 2>/dev/null)

    if echo "$targets" | grep -q 'image/png'; then
        local fname
        fname="$(timestamp).png"
        xclip -selection clipboard -t image/png -o > "${IMG_DIR}/${fname}"
        echo "$(timestamp)|img|${IMG_DIR}/${fname}" >> "$INDEX_FILE"
        notify-send -i "${IMG_DIR}/${fname}" " Clipped image"
    else
        # Copy primary selection to clipboard, then save
        local text
        text=$(xclip -o -selection primary 2>/dev/null | xclip -i -f -selection clipboard 2>/dev/null)

        if [ -z "$text" ]; then
            text=$(xclip -o -selection clipboard 2>/dev/null)
        fi

        [ -z "$text" ] && return

        # Deduplicate: check if this exact text already exists
        local hash
        hash=$(echo "$text" | md5sum | cut -d' ' -f1)
        local existing
        existing="${TEXT_DIR}/${hash}.txt"

        if [ -f "$existing" ]; then
            # Move to top of index (remove old entry, add new)
            grep -v "|${existing}$" "$INDEX_FILE" > "${INDEX_FILE}.tmp"
            mv "${INDEX_FILE}.tmp" "$INDEX_FILE"
        fi

        printf '%s' "$text" > "$existing"
        echo "$(timestamp)|txt|${existing}" >> "$INDEX_FILE"
        notify-send " Clipped" "$(truncate_text "$text")"
    fi

    prune
}

###############################################################################
# out — pipe command output to clipboard + history
###############################################################################

out() {
    shift  # remove 'out' arg
    local text
    text=$("$@" 2>&1)
    printf '%s' "$text" | xclip -i -selection clipboard
    local hash
    hash=$(echo "$text" | md5sum | cut -d' ' -f1)
    printf '%s' "$text" > "${TEXT_DIR}/${hash}.txt"
    echo "$(timestamp)|txt|${TEXT_DIR}/${hash}.txt" >> "$INDEX_FILE"
    notify-send " Output clipped" "$(truncate_text "$text")"
    prune
}

###############################################################################
# link — download image URL to history
###############################################################################

link() {
    local url="$2"
    [ -z "$url" ] && { notify-send "dclip" "No URL provided"; return 1; }
    local fname
    fname="$(timestamp).png"
    if wget -q "$url" -O "${IMG_DIR}/${fname}"; then
        echo "$(timestamp)|img|${IMG_DIR}/${fname}" >> "$INDEX_FILE"
        notify-send -i "${IMG_DIR}/${fname}" " Image saved"
    else
        rm -f "${IMG_DIR}/${fname}"
        notify-send "dclip" "Download failed"
    fi
}

###############################################################################
# sel — browse history via dmenu and re-copy
###############################################################################

sel() {
    [ ! -s "$INDEX_FILE" ] && { notify-send "dclip" "History empty"; exit 0; }

    # Build dmenu list (newest first)
    local menu=""
    local -a entries=()
    local i=0

    while IFS='|' read -r ts type path; do
        [ -f "$path" ] || continue
        local display
        if [ "$type" = "img" ]; then
            local fname
            fname=$(basename "$path")
            display="  ${fname}"
        else
            local content
            content=$(cat "$path" 2>/dev/null)
            display="  $(truncate_text "$content")"
        fi
        entries+=("$i|$type|$path")
        menu+="${display}"$'\n'
        i=$((i + 1))
    done < <(tac "$INDEX_FILE")

    [ -z "$menu" ] && { notify-send "dclip" "History empty"; exit 0; }

    # Show dmenu
    local choice
    choice=$(printf '%s' "$menu" | sed '/^$/d' | dmenu -i -l 20 -p "  Clipboard")
    [ -z "$choice" ] && exit 0

    # Find the matching entry by line number
    local line_num
    line_num=$(printf '%s' "$menu" | sed '/^$/d' | grep -nF "$choice" | head -1 | cut -d: -f1)
    [ -z "$line_num" ] && exit 0

    local idx=$((line_num - 1))
    local entry="${entries[$idx]}"
    local type path
    IFS='|' read -r _ type path <<< "$entry"

    # Re-copy to clipboard
    if [ "$type" = "img" ]; then
        xclip -i -selection clipboard -t image/png "$path"
        notify-send -i "$path" " Re-clipped image"
    else
        xclip -i -selection clipboard < "$path"
        local content
        content=$(cat "$path")
        notify-send " Re-clipped" "$(truncate_text "$content")"
    fi
}

###############################################################################
# clear / wipe
###############################################################################

do_clear() {
    local confirm
    confirm=$(printf '%s\n' "Yes, clear all" "No, cancel" | dmenu -i -p "  Clear history?")
    if [[ "$confirm" == "Yes"* ]]; then
        rm -rf "${TEXT_DIR:?}"/* "${IMG_DIR:?}"/*
        : > "$INDEX_FILE"
        notify-send "dclip" "History cleared"
    fi
}

copy_last() {
    [ ! -s "$INDEX_FILE" ] && { notify-send "dclip" "History empty"; exit 0; }
    local last
    last=$(tail -1 "$INDEX_FILE")
    IFS='|' read -r ts type path <<< "$last"
    if [ "$type" = "img" ]; then
        xclip -i -selection clipboard -t image/png "$path"
        notify-send -i "$path" " Last clip (image)"
    else
        xclip -i -selection clipboard < "$path"
        notify-send " Last clip" "$(truncate_text "$(cat "$path")")"
    fi
}

###############################################################################
# main
###############################################################################

case "${1:-sel}" in
    add)        add ;;
    out)        out "$@" ;;
    link)       link "$@" ;;
    sel|"")     sel ;;
    copy-last)  copy_last ;;
    clear)      do_clear ;;
    *)
        notify-send "dclip" "Usage: dclip [add|sel|out|link|clear|copy-last]"
        ;;
esac
