#!/usr/bin/env bash

# Description:
#   View and manage clipboard history using fzf, with support for
#   image preview in compatible terminals.
# Requirements:
#   - fzf
#   - cliphist
#   - wl-clipboard (including wl-copy and wl-paste)
#   - python3 with urllib (for URL quoting/unquoting)
#   - chafa (optional, for image preview)
#   - ffmpegthumbnailer (optional, for video thumbnails)
# Credits:
#   - Original idea and some code adapted from https://github.com/SHORiN-KiWATA/shorinclip
#     License:
#     # MIT License
#     #
#     # Copyright (c) 2026 shorinkiwata
#     #
#     # Permission is hereby granted, free of charge, to any person obtaining a copy
#     # of this software and associated documentation files (the "Software"), to deal
#     # in the Software without restriction, including without limitation the rights
#     # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#     # copies of the Software, and to permit persons to whom the Software is
#     # furnished to do so, subject to the following conditions:
#     #
#     # The above copyright notice and this permission notice shall be included in all
#     # copies or substantial portions of the Software.
#     #
#     # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#     # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#     # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#     # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#     # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#     # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#     # SOFTWARE.

set -euo pipefail

SHELL=$(command -v bash)
export SHELL

_cleanup() {
	if [ -n "${CACHE_DIR:-}" ] && [ -d "$CACHE_DIR" ]; then
		rm -rf "$CACHE_DIR"
	fi
	if [ -n "${WATCH_PID:-}" ]; then
		kill "$WATCH_PID" 2>/dev/null || true
	fi
	if [ -n "${FZF_SOCKET:-}" ] && [ -S "$FZF_SOCKET" ]; then
		rm -f "$FZF_SOCKET"
	fi
}
trap _cleanup EXIT

_check_dependencies() {
	local missing=()
	for cmd in fzf cliphist wl-copy wl-paste python3; do
		if ! type "$cmd" &>/dev/null; then
			missing+=("$cmd")
		fi
	done
	if [ ${#missing[@]} -ne 0 ]; then
		echo "Error: Missing dependencies: ${missing[*]}" >&2
		exit 1
	fi

	for cmd in chafa ffmpegthumbnailer; do
		if ! type "$cmd" &>/dev/null; then
			echo "Warning: Optional dependency '$cmd' not found. Some features may be unavailable." >&2
		fi
	done
}
_check_dependencies

CACHE_DIR=$(mktemp -d)
export CACHE_DIR

export C_TERTIARY='\x1b[1;35m'
export C_PRIMARY='\x1b[1;34m'
export C_CYAN='\x1b[1;36m'
export C_RESET='\x1b[0m'

export C_PATTERN='\x1b\[[0-9];?([0-9]+)?m'

# Check for terminal graphics support and set environment variables accordingly

_graphics_query() {
	# Port of [graphics-query](https://github.com/Uyanide/dotfiles/blob/main/config/scripts/.local/scripts/graphics-query)

	# Ensure in a interactive terminal
	[ ! -t 0 ] && return

	(
		# Construct query
		KGP_QUERY_ID=$RANDOM
		KGP_QUERY_CODE=$(printf "\033_Gi=%d,s=1,v=1,a=q,t=d,f=24;AAAA\033\\" "$KGP_QUERY_ID")
		ITERM2_QUERY_CODE=$(printf "\033]1337;ReportCellSize\a")
		KGP_EXPECTED_RESPONSE=$(printf "\033_Gi=%d;OK\033\\" "$KGP_QUERY_ID")
		ITERM2_EXPECTED_RESPONSE=$(printf "\033]1337;") # followed by "ReportCellSize=...", but only the prefix is enough
		FENCE_CODE=$(printf "\033[c")

		# Set terminal to raw mode with timeout
		stty_orig=$(stty -g)
		trap 'stty "$stty_orig"' EXIT
		stty -echo -icanon min 1 time 0

		printf "%s%s%s" "$ITERM2_QUERY_CODE" "$KGP_QUERY_CODE" "$FENCE_CODE" >/dev/tty

		support_kgp=0
		support_iterm2=0
		support_sixel=0

		response=""
		while true; do
			IFS= read -r -N 1 -t 0.3 char || {
				[ -z "$char" ] && break
			}

			response+="$char"

			if [[ "$response" == *"$KGP_EXPECTED_RESPONSE"* ]]; then
				support_kgp=1
			fi

			if [[ "$response" == *"$ITERM2_EXPECTED_RESPONSE"* ]]; then
				support_iterm2=1
			fi

			if [[ "$response" == *$'\033['*'c' ]]; then
				break
			fi

			if [ ${#response} -gt 1024 ]; then
				break
			fi
		done

		if [[ "$response" =~ $'\x1b'\[\?([0-9;]*)c ]]; then
			params="${BASH_REMATCH[1]}"

			IFS=';' read -ra codes <<<"$params"

			for code in "${codes[@]}"; do
				if [[ "$code" == "4" ]]; then
					support_sixel=1
					break
				fi
			done
		fi

		if [ "$support_kgp" -eq 1 ]; then
			echo "kitty"
		fi

		if [ "$support_iterm2" -eq 1 ]; then
			echo "iterm"
		fi

		if [ "$support_sixel" -eq 1 ]; then
			echo "sixels"
		fi
	)
}

SUPPORT_ICAT=0
SUPPORT_SIXEL=0
SUPPORT_ITERM2=0

ENABLE_ICAT=0
ENABLE_SIXEL=0
ENABLE_ITERM2=0

_check_graphics_support() {
	local result
	result=$(_graphics_query)
	if [[ "$result" == *"kitty"* ]]; then
		SUPPORT_ICAT=1
	fi
	if [[ "$result" == *"sixels"* ]]; then
		SUPPORT_SIXEL=1
	fi
	if [[ "$result" == *"iterm"* ]]; then
		SUPPORT_ITERM2=1
	fi
}

_check_kitty_icat() {
	# # workaround for WezTerm
	if [ -n "${WEZTERM_EXECUTABLE:-}" ]; then
		return 1
	fi
	[[ "$SUPPORT_ICAT" -eq 1 ]]
}

_check_sixel() {
	# workaround for Zellij
	if [ -n "${ZELLIJ_SESSION_NAME:-}" ]; then
		return 1
	# same for tmux, unless otherwise configured
	elif [ -n "${TMUX:-}" ]; then
		return 1
	fi
	[[ "$SUPPORT_SIXEL" -eq 1 ]]
}

_check_iterm2() {
	[[ "$SUPPORT_ITERM2" -eq 1 ]]
}

# Priority: KGP > sixel > iterm2
_check_graphics_support
if _check_kitty_icat; then
	ENABLE_ICAT=1
elif _check_sixel; then
	ENABLE_SIXEL=1
elif _check_iterm2; then
	ENABLE_ITERM2=1
fi

export ENABLE_ICAT
export ENABLE_SIXEL
export ENABLE_ITERM2

# URL handling (for file:// URLs)

url_unquote() {
	if type python3 &>/dev/null; then
		python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()), end='')"
	else
		cat
	fi
}

url_quote() {
	if type python3 &>/dev/null; then
		python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()), end='')"
	else
		cat
	fi
}
export -f url_unquote
export -f url_quote

# Preview functions

_clear_preview() {
	# chafa --clear will simply send '\x1b[H\x1b[2J' which may not work
	# for images displayed with KGP, so we send the specific clear sequence
	# manually in this case.
	if [ "$ENABLE_ICAT" -eq 1 ]; then
		printf "\x1b_Ga=d\x1b\\"
	fi
}
export -f _clear_preview

_preview_image() {
	local file="$1"
	if ! type chafa >/dev/null 2>&1; then
		text="Preview not available (chafa not found)."$'\n'
		text+="Image: $file"
		_preview_text "$text"
		return
	fi
	# Though chafa is able to detect which output format to use based on the terminal capabilities,
	# it is yet not always reliable, so we force it based on our checks.
	if [ "$ENABLE_ICAT" -eq 1 ]; then
		chafa -f kitty --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
	elif [ "$ENABLE_SIXEL" -eq 1 ]; then
		chafa -f sixels --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
	elif [ "$ENABLE_ITERM2" -eq 1 ]; then
		chafa -f iterm2 -size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
	else
		chafa -f symbols --size="${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}" "$file"
	fi
}
export -f _preview_image

_preview_text() {
	local content="$1"
	printf "%s" "$content" | head -n 100
}
export -f _preview_text

_preview_video() {
	local video_hash thumb_file path
	path="$1"
	video_hash=$(echo -n "$path" | md5sum | cut -d" " -f1)
	thumb_file="$CACHE_DIR/$video_hash.png"
	if [ ! -f "$thumb_file" ]; then
		if type ffmpegthumbnailer &>/dev/null; then
			ffmpegthumbnailer -i "$path" -o "$thumb_file" -s 480 -t 0 >/dev/null 2>&1
		else
			_preview_text "Thumbnail not available (ffmpegthumbnailer not found)."$'\n'
		fi
	fi
	if [ -s "$thumb_file" ]; then
		_preview_image "$thumb_file"
	else
		_preview_text "Video: $path"
	fi
}
export -f _preview_video

# Kinda buggy right now
# _preview_audio() {
# 	local path="$1"
# 	if type mpv &>/dev/null; then
# 		echo "Playing audio: $path" >&2
# 		exec mpv --no-video --keep-open=no --loop-file=no --loop-playlist=no "$path" &>/dev/null
# 	else
# 		_preview_text "Audio: $path"
# 	fi
# }
# export -f _preview_audio

_preview_file() {
	path="$1"
	path_mime=$(file -b --mime-type "$path")
	if [[ $path_mime =~ image ]]; then
		_preview_image "$path"
	elif [[ "$path_mime" =~ video ]]; then
		_preview_video "$path"
	# elif [[ "$path_mime" =~ audio ]]; then
	# 	_preview_audio "$path"
	else
		_preview_text "$path"
	fi
}
export -f _preview_file

preview() {
	_clear_preview

	entry="$1"

	mimeType=$(echo -n "$entry" | cliphist decode | file -b --mime-type -)
	ext=$(echo -n "$mimeType" | awk -F"/" "{print \$2}")

	if [[ $mimeType =~ image ]]; then
		img_hash=$(echo -n "$entry" | cliphist decode | md5sum | cut -d" " -f1)
		cache_file="$CACHE_DIR/$img_hash.$ext"
		[ -f "$cache_file" ] || echo -n "$entry" | cliphist decode >"$cache_file"
		_preview_image "$cache_file"

	elif path=$(echo -n "$entry" | cliphist decode) && [[ "$path" == /* ]]; then
		if [ -e "$path" ]; then
			_preview_file "$path"
		else
			_preview_text "$path does not exist."
		fi

	elif decoded=$(echo -n "$entry" | cliphist decode) && [[ "$decoded" == file://* ]]; then
		paths=()
		for path in $decoded; do
			raw_path="${path#file://}"
			raw_path=$(echo -n "$raw_path" | url_unquote)
			paths+=("$raw_path")
		done

		if [ "${#paths[@]}" -eq 1 ] && [ -e "${paths[0]}" ]; then
			_preview_file "${paths[0]}"
		else
			text="Multiple files:"$'\n'
			for p in "${paths[@]}"; do
				text+="$p"$'\n'
			done
			_preview_text "$text"
		fi

	else
		if [ "$ENABLE_ICAT" = 1 ]; then
			printf "\x1b_Ga=d\x1b\\"
		fi
		_preview_text "$(echo -n "$entry" | cliphist decode)"
	fi
}
export -f preview

# Optimize entry formatting

format_clip_list() {
	sed -E \
		-e "s/(\t).*\.(mp4|mkv|webm|avi|mov|flv|wmv)$/\1${C_TERTIARY}[VIDEO]File.\2${C_RESET}/" \
		-e "s/(\t)file:\/\/.*\.(mp4|mkv|webm|avi|mov|flv|wmv)$/\1${C_TERTIARY}[URL]Video.\2${C_RESET}/" \
		-e "s/(\t)file:\/\/.*\.gif$/\1${C_PRIMARY}[URL]Image.gif${C_RESET}/" \
		-e "s/(\t)file:\/\/.*\.(png|jpg|jpeg|webp|bmp)$/\1${C_TERTIARY}[URL]Image.\2${C_RESET}/" \
		-e "s/(\t)file:\/\/.*/\1${C_CYAN}[URL]File${C_RESET}/" \
		-e "s/(\t)\/.*\.gif$/\1${C_PRIMARY}[PATH]Image.gif${C_RESET}/" \
		-e "s/(\t)\/.*\.(png|jpg|jpeg|webp|bmp)$/\1${C_TERTIARY}[PATH]Image.\2${C_RESET}/" \
		-e "s/\[\[ binary data .* (png|jpg|jpeg|gif|webp) .*\]\]/${C_TERTIARY}[IMG]Bin.\1${C_RESET}/" \
		-e "s/\[\[ binary data .* \]\]/${C_CYAN}[BINARY]${C_RESET}/"
}
export -f format_clip_list

add_num() {
	awk -F '\t' '{printf "%s\t\x1b[90m%-2d \x1b[0m%s\n", $1, NR, $2}'
}
export -f add_num

# Action when confirmed

copy_selection() {
	local input="$1"
	local content
	content=$(echo -n "$input" | awk "{print \$3}")

	if [[ "$content" == "[URL]"* ]]; then
		echo -n "$input" | cliphist decode | wl-copy --type text/uri-list

	else
		echo -n "$input" | cliphist decode | wl-copy
	fi

	echo -n "$input" | cliphist delete || true
}
export -f copy_selection

# Reload mechanism

RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp}"
FZF_SOCKET=$(mktemp -u "$RUNTIME_DIR/fzfclip.XXXXXX.sock")
RELOAD_CMD="cliphist list | format_clip_list | add_num"
wl-paste --watch bash -c "curl -s --unix-socket '$FZF_SOCKET' -X POST -d 'reload($RELOAD_CMD)' http://localhost" &>/dev/null &
WATCH_PID=$!

# Ensure terminal is large enough

wait_timeout=50
while [[ $(tput cols) -lt 35 || $(tput lines) -lt 25 ]]; do
	printf "\rWaiting for terminal size at least 35x25... %d" "$wait_timeout"
	sleep 1
	((wait_timeout--))
	[ "$wait_timeout" -eq 0 ] && exit 1
done

$RELOAD_CMD | fzf \
	--ansi \
	--listen "$FZF_SOCKET" \
	--bind "ctrl-r:reload($RELOAD_CMD)" \
	--bind "ctrl-x:execute-silent(bash -c 'cliphist delete <<< \"\$1\"' -- {})+reload($RELOAD_CMD)" \
	--prompt="󰅍 > " \
	--header='CTRL-X: Delete | CTRL-R: Reload | ENTER: Paste' \
	--color='header:italic:yellow,prompt:blue,pointer:blue' \
	--info=hidden \
	--no-sort \
	--layout=reverse \
	--with-nth 2.. \
	--delimiter '\t' \
	--preview-window=down:60%,wrap \
	--preview "preview {}" \
	--bind "enter:execute-silent(bash -c 'copy_selection \"\$1\"' -- {})+accept" \
	>/dev/null || [ $? -eq 141 ]
