#!/usr/bin/env sh
# shellcheck disable=SC3043,SC2034,SC1090
# yt-x - Browse media from the terminal or app launcher
# MIT License - Copyright (c) 2024-2026 Benexl

# ==============================================================================
# META
# ==============================================================================
readonly CLI_NAME="yt-x"
readonly CLI_ARGS="$*"
readonly CLI_APP_NAME="${YT_X_APP_NAME:-"$CLI_NAME"}"
readonly CLI_VERSION="0.8.6"
readonly CLI_AUTHOR="Benexl"
readonly CLI_REPO_URL="https://github.com/Benexl/yt-x"
readonly CLI_VERSION_URL="https://raw.githubusercontent.com/Benexl/yt-x/refs/heads/master/version.txt"
readonly CLI_RELEASES_BASE="https://github.com/Benexl/yt-x/releases/download"
readonly CLI_RELEASE_TAG="$CLI_RELEASES_BASE/v$CLI_VERSION"
readonly CLI_RELEASE_URL="$CLI_RELEASE_TAG/yt-x"

# ==============================================================================
# SETUP
# ==============================================================================
# shellcheck disable=2155
readonly CLI_START_TIME="$(date +%s)"

_dep_ch() {
  command -v "$1" >/dev/null 2>&1
}

# ==============================================================================
# SETUP ENVIRONMENT
# ==============================================================================
if [ -n "$BASH_VERSION" ]; then
  readonly CLI_SHELL="bash"
elif [ -n "$ZSH_VERSION" ]; then
  readonly CLI_SHELL="zsh"
  setopt shwordsplit
elif [ -n "$KSH_VERSION" ]; then
  readonly CLI_SHELL="ksh"
else
  readonly CLI_SHELL="sh"
fi

if ! _dep_ch local; then
  eval 'local() { unset -v $1; }'
fi

if _dep_ch realpath; then
  # shellcheck disable=SC2155
  readonly CLI_DIR="$(dirname "$(realpath "$0")")"
else
  # shellcheck disable=SC2155
  readonly CLI_DIR="$(cd "$(dirname "$0")" && pwd -P)"
fi

readonly CLI_PATH="$CLI_DIR/$CLI_NAME"

case "$(uname -a)" in
*ndroid*) readonly CLI_PLATFORM="android" ;;
*Darwin*) readonly CLI_PLATFORM="mac" ;;
*WSL* | *microsoft* | *Microsoft*) readonly CLI_PLATFORM="linux" ;;
*MINGW* | *MSYS* | *CYGWIN*) readonly CLI_PLATFORM="windows" ;;
*) readonly CLI_PLATFORM="linux" ;;
esac

if [ -t 1 ]; then
  readonly CLI_IS_TERMINAL=true
else
  readonly CLI_IS_TERMINAL=false
fi

if [ "$COLORTERM" = "truecolor" ] || [ "$COLORTERM" = "24bit" ]; then
  readonly CLI_SUPPORTS_TRUE_COLOR=true
else
  readonly CLI_SUPPORTS_TRUE_COLOR=false
fi

export SHELL="sh"

# ==============================================================================
# SETUP PATHS
# ==============================================================================
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"

readonly CLI_CONFIG_DIR="$XDG_CONFIG_HOME/$CLI_APP_NAME"

readonly CLI_EXTENSIONS_DIR="$CLI_CONFIG_DIR/extensions"
readonly CLI_EXTENSIONS_SITES_DIR="$CLI_EXTENSIONS_DIR/sites"
readonly CLI_EXTENSIONS_THEMES_DIR="$CLI_EXTENSIONS_DIR/themes"
readonly CLI_EXTENSIONS_LANGS_DIR="$CLI_EXTENSIONS_DIR/langs"
readonly CLI_EXTENSIONS_CMDS_DIR="$CLI_EXTENSIONS_DIR/cmds"
readonly CLI_EXTENSIONS_UI_DIR="$CLI_EXTENSIONS_DIR/ui"

readonly CLI_CACHE_DIR="$XDG_CACHE_HOME/$CLI_APP_NAME"

readonly CLI_STATE_DIR="$CLI_CACHE_DIR/state"
readonly CLI_CURRENT_STATE_DIR="$CLI_STATE_DIR/${CLI_START_TIME}-$$"

readonly CLI_PREVIEW_DIR="$CLI_CACHE_DIR/previews"
readonly CLI_PREVIEW_IMGS_DIR="$CLI_PREVIEW_DIR/images"
readonly CLI_PREVIEW_SCRIPTS_DIR="$CLI_PREVIEW_DIR/text"
readonly CLI_FZF_PREVIEW_SCRIPT="$CLI_PREVIEW_SCRIPTS_DIR/fzf-preview.sh"

readonly CLI_AUTO_GEN_PLAYLISTS="$CLI_CACHE_DIR/generated-playlists"
readonly CLI_DOWNLOAD_ARCHIVE_DIR="$CLI_CACHE_DIR/archives"
readonly CLI_LOG_DIR="$CLI_CACHE_DIR/logs"

readonly CLI_CONFIG_FILE="$CLI_CONFIG_DIR/config"
readonly CLI_DEFAULT_THEME_FILE="$CLI_EXTENSIONS_THEMES_DIR/default.theme"
readonly CLI_DEFAULT_LANG_FILE="$CLI_EXTENSIONS_LANGS_DIR/default.lang"
readonly CLI_RECENT_FILE="$CLI_CONFIG_DIR/recent.json"
readonly CLI_SAVED_VIDEOS_FILE="$CLI_CONFIG_DIR/saved-videos.json"
readonly CLI_CUSTOM_PLAYLISTS_FILE="$CLI_CONFIG_DIR/custom-playlists.json"
readonly CLI_SUBSCRIPTIONS_FILE="$CLI_CONFIG_DIR/subscriptions.json"
readonly CLI_CUSTOM_CMDS_FILE="$CLI_CONFIG_DIR/custom-cmds.json"
readonly CLI_SEARCH_HISTORY_FILE="$CLI_CACHE_DIR/search-history.txt"
readonly CLI_LOG_FILE="$CLI_LOG_DIR/${CLI_NAME}.log"

mkdir -p \
  "$CLI_EXTENSIONS_SITES_DIR" \
  "$CLI_EXTENSIONS_THEMES_DIR" \
  "$CLI_EXTENSIONS_LANGS_DIR" \
  "$CLI_EXTENSIONS_CMDS_DIR" \
  "$CLI_EXTENSIONS_UI_DIR" \
  "$CLI_CURRENT_STATE_DIR" \
  "$CLI_PREVIEW_IMGS_DIR" \
  "$CLI_PREVIEW_SCRIPTS_DIR" \
  "$CLI_AUTO_GEN_PLAYLISTS" \
  "$CLI_DOWNLOAD_ARCHIVE_DIR" \
  "$CLI_LOG_DIR"

# ==============================================================================
# Declarations for lsp
# ==============================================================================
CMD_EXIT=""
CMD_PLAYLIST_RESULTS_SKIP=""
CMD_MEDIA_EXIT=""
CMD_MEDIA_ACTION=""
CMD_MAIN_ACTION=""
CMD_MISC_ACTION=""
CMD_CHANNEL=""
CMD_CHANNEL_ACTION=""
CMD_INPUT=""

# ==============================================================================
# CONFIGURATION & ENVIRONMENT
# ==============================================================================
__load_default_config() {
  : "${CONFIG_AUTOLOADED_EXTENSIONS:=}"
  : "${CONFIG_ENABLE_COLORS:=true}"
  : "${CONFIG_LAUNCHER:=fzf}"
  : "${CONFIG_ENABLE_PREVIEW:=false}"
  : "${CONFIG_ENABLE_PREVIEW_IMAGES:=false}"
  : "${CONFIG_IMAGE_RENDERER:=chafa}"
  : "${CONFIG_CHAFA_ARGS:=}"
  : "${CONFIG_ICAT_ARGS:=}"
  : "${CONFIG_IMGCAT_ARGS:=}"
  : "${CONFIG_YT_DLP_OPTS:=}"
  : "${CONFIG_PLAYER:=mpv}"
  : "${CONFIG_DISOWN_PLAYER:=false}"
  : "${CONFIG_MPV_ARGS:=}"
  : "${CONFIG_VLC_ARGS:=}"
  : "${CONFIG_TPLAY_ARGS:=}"
  : "${CONFIG_PER_PAGE:=30}"
  : "${CONFIG_BROWSER:=}"
  : "${CONFIG_CACHE_RETENTION_DAYS:=3}"
  : "${CONFIG_EDITOR:=${EDITOR:-vi}}"
  : "${CONFIG_NOTIFICATION_DURATION:=5}"
  : "${CONFIG_DOWNLOAD_DIR:=$HOME/Videos/$CLI_NAME}"
  : "${CONFIG_DOWNLOADS_ENUMERATE:=false}"
  : "${CONFIG_CHECK_FOR_UPDATES:=true}"
  : "${CONFIG_NO_OF_RECENT:=10}"
  : "${CONFIG_ENABLE_SEARCH_HISTORY:=true}"
  : "${CONFIG_ROFI_THEME:=}"
  : "${CONFIG_GUM_FILTER_OPTS:="--prompt.foreground #2ac3de --indicator.foreground #2ac3de --selected-indicator.foreground #2ac3de --unselected-prefix.foreground #585858 --header.foreground #2ac3de --text.foreground #c0caf5 --cursor-text.foreground #c0caf5 --match.foreground #2ac3de --placeholder.foreground #585858  --prompt  > --indicator ◆ --selected-prefix  ◉  --unselected-prefix  ○  --placeholder こんにちは..."}"
  : "${CONFIG_GUM_INPUT_OPTS:="--prompt.foreground #2ac3de --cursor.foreground #c0caf5 --placeholder こんにちは... --cursor.mode blink"}"
  : "${CONFIG_GUM_PAGER_OPTS:="--foreground #c0caf5 --line-number.foreground #585858 --match.foreground #2ac3de --match-highlight.foreground #1a1b26 --match-highlight.background #2ac3de --help.foreground #585858 --show-line-numbers --soft-wrap --border-foreground #2ac3de"}"
  : "${CONFIG_GUM_SPIN_OPTS:="--spinner dot --spinner.foreground #2ac3de --title.foreground #585858  --align left"}"
  : "${CONFIG_GUM_CONFIRM_OPTS:="--prompt.foreground #2ac3de --selected.foreground #1a1b26 --selected.background #2ac3de --unselected.foreground #585858"}"
  : "${CONFIG_FZF_HEADER:=$CLI_HEADER}"
  : "${CONFIG_FZF_OPTS:="
  --ansi
  --border
  --scheme=history
  --style=full
  --color=bg+:#283457
  --color=bg:#16161e
  --color=border:#27a1b9
  --color=fg:#c0caf5
  --color=gutter:#16161e
  --color=header:#2ac3de
  --color=hl+:#2ac3de
  --color=hl:#2ac3de
  --color=info:#545c7e
  --color=marker:#ff007c
  --color=pointer:#ff007c
  --color=prompt:#2ac3de
  --color=query:#c0caf5:regular
  --color=scrollbar:#27a1b9
  --color=separator:#ff9e64
  --color=spinner:#ff007c
  --border=rounded
  --border-label=''
  --prompt=' >'
  --marker=' >'
  --pointer='◆'
  --layout=reverse
  --cycle
  --ghost='こんにちは...'
  --height=100%
  --bind=ctrl-/:toggle-preview,ctrl-space:toggle-wrap+toggle-preview-wrap
  --no-margin
  +m
  -i
  --exact
  --tabstop=1
  --preview-window=border-rounded,left,35%,wrap
  --wrap"}"
}

__load_env_config() {
  CONFIG_AUTOLOADED_EXTENSIONS="${YT_X_AUTOLOADED_EXTENSIONS:-$CONFIG_AUTOLOADED_EXTENSIONS}"
  CONFIG_ENABLE_COLORS="${YT_X_ENABLE_COLORS:-$CONFIG_ENABLE_COLORS}"
  CONFIG_LAUNCHER="${YT_X_LAUNCHER:-$CONFIG_LAUNCHER}"
  CONFIG_ENABLE_PREVIEW="${YT_X_ENABLE_PREVIEW:-$CONFIG_ENABLE_PREVIEW}"
  CONFIG_ENABLE_PREVIEW_IMAGES="${YT_X_ENABLE_PREVIEW_IMAGES:-$CONFIG_ENABLE_PREVIEW_IMAGES}"

  CONFIG_IMAGE_RENDERER="${YT_X_IMAGE_RENDERER:-$CONFIG_IMAGE_RENDERER}"
  CONFIG_CHAFA_ARGS="${YT_X_CHAFA_ARGS:-$CONFIG_CHAFA_ARGS}"
  CONFIG_ICAT_ARGS="${YT_X_ICAT_ARGS:-$CONFIG_ICAT_ARGS}"
  CONFIG_IMGCAT_ARGS="${YT_X_IMGCAT_ARGS:-$CONFIG_IMGCAT_ARGS}"

  CONFIG_PLAYER="${YT_X_PLAYER:-$CONFIG_PLAYER}"
  CONFIG_DISOWN_PLAYER="${YT_X_DISOWN_PLAYER:-$CONFIG_DISOWN_PLAYER}"
  CONFIG_MPV_ARGS="${YT_X_MPV_ARGS:-$CONFIG_MPV_ARGS}"
  CONFIG_VLC_ARGS="${YT_X_VLC_ARGS:-$CONFIG_VLC_ARGS}"
  CONFIG_TPLAY_ARGS="${YT_X_TPLAY_ARGS:-$CONFIG_TPLAY_ARGS}"

  CONFIG_DOWNLOAD_DIR="${YT_X_DOWNLOAD_DIR:-$CONFIG_DOWNLOAD_DIR}"
  CONFIG_YT_DLP_OPTS="${YT_X_YT_DLP_OPTS:-$CONFIG_YT_DLP_OPTS}"

  CONFIG_PER_PAGE="${YT_X_PER_PAGE:-$CONFIG_PER_PAGE}"
  CONFIG_BROWSER="${YT_X_BROWSER:-$CONFIG_BROWSER}"

  CONFIG_EDITOR="${YT_X_EDITOR:-$CONFIG_EDITOR}"
  CONFIG_TERMINAL_EXEC="${YT_X_TERMINAL_EXEC:-$CONFIG_TERMINAL_EXEC}"
  CONFIG_NOTIFICATION_DURATION="${YT_X_NOTIFICATION_DURATION:-$CONFIG_NOTIFICATION_DURATION}"

  CONFIG_DOWNLOADS_ENUMERATE="${YT_X_ENUMERATE_DOWNLOADS:-$CONFIG_DOWNLOADS_ENUMERATE}"
  CONFIG_CHECK_FOR_UPDATES="${YT_X_CHECK_FOR_UPDATES:-$CONFIG_CHECK_FOR_UPDATES}"
  CONFIG_NO_OF_RECENT="${YT_X_NO_OF_RECENT:-$CONFIG_NO_OF_RECENT}"
  CONFIG_ENABLE_SEARCH_HISTORY="${YT_X_ENABLE_SEARCH_HISTORY:-$CONFIG_ENABLE_SEARCH_HISTORY}"

  CONFIG_GUM_FILTER_OPTS="${YT_X_GUM_OPTS:-$CONFIG_GUM_FILTER_OPTS}"
  CONFIG_GUM_INPUT_OPTS="${YT_X_GUM_INPUT_OPTS:-$CONFIG_GUM_INPUT_OPTS}"
  CONFIG_GUM_PAGER_OPTS="${YT_X_GUM_PAGER_OPTS:-$CONFIG_GUM_PAGER_OPTS}"
  CONFIG_GUM_SPIN_OPTS="${YT_X_GUM_SPIN_OPTS:-$CONFIG_GUM_SPIN_OPTS}"
  CONFIG_GUM_CONFIRM_OPTS="${YT_X_GUM_CONFIRM_OPTS:-$CONFIG_GUM_CONFIRM_OPTS}"

  CONFIG_FZF_HEADER="${YT_X_FZF_HEADER:-$CONFIG_FZF_HEADER}"
  CONFIG_FZF_OPTS="${YT_X_FZF_OPTS:-$CONFIG_FZF_OPTS}"
  CONFIG_ROFI_THEME_MAIN="${YT_X_ROFI_THEME_MAIN:-$CONFIG_ROFI_THEME_MAIN}"
  CONFIG_ROFI_THEME_PREVIEW="${YT_X_ROFI_THEME_PREVIEW:-$CONFIG_ROFI_THEME_PREVIEW}"
  CONFIG_ROFI_THEME_PROMPT="${YT_X_ROFI_THEME_PROMPT:-$CONFIG_ROFI_THEME_PROMPT}"
  CONFIG_ROFI_THEME_CONFIRM="${YT_X_ROFI_THEME_CONFIRM:-$CONFIG_ROFI_THEME_CONFIRM}"
  CONFIG_ROFI_THEME_PAGER="${YT_X_ROFI_THEME_PAGER:-$CONFIG_ROFI_THEME_PAGER}"
}

__load_default_lang() {
  TXT_BYEBYE="Have a good day"
  TXT_NO_TERMINAL_EXEC="No supported terminal emulator found for executing commands. Please set CONFIG_TERMINAL_EXEC to a valid terminal command. Currently set to: $CONFIG_TERMINAL_EXEC"

  TXT_STATE_MALFORMED_CURRENT="Malformed State. Confirm contents of: $CLI_CURRENT_STATE_DIR/$STATE_CURRENT"

  TXT_MENU_NEXT="Next"
  TXT_MENU_PREVIOUS="Previous"
  TXT_MENU_BACK="Back"
  TXT_MENU_EXIT="Exit"

  TXT_LAUNCHER_UNKNOWN="Unknown launcher"
  TXT_LAUNCHER_PREVIEWS_UNSUPPORTED="The current launcher ($CONFIG_LAUNCHER) does not support previews. Please switch to a supported launcher or disable previews in the config."

  TXT_FZF_PREVIEW_IMAGE_LOADING="loading preview image..."
  TXT_FZF_PREVIEW_CHANNEL="Channel"
  TXT_FZF_PREVIEW_DURATION="Duration"
  TXT_FZF_PREVIEW_VIEW_COUNT="Views"
  TXT_FZF_PREVIEW_LIVE_STATUS="Live Status"
  TXT_FZF_PREVIEW_TIMESTAMP="Uploaded"
  TXT_FZF_PREVIEW_CHANNEL_FOLLOWERS="Followers"

  TXT_ROFI_NOT_CONFIGURED="Rofi is not configured. Please set to a valid rofi config file path in your config.sh"
  TXT_ROFI_PAGER_MESSAGE="Press [Esc] to exit pager"
  TXT_ROFI_PAGER_PROMPT="Filter lines by typing"

  TXT_MENU_INVALID_ACTION="Invalid Action"
  TXT_EDITOR_NOT_FOUND="No suitable editor found. Please set CONFIG_EDITOR to a valid text editor command or ensure \$EDITOR is set."

  TXT_YES="y"
  TXT_NO="n"
  TXT_CONFIRM_DEFAULT="Default"

  TXT_LOADING="Loading..."

  TXT_PREVIEW_INSTALL_VIEWER="please install a terminal image viewer\neither icat for kitty terminal and wezterm or imgcat or chafa"

  TXT_CONFIG_BROWSER_NOT_SET="Please set CONFIG_BROWSER to proceed"

  TXT_CUSTOM_CMDS_FILE_NOT_FOUND="You dont have any custom cmds. Create them here $CLI_CUSTOM_CMDS_FILE or use the ui"
  TXT_SEARCH_HISTORY_FILE_NOT_FOUND="You dont have any search history. Start searching to generate search history or edit the file here $CLI_SEARCH_HISTORY_FILE"
  TXT_SAVED_VIDEOS_FILE_NOT_FOUND="You dont have any saved videos. Start saving videos to generate this file or edit the file here $CLI_SAVED_VIDEOS_FILE"
  TXT_CUSTOM_PLAYLISTS_FILE_NOT_FOUND="You dont have any custom playlists. Start saving playlists to generate this file or edit the file here $CLI_CUSTOM_PLAYLISTS_FILE"
  TXT_SUBS_FILE_NOT_FOUND="You don't have any channel subscriptions. Please use the miscellaneous menu to add subscriptions to $CLI_SUBSCRIPTIONS_FILE"
  TXT_RECENT_FILE_NOT_FOUND="You don't have any recent videos. Try watching sth first : ). And an entry will be created at $CLI_RECENT_FILE"

  TXT_MENU_MAIN_PROMPT_ACTION="Select an action"
  TXT_MENU_MAIN="Main Menu"
  TXT_MENU_MAIN_FEED="Your Feed"
  TXT_MENU_MAIN_TRENDING="Trending"
  TXT_MENU_MAIN_PLAYLISTS="Playlists"
  TXT_MENU_MAIN_SEARCH="Search"
  TXT_MENU_MAIN_SEARCH_PROMPT="Enter search query"
  TXT_MENU_MAIN_SEARCH_OR_HISTORY_PROMPT="Enter search query or select from history"
  TXT_SEARCH_FILTER_HELP="\
Filter by upload date –  :hour, :today, :week, :month, :year
Filter by content      –  :video, :movie, :live, :short, :long
Filter by features     –  :4k, :hd, :hdr, :subtitles, :360, :vr, :3d, :local
Sort by                –  :newest, :views, :rating
Recall history         –  !1, !2, … (newest = 1)"

  TXT_MENU_MAIN_WATCH_LATER="Watch Later"
  TXT_MENU_MAIN_SUBS_FEED="Subscriptions Feed"

  TXT_MENU_MAIN_CHANNELS="Channels"
  TXT_MENU_MAIN_CHANNELS_PROMPT="Select Channel"

  TXT_MENU_MAIN_CUSTOM_PLAYLISTS="Custom Playlists"
  TXT_MENU_MAIN_CUSTOM_PLAYLISTS_PROMPT="Select Playlist"

  TXT_MENU_MAIN_LIKED="Liked Videos"

  TXT_MENU_MAIN_SAVED="Saved Videos"
  TXT_MENU_MAIN_SAVED_PROMPT="Select video"

  TXT_MENU_MAIN_HISTORY="Watch History"
  TXT_MENU_MAIN_RECENT="Recent"
  TXT_MENU_MAIN_CLIPS="Clips"
  TXT_MENU_MAIN_EDIT_CONFIG="Edit Config"
  TXT_MENU_MAIN_MISC="Miscellaneous"

  TXT_MENU_PROMPT_SEARCH="Enter search query"

  TXT_MENU_MISC_PROMPT="Select an action"

  TXT_MENU_MISC_EXPLORE_CHANNELS="Explore Channels"
  TXT_MENU_MISC_EXPLORE_CHANNELS_PROMPT="Enter channel search term"

  TXT_MENU_MISC_EXPLORE_PLAYLISTS="Explore Playlists"
  TXT_MENU_MISC_EXPLORE_PLAYLISTS_PROMPT="Enter playlist search term"

  TXT_MENU_MISC_EXPLORE_SHORTS="Explore Shorts"
  TXT_MENU_MISC_EXPLORE_SHORTS_PROMPT="Enter short search term"

  TXT_MENU_MISC_EXPLORE_MOVIES="Explore Movies"
  TXT_MENU_MISC_EXPLORE_MOVIES_PROMPT="Enter movie search term"

  TXT_MENU_MISC_SEARCH_HISTORY="Search History"
  TXT_MENU_MISC_SEARCH_HISTORY_PROMPT="Search for"
  TXT_MENU_MISC_CLEAR_SEARCH_HISTORY="Clear Search History"
  TXT_MENU_MISC_CLEAR_SEARCH_HISTORY_PROMPT="Are you sure you would like to clear your search history at $CLI_SEARCH_HISTORY_FILE?"

  TXT_MENU_MISC_NEW_CUSTOM_CMD="New Custom Command"
  TXT_MENU_MISC_NEW_CUSTOM_CMD_DESC="\
# ==============================================================================
# YT-X CUSTOM COMMANDS
# ==============================================================================
Create a custom command that executes a URL with yt-dlp options.
You can use this to create custom searches or actions on specific sites.
For example, you can create a custom command that searches for a query
on a specific site and returns the results in the launcher.
NOTE: There are base options which are given to yt-dlp but can be overriden
    DEFAULT OPTS:
      --dump-single-json
      --flat-playlist
      --playlist-start 1
      --playlist-end $CONFIG_PER_PAGE
      --cookies-from-browser $CONFIG_BROWSER (Dependent on CONFIG_BROWSER)
    Don't recommend to override the first two will break the display logic
# ==============================================================================
# =============================================================================="

  TXT_MENU_MISC_NEW_CUSTOM_CMD_NAME_PROMPT="Enter a name for the custom command"
  TXT_MENU_MISC_NEW_CUSTOM_CMD_URL_PROMPT="Enter the URL for the custom command"
  TXT_MENU_MISC_NEW_CUSTOM_CMD_YT_DLP_OPTS_PROMPT="Enter any extra yt-dlp options for the custom command (optional)"
  TXT_MENU_MISC_CUSTOM_CMDS="Custom Commands"
  TXT_MENU_MISC_CUSTOM_CMDS_PROMPT="Enter the name of the custom command to execute"

  TXT_MENU_MISC_EDIT_SEARCH_HISTORY="Edit Search History"
  TXT_MENU_MISC_EDIT_CUSTOM_PLAYLISTS="Edit Custom Playlists"
  TXT_MENU_MISC_EDIT_MPV_CONFIG="Edit MPV Config"
  TXT_MENU_MISC_EDIT_YTDLP_CONFIG="Edit yt-dlp Config"
  TXT_MENU_MISC_EDIT_CUSTOM_CMDS="Edit Custom Commands"

  TXT_MENU_MISC_SYNC_SUBS="Sync YouTube Subscriptions"
  TXT_MENU_MISC_SYNC_SUBS_CONFIRM="This will erase your local subs, proceed?"
  TXT_MENU_MISC_SYNC_SUBS_START="Syncing subscriptions..."
  TXT_MENU_MISC_SYNC_SUBS_FAILED="Failed to sync subs"

  TXT_MENU_CHANNELS_EXPLORER_PROMPT="Select a channel"

  TXT_MENU_CHANNEL_ACTIONS_PROMPT="Select action"
  TXT_MENU_CHANNEL_ACTIONS_VIDEOS="Videos"
  TXT_MENU_CHANNEL_ACTIONS_FEATURED="Featured"
  TXT_MENU_CHANNEL_ACTIONS_SEARCH="Search"
  TXT_MENU_CHANNEL_ACTIONS_SEARCH_PROMPT="Search for"
  TXT_MENU_CHANNEL_ACTIONS_PLAYLISTS="Playlists"
  TXT_MENU_CHANNEL_ACTIONS_SHORTS="Shorts"
  TXT_MENU_CHANNEL_ACTIONS_STREAMS="Streams"
  TXT_MENU_CHANNEL_ACTIONS_PODCASTS="Podcasts"
  TXT_MENU_CHANNEL_ACTIONS_SUBSCRIBE="Subscribe"
  TXT_MENU_CHANNEL_ACTIONS_SUBSCRIBE_CONFIRM="Would you like to import your youtube subscriptions first? You wont be able to do so again unless you delete $CLI_SUBSCRIPTIONS_FILE"

  TXT_MENU_PLAYLISTS_EXPLORER_PROMPT="Select Playlist"

  TXT_MENU_PLAYLIST_EXPLORER_PROMPT="Select Media"

  TXT_MENU_PLAYLIST_PROMPT_ACTION="Select Media Action"
  TXT_MENU_PLAYLIST_ACTIONS_WATCH="Watch"
  TXT_MENU_PLAYLIST_ACTIONS_WATCH_ALL="Play All"
  TXT_MENU_PLAYLIST_ACTIONS_LISTEN="Listen"
  TXT_MENU_PLAYLIST_ACTIONS_LISTEN_ALL="Listen To All"
  TXT_MENU_PLAYLIST_ACTIONS_MIX="Mix"
  TXT_MENU_PLAYLIST_ACTIONS_SAVE="Save"
  TXT_MENU_PLAYLIST_ACTIONS_UNSAVE="UnSave"
  TXT_MENU_PLAYLIST_ACTIONS_SAVE_PLAYLIST="Save Playlist"
  TXT_MENU_PLAYLIST_ACTIONS_UNSAVE_PLAYLIST="UnSave Playlist"
  TXT_MENU_PLAYLIST_ACTIONS_VISIT_CHANNEL="Go To Channel"
  TXT_MENU_PLAYLIST_ACTIONS_SUBSCRIBE="Subscribe To Channel"
  TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD="Download"
  TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL="Download All"
  TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_AUDIO="Download (Audio Only)"
  TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL_AUDIO="Download All (Audio Only)"
  TXT_MENU_PLAYLIST_ACTIONS_BROWSER="Open in Browser"
  TXT_MENU_PLAYLIST_ACTIONS_TOGGLE_ENUM="Toggle Enumerate Downloads"
  TXT_MENU_PLAYLIST_ACTIONS_SHELL="Shell"
  TXT_MENU_PLAYLIST_ACTIONS_CHANNEL_NOT_FOUND="Could not determine a channel URL for this video"

  TXT_ENTER_PLAYLIST_TITLE="Enter playlist title"

  TXT_PLAYER_START_WATCHING="Now Watching"
  TXT_PLAYER_START_LISTENING="Now Listening To"
  TXT_PLAYER_NOT_FOUND="No suitable media player found. Please set CONFIG_PLAYER to a valid media player command. Currently set to: $CONFIG_PLAYER"
  TXT_MENU_PLAYLIST_ACTIONS_PROMPT_SAVE_PLAYLIST="Enter the name of the playlist"

  TXT_DOWNLOAD_COMPLETED="Completed downloading of "

  TXT_UPDATE_ROOT_WARNING="It seems you have installed $CLI_NAME as root, which is not recommended. Do you still want to update using sudo? (I recommended reinstalling the cli as non root)"
  TXT_UPDATE_SUDO_MISSING="Insufficient permissions to update and can't find sudo in PATH"
  TXT_UPDATE_SCRIPT_REEXECUTE="Script has been updated! Would you like to re-execute it?"
  TXT_UPDATE_FAILED="Can't update for some reason!"
  TXT_UPDATE_FETCH_FAILED="Something went wrong fetching the update, can't proceed with the update"
  TXT_UPDATE_FOUND="An update has been found would you like to see the changes before deciding whether to update?"
  TXT_DEP_MISSING_DIFF="Could not find diff in path please install it to view update diffs"
  TXT_UPDATE_CONFIRM="Would you like to proceed with the update?"
  TXT_UPDATE_NOT_FOUND="No updates found"

  TXT_DEP_MISSING_YTDLP="yt-dlp is not installed and is a core dep please install it to proceed"
  TXT_DEP_MISSING_JQ="jq is not installed and is a core dep please install it to proceed"
  TXT_DEP_MISSING_FZF="fzf is not installed and is a core dep please install it to proceed"

  TXT_MENU_PLAYLIST_ACTIONS_SHELL_WELCOME="Welcome to the $CLI_NAME shell."
  TXT_MENU_PLAYLIST_ACTIONS_SHELL_INTRO="You can use the following:"
  TXT_MENU_PLAYLIST_ACTIONS_SHELL_VARIABLES="variables:"

  TXT_MENU_PLAYLIST_ACTIONS_OPEN_IN_BROWSER_ERROR="Could not find a supported URL opener (open, xdg-open, wslview, or cmd.exe)"
  TXT_MENU_PLAYLIST_ACTIONS_SUBSCRIBE_UNSUPPORTED="contribute to the project by adding this feature"

  TXT_MENU_MAIN_RECENT_SEARCHES="Recent searches:"
  TXT_MENU_MAIN_NEW_SEARCH="New search…"

  TXT_DESKTOP_ENTRY_PROMPT="Which launcher would you like to configure a desktop entry for"
  TXT_DESKTOP_ENTRY_COMMENT="Browse Youtube from the terminal plus other sites yt-dlp supports"
  TXT_LAUNCHER_LABEL_ROFI="rofi"
  TXT_LAUNCHER_LABEL_FZF="fzf"

  TXT_APP_USAGE="\
A script written to browse YouTube and other yt‑dlp supported sites from the terminal.

Usage: $CLI_NAME [OPTIONS]
       $CLI_NAME completions [--fish|--bash|--zsh|--help]
       $CLI_NAME channels [OPTIONS]

All options can also be set permanently in the config file ($CLI_CONFIG_FILE).
Environment variables (YT_X_*) override the config.

────────────────────────────────────────────────────────────────────
Options:
  -h, --help                     Show this help message and exit
  -v, --version                  Print version information and exit
  -e, --edit-config              Open the config file in your editor
  -x, --extension <ext>          Load an extension file (absolute path or relative to $CLI_CONFIG_DIR/extensions)
  -E, --generate-desktop-entry   Print a .desktop file to stdout and exit
  -U, --update                   Check for and apply an update for $CLI_NAME
  -l, --launcher <launcher>      Menu launcher: fzf or rofi or gum
  --config-write                 Write the current runtime config to the config file
  --preview                      Enable the preview window (images/text)
  --no-preview                   Disable the preview window
  --preview-images               Enable the image preview window
  --no-preview-images            Disable the image preview window
  -p, --player <player>          Media player: mpv, vlc or tplay
  --mpv-args                     Pass custom mpv args at runtime
  --vlc-args                     Pass custom vlc args at runtime
  --tplay-args                   Pass custom tplay args at runtime
  --disown-player                Detach the player process from the terminal
  --no-disown-player             Keep player attached (default)
  --rofi-theme-main <path>       Path to the rofi main theme file
  --rofi-theme-preview <path>    Path to the rofi preview theme file
  --rofi-theme-prompt <path>     Path to the rofi prompt theme file
  --rofi-theme-confirm <path>    Path to the rofi confirm theme file
  --rofi-theme-pager <path>      Path to the rofi pager theme file
  -xargs, --extension-arguments <args>  The arguments to pass to cmd extension;
                                        every argument passed after it is automatically assumed
                                        to be intend for processing by an extension (available via the readonly CLI_EXTENSION_CMDLINE_ARGS env var)

────────────────────────────────────────────────────────────────────
Direct media action shortcuts (skip the media action menu):
  -me, --media-exit              Exit after performing a media action (watch, listen, etc.)
  --play                         Immediately watch the selected video (you still choose from the list)
  --play-all                     Immediately play the whole playlist (implies --playlist-skip)
  --listen                       Immediately listen to the audio of the selected video
  --listen-all                   Immediately listen to the whole playlist (audio only, implies --playlist-skip)
  --download                     Download the selected video
  --download-all                 Download the whole playlist (video, implies --playlist-skip)
  --download-audio               Download only the audio of the selected video
  --download-audio-all           Download the whole playlist as audio (implies --playlist-skip)
  --save                         Save the selected video to your saved videos list
  --save-playlist                Save the current playlist to your custom playlists (implies --playlist-skip)
  --shell                        Open a subshell with current context variables

────────────────────────────────────────────────────────────────────
Direct menu shortcuts
  -ce, --cmd-exit                Exit after shortcut menu commandline options
  -ps, --playlist-skip           Skip the item selection menu and automatically pick the first entry
  -s, --search <term>            Search for videos directly
  -sp, --search-playlist <term>  Search for playlists directly
  -sc, --search-channel <term>   Search for channels directly
  -ss, --search-short <term>     Search for shorts directly
  -sm, --search-movie <term>     Search for movies directly
  -cp, --custom-playlist <name>  Open a specific custom playlist by its saved name
  -cc, --custom-cmd <name>       Execute a specific custom command by its saved name
  -sv, --saved-video <title>     Open a specific saved video by its title
  --feed                         Open your personalised feed
  --subscriptions-feed           Show latest videos from subscriptions
  --watch-later                  Open the Watch Later playlist
  --playlists                    Show saved YouTube playlists
  --custom-playlists             Browse custom playlists you've saved
  --saved                        Open saved videos
  --recent                       Show recently watched videos
  --liked                        Open your Liked Videos playlist
  --watch-history                Show your watch history
  --clips                        Browse your clips
  --new-custom-cmd               Go straight to creating a custom command
  --custom-cmds                  Execute an existing custom command
  --search-history               Browse your search history
  --edit-search-history          Edit the search history file
  --edit-custom-playlists        Edit the custom playlists JSON file
  --edit-mpv-config              Edit mpv's configuration file
  --edit-yt-dlp-config           Edit yt‑dlp's configuration file
  --edit-custom-cmds             Edit the custom commands JSON file

────────────────────────────────────────────────────────────────────
Sub‑commands:
  channels                       Browse or search within a specific channel
    -n, --name <channel>         Choose the channel by name (exact match)
    -s, --search <query>         Search within the channel's uploads
    -v, --videos                 List the channel's videos
    -f, --featured               Show the channel's featured playlists
    -p, --playlists              List the channel's playlists
    -sh, --shorts                Show the channel's shorts
    -st, --streams               Show live streams & past broadcasts
    -po, --podcasts              Show the channel's podcasts

  completions                    Generate shell completion definitions
    --fish                       Fish shell completions
    --bash                       Bash shell completions
    --zsh                        Zsh shell completions
    --help                       Show help for completions

────────────────────────────────────────────────────────────────────
Examples:
  $CLI_NAME                                 # Start the interactive browser
  $CLI_NAME --feed                          # Open the feed directly
  $CLI_NAME --subscriptions-feed            # Open subscriptions feed
  $CLI_NAME -s 'onepiece elbaf trailer'     # Search immediately
  $CLI_NAME -sp 'rust tutorial'             # Search playlists immediately
  $CLI_NAME -sc 'learn coding'              # Search channels immediately
  $CLI_NAME --launcher rofi --preview --preview-images       # Use rofi with previews

  # Media action shortcuts
  $CLI_NAME --play                          # Play the selected video (interactive selection)
  $CLI_NAME --playlist-skip --play          # Automatically play the first video without selection
  $CLI_NAME --play-all                      # Play the entire current playlist (no selection)
  $CLI_NAME --listen-all                    # Listen to the whole playlist as audio
  $CLI_NAME --download-all                  # Download the whole playlist (video)
  $CLI_NAME --download-audio-all            # Download the whole playlist as audio
  $CLI_NAME --save-playlist                 # Save the current playlist (no selection needed)

  $CLI_NAME --save                          # Save the current video to saved list
  $CLI_NAME --shell                         # Open a subshell with state variables

  # Skip and exit helpers
  $CLI_NAME --playlist-skip --play          # Automatically play the first item
  $CLI_NAME --media-exit --download         # Download and then exit the script
  $CLI_NAME -cp 'Jazz Favourites'           # Open a saved custom playlist by name
  $CLI_NAME -sv 'My favourite video'        # Open a specific saved video by title
  $CLI_NAME -cc 'My Custom Search'          # Execute a specific custom command by name

  # Extensions and desktop entry
  $CLI_NAME -x themes/catppuccin.theme      # Load a theme extension
  $CLI_NAME -x langs/es.lang                # Load a language extension
  $CLI_NAME -x sites/dailymotion.site       # Load a site extension
  $CLI_NAME -x cmd/downloads                # Execute a cmd extension
  $CLI_NAME completions --fish              # Print fish completions
  $CLI_NAME -E > ~/.local/share/applications/yt-x.desktop

  # Channel subcommand examples
  $CLI_NAME channels -n 'Linus Tech Tips' -v   # Browse channel videos
  $CLI_NAME channels -n 'iambenexl' -s 'Top linux tools' # search within a channel
  $CLI_NAME --cmd-exit channels -n 'freeCodeCamp.org' -p # Browse freecodecamp playlists and exit on back
  $CLI_NAME --cmd-exit channels -n 'freeCodeCamp.org' # useful for setting aliases eg a shortcut to always go to freecodecamp channel 'freecodecamp'
  $CLI_NAME --launcher rofi --cmd-exit channels -n 'freeCodeCamp.org' # or as an app eg  'freecodecamp-app'

For more details visit the FAQ:
  https://github.com/Benexl/yt-x#frequently-asked-questions-faq"

  TXT_APP_USAGE_CHANNELS="\
Browse YouTube channels or search within them directly from the command line.

Usage: $CLI_NAME channels [OPTIONS]

Options:
  -n, --name <channel>        Specify the channel name (exact match, case‑sensitive)
  -s, --search <query>        Search inside the channel's uploads
  -v, --videos                List the channel's uploaded videos
  -f, --featured              Show the channel's featured playlists
  -p, --playlists             List the channel's playlists
  -sh, --shorts               Show the channel's short videos
  -st, --streams              Show live streams & past broadcasts
  -po, --podcasts             Show the channel's podcasts

Examples:
  $CLI_NAME channels -n 'Linus Tech Tips' -v     # Open channel and list videos
  $CLI_NAME channels -n 'iambenexl' -s 'Top linux tools'   # Search within a channel
  $CLI_NAME channels -n 'StarTalk' -p          # Show channel playlists
  $CLI_NAME channels -n 'The PrimeTime' -st         # Show channel streams
  $CLI_NAME --cmd-exit channels -n 'freeCodeCamp.org' -p # Browse freecodecamp playlists and immediately exit on back
  $CLI_NAME --cmd-exit channels -n 'freeCodeCamp.org' # useful for setting aliases eg a shortcut to always go to freecodecamp channel 'freecodecamp'
  $CLI_NAME --launcher rofi --cmd-exit channels -n 'freeCodeCamp.org' # or as an app eg  'freecodecamp-app'

If --name is omitted, you'll be asked to pick a channel from your subscriptions.
If --name is given without an action, the interactive channel menu will open.

Note: The channel name must match what appears in your subscriptions exactly.
      Use the Miscellaneous → Explore Channels feature if you haven't subscribed yet."

  TXT_APP_USAGE_COMPLETIONS="\
Generate shell completions for $CLI_NAME

Options:
  --fish
    print fish completions and exit
  --bash
    print bash completions and exit
  --zsh
    print zsh completions and exit

Example:
  $CLI_NAME completions --fish
  $CLI_NAME completions --bash
  $CLI_NAME completions --zsh"

  TXT_ICON_MENU_PLAYLIST_ACTIONS_WATCH=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_WATCH_ALL=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_LISTEN="󰎆"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_LISTEN_ALL=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_MIX=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_SAVE=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_UNSAVE="󰧎"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_SAVE_PLAYLIST="󰐒"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_UNSAVE_PLAYLIST="󰐒"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_VISIT_CHANNEL="󰑈"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_SUBSCRIBE="󰵀"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD="󱑤"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL="󰦗"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD_AUDIO="󱑤"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL_AUDIO="󰦗"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_BROWSER=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_TOGGLE_ENUM=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_SHELL=""
  TXT_ICON_MENU_PLAYLIST_ACTIONS_BACK="󰌍"
  TXT_ICON_MENU_PLAYLIST_ACTIONS_EXIT="󰈆"

  TXT_ICON_MENU_CHANNEL_ACTIONS_VIDEOS=""
  TXT_ICON_MENU_CHANNEL_ACTIONS_FEATURED="󰩉"
  TXT_ICON_MENU_CHANNEL_ACTIONS_SEARCH=""
  TXT_ICON_MENU_CHANNEL_ACTIONS_PLAYLISTS="󰐑"
  TXT_ICON_MENU_CHANNEL_ACTIONS_SHORTS=""
  TXT_ICON_MENU_CHANNEL_ACTIONS_STREAMS="󰠿"
  TXT_ICON_MENU_CHANNEL_ACTIONS_PODCASTS=""
  TXT_ICON_MENU_CHANNEL_ACTIONS_SUBSCRIBE="󰵀"
  TXT_ICON_MENU_CHANNEL_ACTIONS_BACK="󰌍"
  TXT_ICON_MENU_CHANNEL_ACTIONS_EXIT="󰈆"

  TXT_ICON_MENU_MISC_EXPLORE_CHANNELS="󰵀"
  TXT_ICON_MENU_MISC_EXPLORE_PLAYLISTS="󰐑"
  TXT_ICON_MENU_MISC_EXPLORE_SHORTS="󰕧"
  TXT_ICON_MENU_MISC_EXPLORE_MOVIES="󰿎"
  TXT_ICON_MENU_MISC_SEARCH_HISTORY="󱘢"
  TXT_ICON_MENU_MISC_NEW_CUSTOM_CMD="󰆺"
  TXT_ICON_MENU_MISC_CUSTOM_CMDS="󰡦"
  TXT_ICON_MENU_MISC_EDIT_SEARCH_HISTORY=""
  TXT_ICON_MENU_MISC_EDIT_CUSTOM_PLAYLISTS="󰤀"
  TXT_ICON_MENU_MISC_EDIT_MPV_CONFIG="󱄢"
  TXT_ICON_MENU_MISC_EDIT_YTDLP_CONFIG="󰮆"
  TXT_ICON_MENU_MISC_EDIT_CUSTOM_CMDS="󱘫"
  TXT_ICON_MENU_MISC_SYNC_SUBS="󰓦"
  TXT_ICON_MENU_MISC_CLEAR_SEARCH_HISTORY="󰆴"
  TXT_ICON_MENU_MISC_BACK=""
  TXT_ICON_MENU_MISC_EXIT="󰈆"

  TXT_ICON_MENU_MAIN_FEED=""
  TXT_ICON_MENU_MAIN_SEARCH=""
  TXT_ICON_MENU_MAIN_SUBS_FEED="󰵀"
  TXT_ICON_MENU_MAIN_WATCH_LATER=""
  TXT_ICON_MENU_MAIN_PLAYLISTS="󰐑"
  TXT_ICON_MENU_MAIN_CHANNELS="󰑈"
  TXT_ICON_MENU_MAIN_CUSTOM_PLAYLISTS=""
  TXT_ICON_MENU_MAIN_SAVED=""
  TXT_ICON_MENU_MAIN_RECENT=""
  TXT_ICON_MENU_MAIN_LIKED=""
  TXT_ICON_MENU_MAIN_HISTORY=""
  TXT_ICON_MENU_MAIN_CLIPS=""
  TXT_ICON_MENU_MAIN_MISC=""
  TXT_ICON_MENU_MAIN_EDIT_CONFIG=""
  TXT_ICON_MENU_MAIN_EXIT="󰈆"

  [ -s "$CLI_DEFAULT_LANG_FILE" ] && . "$CLI_DEFAULT_LANG_FILE"
}

__load_default_theme() {
  THEME_ESC="$(printf '\033')"

  if $CLI_SUPPORTS_TRUE_COLOR && $CONFIG_ENABLE_COLORS; then
    # Tokyo Night: Cyan (#2ac3de)
    THEME_FZF_ICON_COLOR_PRIMARY="$(printf '\033[38;2;42;195;222m')"

    # Tokyo Night: Dark3 (#545c7e)
    THEME_FZF_ICON_COLOR_SECONDARY="$(printf '\033[38;2;84;92;126m')"

    # Tokyo Night: Orange (#ff9e64)
    THEME_FZF_ICON_COLOR_ACCENT="$(printf '\033[38;2;255;158;100m')"

    # Tokyo Night: Pink (#ff007c)
    THEME_FZF_ICON_COLOR_ERROR="$(printf '\033[38;2;255;0;124m')"

    # Tokyo Night: Foreground (#c0caf5)
    THEME_FZF_PREVIEW_TITLE="$(printf '\033[38;2;192;202;245m')"

    # Tokyo Night: Cyan (#2ac3de)
    THEME_FZF_PREVIEW_KEY="$(printf '\033[38;2;42;195;222m')"

    # Tokyo Night: Dark3 (#545c7e)
    THEME_FZF_PREVIEW_VALUE="$(printf '\033[38;2;88;91;112m')"

    # Tokyo Night: Dark3 (#545c7e)
    THEME_FZF_PREVIEW_DIVIDER="$(printf '\033[38;2;88;91;112m')"

    # Tokyo Night: Green (#9ece6a)
    THEME_PLAYER_START="$(printf '\033[38;2;158;206;106m')"

    THEME_BOLD="$(printf '\033[1m')"
    THEME_RESET="$(printf '\033[0m')"

    [ -s "$CLI_DEFAULT_THEME_FILE" ] && . "$CLI_DEFAULT_THEME_FILE"
  fi
}

__post_config_load() {
  CLI_HEADER="$(printf '%s' "$THEME_FZF_ICON_COLOR_PRIMARY")\
██╗░░░██╗████████╗░░░░░░██╗░░██╗
╚██╗░██╔╝╚══██╔══╝░░░░░░╚██╗██╔╝
░╚████╔╝░░░░██║░░░█████╗░╚███╔╝░
░░╚██╔╝░░░░░██║░░░╚════╝░██╔██╗░
░░░██║░░░░░░██║░░░░░░░░░██╔╝╚██╗
░░░╚═╝░░░░░░╚═╝░░░░░░░░░╚═╝░░╚═╝$(printf '%s' "$THEME_RESET")"

  if [ -n "$CONFIG_AUTOLOADED_EXTENSIONS" ]; then
    for ext in $(printf '%s' "$CONFIG_AUTOLOADED_EXTENSIONS" | tr ',' '\n'); do
      [ -s "$CLI_EXTENSIONS_DIR/$ext" ] && . "$CLI_EXTENSIONS_DIR/$ext"
    done
  fi

  export FZF_DEFAULT_OPTS="$CONFIG_FZF_OPTS"
}

_print_config() {
  cat <<EOF
# ===================================================================================
# 大きな力には大きな責任が伴う
# ===================================================================================
#
#     ██╗░░░██╗████████╗░░░░░░██╗░░██╗  ░█████╗░░█████╗░███╗░░██╗███████╗██╗░██████╗░
#     ╚██╗░██╔╝╚══██╔══╝░░░░░░╚██╗██╔╝  ██╔══██╗██╔══██╗████╗░██║██╔════╝██║██╔════╝░
#     ░╚████╔╝░░░░██║░░░█████╗░╚███╔╝░  ██║░░╚═╝██║░░██║██╔██╗██║█████╗░░██║██║░░██╗░
#     ░░╚██╔╝░░░░░██║░░░╚════╝░██╔██╗░  ██║░░██╗██║░░██╗██║╚████║██╔══╝░░██║██║░░╚██╗
#     ░░░██║░░░░░░██║░░░░░░░░░██╔╝╚██╗  ╚█████╔╝╚█████╔╝██║░╚███║██║░░░░░██║╚██████╔╝
#     ░░░╚═╝░░░░░░╚═╝░░░░░░░░░╚═╝░░╚═╝  ░╚════╝░░╚════╝░╚═╝░░╚══╝╚═╝░░░░░╚═╝░╚═════╝░
#
# ===================================================================================
#
# !! BE CAREFUL WHAT YOU INCLUDE HERE SINCE THE CONFIG FILE IS SOURCED AS A SCRIPT  !!
# !! ITS RECOMMENDED TO ONLY DO VARIABLE ASSIGNMENTS AND CONDITIONAL ASSIGNMENTS    !!
# !! AND DO MORE COMPLEX STUFF USING EXTENSIONS BY CONVENTION                       !!
# !! Environment variables (export YT_X_VAR=...) override these settings.           !!
#
# ===================================================================================

# List of extensions to load automatically on startup
CONFIG_AUTOLOADED_EXTENSIONS="$CONFIG_AUTOLOADED_EXTENSIONS"

# Enable or disable colored/formatted output
# Options: true, false (Default: true)
CONFIG_ENABLE_COLORS="$CONFIG_ENABLE_COLORS"

# The menu launcher tool used for the ui
# Options: fzf, rofi, gum (Default: fzf)
CONFIG_LAUNCHER="$CONFIG_LAUNCHER"

# Enable image/video previews within the selector
# Options: true, false (Default: false)
CONFIG_ENABLE_PREVIEW="$CONFIG_ENABLE_PREVIEW"
CONFIG_ENABLE_PREVIEW_IMAGES="$CONFIG_ENABLE_PREVIEW_IMAGES"

# The tool used to render previews in the terminal
# Options: chafa, icat, imgcat (Default: chafa)
CONFIG_IMAGE_RENDERER="$CONFIG_IMAGE_RENDERER"

# Extra arguments for specific image renderers
CONFIG_CHAFA_ARGS="$CONFIG_CHAFA_ARGS"
CONFIG_ICAT_ARGS="$CONFIG_ICAT_ARGS"
CONFIG_IMGCAT_ARGS="$CONFIG_IMGCAT_ARGS"

# custom opts for yt-dlp
CONFIG_YT_DLP_OPTS="$CONFIG_YT_DLP_OPTS"

# The media player used for playback
# Options: mpv, vlc, tplay (Default: mpv)
CONFIG_PLAYER="$CONFIG_PLAYER"

# Extra arguments for specific media players
CONFIG_MPV_ARGS="$CONFIG_MPV_ARGS"
CONFIG_VLC_ARGS="$CONFIG_VLC_ARGS"
CONFIG_TPLAY_ARGS="$CONFIG_TPLAY_ARGS"

# Whether to disown the player, process to prevent blocking the UI
# options: true, false (Default: false)
CONFIG_DISOWN_PLAYER="$CONFIG_DISOWN_PLAYER"

# Maximum number of search results to fetch per page
# Options: Integer (Default: 30)
CONFIG_PER_PAGE="$CONFIG_PER_PAGE"

# Browser for yt-dlp to get cookies from
# passed to --cookies-from-browser yt-dlp option
# Currently supported browsers are: brave, chrome, chromium, edge, firefox, opera, safari, vivaldi, whale
# NOTE: this option is entirely dependent on what yt-dlp supports
CONFIG_BROWSER="$CONFIG_BROWSER"

# Text editor for manual configuration/metadata editing
# Options: vim, nano, code ... (Default: \$EDITOR or vi)
CONFIG_EDITOR="$CONFIG_EDITOR"

# The terminal to use to execute commands that require a terminal
CONFIG_TERMINAL_EXEC="$CONFIG_TERMINAL_EXEC"

# Duration in seconds for desktop notifications
CONFIG_NOTIFICATION_DURATION="$CONFIG_NOTIFICATION_DURATION"

# Directory where downloads are saved
# Default: $HOME/Videos/$CLI_NAME
CONFIG_DOWNLOAD_DIR="$CONFIG_DOWNLOAD_DIR"
CONFIG_DOWNLOADS_ENUMERATE="$CONFIG_DOWNLOADS_ENUMERATE"

# Automatically check for script updates on startup
# Options: true, false (Default: true)
CONFIG_CHECK_FOR_UPDATES="$CONFIG_CHECK_FOR_UPDATES"

# Number of items to keep in the recent history
CONFIG_NO_OF_RECENT="$CONFIG_NO_OF_RECENT"

# Enable or disable saving search queries to history
# Options: true, false (Default: true)
CONFIG_ENABLE_SEARCH_HISTORY="$CONFIG_ENABLE_SEARCH_HISTORY"

# Custom options passed directly to gum
CONFIG_GUM_OPTS="$CONFIG_GUM_FILTER_OPTS"
CONFIG_GUM_INPUT_OPTS="$CONFIG_GUM_INPUT_OPTS"
CONFIG_GUM_PAGER_OPTS="$CONFIG_GUM_PAGER_OPTS"
CONFIG_GUM_SPIN_OPTS="$CONFIG_GUM_SPIN_OPTS"
CONFIG_GUM_CONFIRM_OPTS="$CONFIG_GUM_CONFIRM_OPTS"

# Custom options passed directly to fzf
CONFIG_FZF_HEADER="\\
$CONFIG_FZF_HEADER"
CONFIG_FZF_OPTS="$CONFIG_FZF_OPTS"

# Rofi config all are required to run rofi as a launcher
# get the official ones from $CLI_REPO_URL
CONFIG_ROFI_THEME_MAIN="$CONFIG_ROFI_THEME_MAIN"
CONFIG_ROFI_THEME_PREVIEW="$CONFIG_ROFI_THEME_PREVIEW"
CONFIG_ROFI_THEME_PROMPT="$CONFIG_ROFI_THEME_PROMPT"
CONFIG_ROFI_THEME_CONFIRM="$CONFIG_ROFI_THEME_CONFIRM"
CONFIG_ROFI_THEME_PAGER="$CONFIG_ROFI_THEME_PAGER"

# Number of days to keep cached preview images, playlists, and logs
# Default: 3
CONFIG_CACHE_RETENTION_DAYS="$CONFIG_CACHE_RETENTION_DAYS"
# ==============================================================================
# 楽しんでね
# ==============================================================================
EOF
}

_load_config() {
  __load_default_config

  [ -f "$CLI_CONFIG_FILE" ] || _print_config >"$CLI_CONFIG_FILE"

  . "$CLI_CONFIG_FILE"

  __load_env_config
  __load_default_lang
  __load_default_theme
  __post_config_load
}

# ==============================================================================
# CORE: utilities
# ==============================================================================
_util_terminal_exec() {
  local term
  local old_ifs
  local cmd
  cmd="$*"
  old_ifs="$IFS"

  if ! $CLI_IS_TERMINAL; then
    if [ -n "$CONFIG_TERMINAL_EXEC" ]; then
      term="$CONFIG_TERMINAL_EXEC"
    elif _dep_ch kitty; then
      term="kitty --exec"
    elif _dep_ch alacritty; then
      term="alacritty --command"
    else
      ui_notify_error "$TXT_NO_TERMINAL_EXEC"
      return 1
    fi
    IFS=" "
    # shellcheck disable=SC2086
    $term $cmd
    IFS="$old_ifs"
    return
  fi
  IFS=" "
  $cmd
  IFS="$old_ifs"
}

_util_open() {
  local target
  local converted_target
  target="$1"

  if _dep_ch open; then
    open "$target" || return 1
    return 0
  elif _dep_ch xdg-open; then
    xdg-open "$target" || return 1
    return 0
  elif _dep_ch wslview; then
    wslview "$target" || return 1
    return 0
  elif _dep_ch cmd.exe; then
    converted_target="$target"

    case "$target" in
    http://* | https://* | ftp://* | ftps://* | mailto:*) ;;
    *)
      if _dep_ch cygpath; then
        converted_target="$(cygpath -w "$target" 2>/dev/null || printf "%s" "$target")"
      elif _dep_ch wslpath; then
        converted_target="$(wslpath -w "$target" 2>/dev/null || printf "%s" "$target")"
      fi
      ;;
    esac

    cmd.exe /C start "" "$converted_target" >/dev/null 2>&1 || return 1
    return 0
  else
    return 1
  fi
}

_util_file_edit() {
  local file_path
  file_path="$1"

  if _dep_ch "$CONFIG_EDITOR"; then
    _util_terminal_exec "$CONFIG_EDITOR" "$file_path"
  elif _dep_ch "$EDITOR"; then
    _util_terminal_exec "$EDITOR" "$file_path"
  elif ! _util_open "$file_path"; then
    ui_notify_warning "$TXT_EDITOR_NOT_FOUND"
  fi
}

_util_generate_hash() {
  local input

  if [ -n "$1" ]; then
    input="$1"
  else
    input=$(cat)
  fi

  if _dep_ch sha256sum; then
    printf "%s" "$input" | sha256sum | awk '{print $1}'
  elif _dep_ch shasum; then
    printf "%s" "$input" | shasum -a 256 | awk '{print $1}'
  elif _dep_ch sha256; then
    printf "%s" "$input" | sha256 | awk '{print $1}'
  elif _dep_ch openssl; then
    printf "%s" "$input" | openssl dgst -sha256 | awk '{print $2}'
  else
    printf "%s" "$input" | base64 | tr '/+' '_-' | tr -d '\n'
  fi
}

_util_menu_sort() {
  # TODO: shift to using awk
  local options_to_sort
  local options_to_sort_copy
  local sort
  local i
  i=0
  options_to_sort="$1"
  sort="$2"

  for option in $(printf '%s' "$options_to_sort" | tr ' ' '-'); do
    i=$((i + 1))
    if [ -z "$options_to_sort_copy" ]; then
      options_to_sort_copy="$i:$option"
    else
      options_to_sort_copy="$options_to_sort_copy\n$i:$option"
    fi
  done

  for order in $(printf '%s' "$sort" | tr ',' '\n'); do
    printf "$options_to_sort_copy" | grep "^$order:" | sed "s/$order://g" | tr '-' ' '
  done
}

_util_byebye() {
  printf '%s %s\n' "$TXT_BYEBYE" "$USER"
  exit
}
# ==============================================================================
# CORE UI: notify
# ==============================================================================
__ui_notify_terminal() {
  #TODO: finish implementation
  case "$1" in
  info) printf '%s\n' "$2" >&2 && sleep "$CONFIG_NOTIFICATION_DURATION" ;;
  warning) printf '%s\n' "$2" >&2 && sleep "$CONFIG_NOTIFICATION_DURATION" ;;
  error) printf '%s\n' "$2" >&2 && sleep "$CONFIG_NOTIFICATION_DURATION" ;;
  critical) printf '%s\n' "$2" >&2 && exit 1 ;;
  *) printf '%s\n' "$2" >&2 && sleep "$CONFIG_NOTIFICATION_DURATION" ;;
  esac

}

__ui_notify_non_terminal() {
  #TODO: finish implementation
  case "$1" in
  info) notify-send "$2" ;;
  warning) notify-send "$2" ;;
  error) notify-send "$2" ;;
  critical) notify-send "$2" && exit 1 ;;
  esac
}

_ui_notify() {
  if $CLI_IS_TERMINAL; then
    __ui_notify_terminal info "$1"
  else
    __ui_notify_non_terminal info "$1"
  fi
}

_ui_notify_warning() {
  if $CLI_IS_TERMINAL; then
    __ui_notify_terminal warning "$1"
  else
    __ui_notify_non_terminal warning "$1"
  fi
}

_ui_notify_error() {
  if $CLI_IS_TERMINAL; then
    __ui_notify_terminal error "$1"
  else
    __ui_notify_non_terminal error "$1"
  fi
}

_ui_notify_critical() {
  if $CLI_IS_TERMINAL; then
    __ui_notify_terminal critical "$1"
  else
    __ui_notify_non_terminal critical "$1"
  fi
}

ui_notify() {
  _ui_notify "$@"
}

ui_notify_warning() {
  _ui_notify_warning "$@"
}

ui_notify_error() {
  _ui_notify_error "$@"
}

ui_notify_critical() {
  _ui_notify_critical "$@"
}

# ==============================================================================
# CORE UI: launcher
# ==============================================================================
__ui_gum_launcher() {
  local custom_opts
  local multi_select
  multi_select="$2"

  [ "$multi_select" = "multi" ] && custom_opts="--no-limit"
  [ -n "$CONFIG_FZF_HEADER" ] && custom_opts="$custom_opts --header $CONFIG_FZF_HEADER"

  local old_ifs
  old_ifs="$IFS"
  IFS=" "

  # shellcheck disable=SC2086
  gum filter $CONFIG_GUM_FILTER_OPTS --prompt="${1}: " $custom_opts

  IFS="$old_ifs"
}

__ui_fzf_launcher() {
  local custom_opts
  local multi_select
  multi_select="$2"

  [ "$multi_select" = "multi" ] && custom_opts="--multi"
  [ -n "$CONFIG_FZF_HEADER" ] && custom_opts="$custom_opts --header-first --header=$CONFIG_FZF_HEADER"

  # TODO: is there a better way to do this
  local old_ifs
  old_ifs="$IFS"
  IFS=" "

  # shellcheck disable=SC2086
  fzf --prompt="${1}: " $custom_opts

  IFS="$old_ifs"
}

__ui_rofi_launcher() {
  ! [ -s "$CONFIG_ROFI_THEME_MAIN" ] && ui_notify_critical "$TXT_ROFI_NOT_CONFIGURED: where CONFIG_ROFI_THEME_MAIN=\"$CONFIG_ROFI_THEME_MAIN\""
  local selection
  local custom_opts
  local multi_select
  local line

  multi_select="$2"
  [ "$multi_select" = "multi" ] && custom_opts="-multi-select"

  # shellcheck disable=SC2086
  selection=$(
    sed -E "s/$THEME_ESC(\[[0-9;]*[a-zA-Z]|\(B)//g" |
      rofi -no-config -theme "$CONFIG_ROFI_THEME_MAIN" -dmenu -i $custom_opts -p "$1"
  )
  printf '%s' "$selection"
}

_ui_launcher() {
  case "$CONFIG_LAUNCHER" in
  rofi) __ui_rofi_launcher "$@" ;;
  fzf) __ui_fzf_launcher "$@" ;;
  gum) __ui_gum_launcher "$@" ;;
  *) ui_notify_critical "$TXT_LAUNCHER_UNKNOWN: $CONFIG_LAUNCHER" ;;
  esac
}

ui_launcher() {
  _ui_launcher "$@"
}

# ==============================================================================
# CORE UI: launcher with preview
# ==============================================================================
__ui_fzf_launcher_with_preview() {
  local custom_opts
  local multi_select
  local preview_script

  preview_script="\
TXT_PREVIEW_INSTALL_VIEWER='$TXT_PREVIEW_INSTALL_VIEWER'
CONFIG_IMAGE_RENDERER='$CONFIG_IMAGE_RENDERER'
CLI_PLATFORM='$CLI_PLATFORM'
THEME_FZF_PREVIEW_DIVIDER='$THEME_FZF_PREVIEW_DIVIDER'
preview_script_path='{1}'
[ -s \"\$preview_script_path\" ] && . \"\$preview_script_path\"\
"

  multi_select="$2"
  [ "$multi_select" = "multi" ] && custom_opts="--multi"
  [ -n "$CONFIG_FZF_HEADER" ] && custom_opts="$custom_opts --header-first --header=$CONFIG_FZF_HEADER"

  # TODO: is there a better way to do this
  local old_ifs
  old_ifs="$IFS"
  IFS=" "

  # shellcheck disable=SC2086
  fzf --prompt="${1}: " --delimiter '|' --with-nth "{2..}" --accept-nth "{2..}" --preview="$preview_script" $custom_opts

  IFS="$old_ifs"
}

__ui_rofi_launcher_with_preview() {
  ! [ -s "$CONFIG_ROFI_THEME_PREVIEW" ] && ui_notify_critical "$TXT_ROFI_NOT_CONFIGURED: where CONFIG_ROFI_THEME_PREVIEW=\"$CONFIG_ROFI_THEME_PREVIEW\""
  local selection
  local custom_opts
  local multi_select
  local line

  multi_select="$2"
  [ "$multi_select" = "multi" ] && custom_opts="-multi-select"

  # shellcheck disable=SC2086
  selection=$(
    sed -E "s/$THEME_ESC(\[[0-9;]*[a-zA-Z]|\(B)//g" |
      rofi -no-config -theme "$CONFIG_ROFI_THEME_PREVIEW" -dmenu -i $custom_opts -p "$1"
  )
  printf '%s' "$selection"
}

_ui_launcher_with_preview() {
  if [ "$CONFIG_ENABLE_PREVIEW" = "true" ]; then
    case "$CONFIG_LAUNCHER" in
    rofi) __ui_rofi_launcher_with_preview "$@" ;;
    fzf) __ui_fzf_launcher_with_preview "$@" ;;
    gum) __ui_gum_launcher "$@" ;;
    *) ui_notify_critical "$TXT_LAUNCHER_UNKNOWN: $CONFIG_LAUNCHER" ;;
    esac
  else
    ui_launcher "$@" && return
  fi
}

ui_launcher_with_preview() {
  _ui_launcher_with_preview "$@"
}

# ==============================================================================
# CORE UI: prompt
# ==============================================================================
__ui_default_prompt() {
  local header
  local value
  header="$2"

  if _dep_ch gum; then
    # shellcheck disable=SC2086
    gum input $CONFIG_GUM_INPUT_OPTS --header "$header" --prompt "$1: "
  else
    printf "%s\n" "$header" >/dev/stderr
    printf "%s: " "$1" >/dev/stderr
    read -r value
    printf "%s" "$value"
  fi
}

__ui_rofi_prompt() {
  local default
  local filter

  if [ -p /dev/stdin ]; then
    default="$(cat 2>/dev/null)"
    filter='-filter'
  fi

  [ -s "$CONFIG_ROFI_THEME_PROMPT" ] ||
    ui_notify_critical "Rofi prompt theme not set: $CONFIG_ROFI_THEME_PROMPT"

  # NOTE: It doesnt look pretty so disabling it
  if [ -n "$2" ] && false; then
    # shellcheck disable=SC2086
    rofi -no-config -theme "$CONFIG_ROFI_THEME_PROMPT" "$filter" "$default" -dmenu -p "$1" -mesg "$2"
  else
    # shellcheck disable=SC2086
    rofi -no-config -theme "$CONFIG_ROFI_THEME_PROMPT" "$filter" "$default" -dmenu -p "$1"
  fi
}

_ui_prompt() {
  case "$CONFIG_LAUNCHER" in
  rofi) __ui_rofi_prompt "$@" ;;
  *) __ui_default_prompt "$@" ;;
  esac
}

ui_prompt() {
  _ui_prompt "$@"
}

# ==============================================================================
# CORE UI: confirm
# ==============================================================================
_ui_default_confirm() {
  local yes
  local no
  local default
  local default_index

  yes="${3:-$TXT_YES}"
  no="${4:-$TXT_NO}"
  default="${2:-$no}"

  if [ "$default" = "$yes" ]; then
    default_index=1
  else
    default_index=0
  fi

  if _dep_ch gum; then
    # shellcheck disable=SC2086
    gum confirm "$1" --default="$default_index" --affirmative="$yes" --negative="$no" $CONFIG_GUM_CONFIRM_OPTS
  else
    printf "%s ($yes/$no; $TXT_CONFIRM_DEFAULT: $default): " "$1" >/dev/stderr
    read -r CONFIRMED
    case "$CONFIRMED" in
    "$yes")
      return 0
      ;;
    "$no")
      return 1
      ;;
    *)
      if [ "$default" = "$yes" ]; then
        return 0
      else
        return 1
      fi
      ;;
    esac
  fi
}

_ui_rofi_confirm() {
  ! [ -s "$CONFIG_ROFI_THEME_CONFIRM" ] && ui_notify_critical "$TXT_ROFI_NOT_CONFIGURED: where CONFIG_ROFI_THEME_CONFIRM=\"$CONFIG_ROFI_THEME_CONFIRM\""
  local yes
  local no
  local default
  local default_index
  local selection

  yes="${3:-$TXT_YES}"
  no="${4:-$TXT_NO}"
  default="${2:-$no}"

  if [ "$default" = "$yes" ]; then
    default_index=0
  else
    default_index=1
  fi

  selection="$(printf "%s\n%s" "$yes" "$no" |
    rofi -no-config -theme "$CONFIG_ROFI_THEME_CONFIRM" -dmenu -i -p "$1" -selected-row "$default_index")"
  [ "$selection" = "$yes" ] && return 0 || return 1
}

_ui_confirm() {
  case "$CONFIG_LAUNCHER" in
  rofi) _ui_rofi_confirm "$@" ;;
  *) _ui_default_confirm "$@" ;;
  esac
}

ui_confirm() {
  _ui_confirm "$@"
}

# ==============================================================================
# CORE UI: pager
# ==============================================================================
__ui_default_pager() {
  if _dep_ch bat; then
    bat --paging=always --theme=TwoDark --color=always
  elif _dep_ch gum; then
    # shellcheck disable=SC2086
    gum pager $CONFIG_GUM_PAGER_OPTS
  elif _dep_ch less; then
    less -R
  else
    more
  fi
}

__ui_rofi_pager() {
  ! [ -s "$CONFIG_ROFI_THEME_PAGER" ] && ui_notify_critical "$TXT_ROFI_NOT_CONFIGURED: where CONFIG_ROFI_THEME_PAGER=\"$CONFIG_ROFI_THEME_PAGER\""

  rofi -no-config -theme "$CONFIG_ROFI_THEME_PAGER" -dmenu -i -mesg "$TXT_ROFI_PAGER_MESSAGE" -p "$TXT_ROFI_PAGER_PROMPT"
}

_ui_pager() {
  case "$CONFIG_LAUNCHER" in
  rofi) __ui_rofi_pager ;;
  *) __ui_default_pager ;;
  esac
}

ui_pager() {
  _ui_pager
}

# ==============================================================================
# CORE UI: loader
# ==============================================================================
__ui_default_loader() {
  local cmd
  cmd="$*"

  if _dep_ch gum; then
    # shellcheck disable=SC2086
    gum spin $CONFIG_GUM_SPIN_OPTS --show-output -- $cmd
  else
    printf '%s\n' "$TXT_LOADING" >/dev/stderr
    $cmd
  fi

}

__ui_rofi_loader() {
  local cmd
  cmd="$*"

  ui_notify "$TXT_LOADING"
  $cmd
}

_ui_loader() {
  case "$CONFIG_LAUNCHER" in
  rofi) __ui_rofi_loader "$@" ;;
  *) __ui_default_loader "$@" ;;
  esac
}

ui_load() {
  _ui_loader "$@"
}

# ==============================================================================
# Previews
# ==============================================================================
__preview_fzf_create_shared_script() {
  if ! [ -s "$CLI_FZF_PREVIEW_SCRIPT" ]; then
    cat <<'EOF' >"$CLI_FZF_PREVIEW_SCRIPT"
#!/usr/bin/env sh
# ==============================================================================
# Shared script for fzf previews
# ==============================================================================

draw_divider() {
  ll=1
  while [ $ll -le $FZF_PREVIEW_COLUMNS ];do printf "${THEME_FZF_PREVIEW_DIVIDER}─${THEME_RESET}" ;ll=$(( ll + 1 ));done;
  echo
}

fzf_preview() {
  file=$1

  dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
  if [ "$dim" = "x" ]; then
    dim=$(stty size </dev/tty | awk "{print \$2 \"x\" \$1}")
  fi
  if ! [ "$CONFIG_IMAGE_RENDERER" = "icat" ] && [ -z "$KITTY_WINDOW_ID" ] && [ "$((FZF_PREVIEW_TOP + FZF_PREVIEW_LINES))" -eq "$(stty size </dev/tty | awk "{print \$1}")" ]; then
    dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
  fi

  if [ "$CONFIG_IMAGE_RENDERER" = "icat" ] && [ -z "$GHOSTTY_BIN_DIR" ]; then
    if command -v kitten >/dev/null 2>&1; then
      kitten icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    elif command -v icat >/dev/null 2>&1; then
      icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    else
      kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    fi

  elif [ -n "$GHOSTTY_BIN_DIR" ]; then
    if command -v kitten >/dev/null 2>&1; then
      kitten icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    elif command -v icat >/dev/null 2>&1; then
      icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    else
      chafa -s "$dim" "$file"
    fi
  elif command -v chafa >/dev/null 2>&1; then
    case "$CLI_PLATFORM" in
    android) chafa -s "$dim" "$file" ;;
    windows) chafa -f sixel -s "$dim" "$file" ;;
    *) chafa -s "$dim" "$file" ;;
    esac
    echo

  elif command -v imgcat >/dev/null; then
    imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"

  else
    printf "%s" "$TXT_PREVIEW_INSTALL_VIEWER"
  fi
}
EOF
  else
    return
  fi
}

__preview_fzf_generate_script_for_item() {
  local data_item
  local title
  local raw_title
  local id
  local image_path
  local view_count
  local live_status
  local description
  local channel
  local channel_follower_count
  local duration
  local timestamp
  local preview_script_path
  local script
  local img_url

  data_item="$1"

  eval "$(printf "%s" "$data_item" | jq -r '
    def format_number:
      tostring | split("") | reverse | join("") |
      gsub("(?<thousands>[0-9]{3})(?=[0-9])"; "\(.thousands),") |
      split("") | reverse | join("");

    def format_duration:
      try(
        if . >= 3600 then
          (./3600|floor) as $h | if $h == 1 then "\($h) hour" else "\($h) hours" end
        elif . >= 60 then
          (./60|floor) as $m | if $m == 1 then "\($m) min" else "\($m) mins" end
        else
          (./1|floor) as $s | if $s == 1 then "\($s) sec" else "\($s) secs" end
        end
      ) catch null;

    def format_timestamp:
      if .timestamp then
        (now - .timestamp) as $diff
        | if $diff < 60 then "Just now"
          elif $diff < 3600 then
            ($diff/60 | floor) as $mins
            | if $mins == 1 then "1 minute ago" else "\($mins) minutes ago" end
          elif $diff < 86400 then
            ($diff/3600 | floor) as $hours
            | if $hours == 1 then "1 hour ago" else "\($hours) hours ago" end
          elif $diff < 2592000 then
            ($diff/86400 | floor) as $days
            | if $days == 1 then "1 day ago" else "\($days) days ago" end
          elif $diff < 31536000 then
            ($diff/2592000 | floor) as $months
            | if $months == 1 then "1 month ago" else "\($months) months ago" end
          else
            ($diff/31536000 | floor) as $years
            | if $years == 1 then "1 year ago" else "\($years) years ago" end
          end
      elif .upload_date then
        (try (.upload_date | strptime("%Y%m%d") | mktime) catch null) as $epoch
        | if $epoch then
            (now - $epoch) as $diff
            | if $diff < 60 then "Just now"
              elif $diff < 3600 then
                ($diff/60 | floor) as $mins
                | if $mins == 1 then "1 minute ago" else "\($mins) minutes ago" end
              elif $diff < 86400 then
                ($diff/3600 | floor) as $hours
                | if $hours == 1 then "1 hour ago" else "\($hours) hours ago" end
              elif $diff < 2592000 then
                ($diff/86400 | floor) as $days
                | if $days == 1 then "1 day ago" else "\($days) days ago" end
              elif $diff < 31536000 then
                ($diff/2592000 | floor) as $months
                | if $months == 1 then "1 month ago" else "\($months) months ago" end
              else
                ($diff/31536000 | floor) as $years
                | if $years == 1 then "1 year ago" else "\($years) years ago" end
              end
          else null end
      else null
      end;
    [
      "id=" + (.id // "" | @sh),
      "title=" + (.title // "" | sub("^[0-9]+ "; "") | @sh | @sh),
      "raw_title=" + (.title // "" | @sh),
      "img_url=" + (.thumbnails[-1].url // "" | @sh),
      "view_count=" + (.view_count | if . == null then "null" else (format_number | @sh) end),
      "live_status=" + (if .live_status == "is_live" then ("Online" | @sh) elif .live_status == "was_live" then ("Offline" | @sh) else "null" end),
      "description=" + (.description | if . == null then "null" else (@sh | @sh) end),
      "channel=" + (.channel | if . == null then "null" else (@sh | @sh) end),
      "channel_follower_count=" + (.channel_follower_count | if . == null then "null" else (format_number | @sh) end),
      "duration=" + (.duration | format_duration | if . == null then "null" else @sh end),
      "timestamp=" + (format_timestamp | if . == null then "null" else @sh end)
    ] | join("\n")
')"

  preview_script_path="$CLI_PREVIEW_SCRIPTS_DIR/$(printf "%s" "$raw_title" | _util_generate_hash).sh"

  case "$img_url" in
  //*) img_url="https:$img_url" ;;
  esac

  image_path="$CLI_PREVIEW_IMGS_DIR/$(printf "%s" "$img_url" | _util_generate_hash).jpg"

  cat <<EOF >"$preview_script_path"
#!/usr/bin/env sh
# ==============================================================================
# This script is generated dynamically for each preview item and is executed 
# by fzf when that item is previewed. You can customize the content and layout 
# of the preview by modifying the way this script is generated in 
# __preview_fzf_generate_script_for_item function. The script has access to the 
# item's data through the variables defined at the beginning (title, image_path,
# view_count, live_status, description, channel, duration, timestamp).
# You can also use any utilities or functions defined in the shared script 
# ($CLI_FZF_PREVIEW_SCRIPT) to render images or format text.
# ==============================================================================

if [ -s "$CLI_FZF_PREVIEW_SCRIPT" ];then
  . "$CLI_FZF_PREVIEW_SCRIPT"
else
  exit 1
fi

if [ '$CONFIG_ENABLE_PREVIEW_IMAGES' = 'true' ];then
  if [ -s "$image_path" ];then
    fzf_preview "$image_path" 2>/dev/null;
  else
    printf '%s\n' "$TXT_FZF_PREVIEW_IMAGE_LOADING";
  fi
fi


draw_divider

title="\$(printf $title | fold -s -w "\$FZF_PREVIEW_COLUMNS")"
printf "${THEME_FZF_PREVIEW_TITLE}%s${THEME_FZF_PREVIEW_TITLE}\n" "\$title"

draw_divider

if ! [ $channel = "null" ];then
channel="\$(printf $channel | fold -s -w \$FZF_PREVIEW_COLUMNS)"
printf "${THEME_FZF_PREVIEW_KEY}${THEME_BOLD}${TXT_FZF_PREVIEW_CHANNEL}: ${THEME_RESET}%s\n" "${THEME_FZF_PREVIEW_VALUE}\$channel${THEME_RESET}";
fi

if ! [ "$channel_follower_count" = "null" ];then
channel_follower_count="\$(printf "$channel_follower_count" | fold -s -w \$FZF_PREVIEW_COLUMNS)"
printf "${THEME_FZF_PREVIEW_KEY}${THEME_BOLD}${TXT_FZF_PREVIEW_CHANNEL_FOLLOWERS}: ${THEME_RESET}%s\n" "${THEME_FZF_PREVIEW_VALUE}\$channel_follower_count${THEME_RESET}";
fi

if ! [ $channel = "null" ] && ! [ "$duration" = "null" ];then
draw_divider
fi

if ! [ "$duration" = "null" ];then
duration="\$(printf "$duration" | fold -s -w \$FZF_PREVIEW_COLUMNS)"
printf "${THEME_FZF_PREVIEW_KEY}${THEME_BOLD}${TXT_FZF_PREVIEW_DURATION}: ${THEME_RESET}%s\n" "${THEME_FZF_PREVIEW_VALUE}\$duration${THEME_RESET}";
fi

if ! [ "$view_count" = "null" ];then
view_count="\$(printf "$view_count" | fold -s -w \$FZF_PREVIEW_COLUMNS)"
printf "${THEME_FZF_PREVIEW_KEY}${THEME_BOLD}${TXT_FZF_PREVIEW_VIEW_COUNT}: ${THEME_RESET}%s\n" "${THEME_FZF_PREVIEW_VALUE}\$view_count views${THEME_RESET}";
fi

if ! [ "$live_status" = "null" ];then
live_status="\$(printf "$live_status" | fold -s -w \$FZF_PREVIEW_COLUMNS)"
printf "${THEME_FZF_PREVIEW_KEY}${THEME_BOLD}${TXT_FZF_PREVIEW_LIVE_STATUS}: ${THEME_RESET}%s\n" "${THEME_FZF_PREVIEW_VALUE}\$live_status${THEME_RESET}";
fi

if ! [ "$timestamp" = "null" ];then
timestamp="\$(printf "$timestamp" | fold -s -w \$FZF_PREVIEW_COLUMNS)"
printf "${THEME_FZF_PREVIEW_KEY}${THEME_BOLD}${TXT_FZF_PREVIEW_TIMESTAMP}: ${THEME_RESET}%s\n" "${THEME_FZF_PREVIEW_VALUE}\$timestamp${THEME_RESET}";
fi
if ! [ $channel = "null" ] || ! [ "$channel_follower_count" = "null" ] || ! [ "$duration" = "null" ] || ! [ "$view_count" = "null" ] || ! [ "$live_status" = "null" ] || ! [ "$timestamp" = "null" ];then
draw_divider
fi

if ! [ $description = "null" ];then
  printf "%s\n" $description|fold -s -w "\$FZF_PREVIEW_COLUMNS"
fi
EOF
}

_preview_fzf_generate_script() {
  local data_to_preview
  local i
  local data_item

  i=0
  data_to_preview="$1"

  __preview_fzf_create_shared_script

  printf "%s" "$data_to_preview" | jq -c '.entries[]' | while IFS= read -r data_item; do
    __preview_fzf_generate_script_for_item "$data_item"
  done
}

__preview_download_image() {
  local url
  local output_path
  local images_to_download_file
  url="$1"
  output_path="$2"
  images_to_download_file="$3"

  if ! [ -s "$output_path" ]; then
    printf 'url=%s\n' "$url" >>"$images_to_download_file"
    printf 'output=%s\n' "$output_path" >>"$images_to_download_file"
  fi
}

_preview_fzf_download_imgs() {
  local data_to_preview
  local images_to_download_file
  local url
  local output_path
  images_to_download_file="$CLI_CURRENT_STATE_DIR/images-to-download-$(date +%s)"
  data_to_preview="$1"

  printf "%s" "$data_to_preview" | jq -r '.entries[].thumbnails[-1].url' | while IFS= read -r url; do
    case "$url" in
    //*) url="https:$url" ;;
    esac

    output_path="$CLI_PREVIEW_IMGS_DIR/$(printf "%s" "$url" | _util_generate_hash).jpg"
    __preview_download_image "$url" "$output_path" "$images_to_download_file"
  done
  [ -s "$images_to_download_file" ] && curl -sL --parallel --parallel-max 5 --config "$images_to_download_file" >/dev/null 2>&1 && rm "$images_to_download_file" 2>/dev/null
}

_preview_fzf() {
  while IFS= read -r line; do
    if [ "$CONFIG_ENABLE_PREVIEW" = "true" ]; then
      printf '%s/%s.sh|%s\n' "$CLI_PREVIEW_SCRIPTS_DIR" "$(printf '%s' "$line" | _util_generate_hash)" "$line"
    else
      printf '%s\n' "$line"
    fi
  done

  local data_to_preview
  data_to_preview="$1"

  if [ "$CONFIG_ENABLE_PREVIEW" = "true" ]; then
    _preview_fzf_generate_script "$data_to_preview" &
    if [ "$CONFIG_ENABLE_PREVIEW_IMAGES" = "true" ]; then
      _preview_fzf_download_imgs "$data_to_preview" &
    fi
  fi
}

preview_fzf() {
  _preview_fzf "$@"
}

_preview_rofi() {
  local data_to_preview
  local images_to_download_file
  local input_titles
  local input_count
  local output_count
  local others
  local output
  data_to_preview="$1"
  output="$CLI_CURRENT_STATE_DIR/rofi-preview-output-$(date +%s)"
  images_to_download_file="$CLI_CURRENT_STATE_DIR/images-to-download-$(date +%s)"

  if [ "$CONFIG_ENABLE_PREVIEW" = "true" ] && [ -n "$data_to_preview" ]; then
    input_titles="$(cat)"

    # shellcheck disable=SC2141
    printf "%s" "$data_to_preview" | jq --compact-output -r '.entries[]' | while IFS='\n' read -r entry; do
      url=$(printf '%s' "$entry" | jq -r '.thumbnails[-1].url')
      title=$(printf '%s' "$entry" | jq -r '.title')
      if [ -n "$url" ] && [ "$url" != "null" ]; then
        case "$url" in
        //*) url="https:$url" ;;
        esac
        url_hash="$(printf "%s" "$url" | _util_generate_hash)"
        img_path="$CLI_PREVIEW_IMGS_DIR/${url_hash}.jpg"

        __preview_download_image "$url" "$img_path" "$images_to_download_file"
        printf '%s\0icon\x1f%s\n' "$title" "$img_path"
      else
        printf '%s\n' "$title"
      fi
    done >"$output"

    [ -s "$images_to_download_file" ] && curl -sL --parallel --parallel-max 5 --config "$images_to_download_file" >/dev/null 2>&1 && rm "$images_to_download_file" 2>/dev/null

    input_count=$(printf "%s" "$input_titles" | wc -l)
    output_count=$(wc -l <"$output")
    others=$((input_count + 1 - output_count))

    cat "$output"
    if [ "$others" -gt 0 ]; then
      printf '%s' "$input_titles" | tail -n "$others"
    fi
  else
    cat
  fi
}

preview_rofi() {
  _preview_rofi "$@"
}

_preview() {
  case "$CONFIG_LAUNCHER" in
  fzf) preview_fzf "$@" ;;
  rofi) preview_rofi "$@" ;;
  *) cat ;;
  esac
}

preview() {
  _preview "$@"
}

# ==============================================================================
# State Management
# ==============================================================================
__state_clean_prev() {
  local current_state_dir
  current_state_dir="$CLI_CURRENT_STATE_DIR/$STATE_CURRENT"
  ! [ -d "$current_state_dir" ] && ui_notify_critical "$TXT_STATE_MALFORMED_CURRENT"
  rm -rf "$current_state_dir"
}

_state_init() {
  STATE_CURRENT=0

  STATE_CURRENT_CHANNEL_RESULTS=""
  STATE_CURRENT_CHANNEL_RESULTS_URL=""
  STATE_CURRENT_CHANNEL_RESULTS_START=1
  STATE_CURRENT_CHANNEL_RESULTS_END="$CONFIG_PER_PAGE"

  STATE_CURRENT_CHANNEL_URL=""
  STATE_CURRENT_CHANNEL_TITLE=""

  STATE_CURRENT_PLAYLISTS_RESULTS=""
  STATE_CURRENT_PLAYLISTS_RESULTS_URL=""
  STATE_CURRENT_PLAYLISTS_RESULTS_TITLE=""
  STATE_CURRENT_PLAYLISTS_RESULTS_START=1
  STATE_CURRENT_PLAYLISTS_RESULTS_END="$CONFIG_PER_PAGE"

  STATE_CURRENT_PLAYLIST_RESULTS=""
  STATE_CURRENT_PLAYLIST_URL=""
  STATE_CURRENT_PLAYLIST_TITLE=""
  STATE_CURRENT_PLAYLIST_START=1
  STATE_CURRENT_PLAYLIST_END="$CONFIG_PER_PAGE"

  STATE_CURRENT_VIDEO=""
  STATE_CURRENT_VIDEO_URL=""
  STATE_CURRENT_VIDEO_TITLE=""
}

_state_push() {
  local current_state_dir

  STATE_CURRENT="$((STATE_CURRENT + 1))"
  current_state_dir="$CLI_CURRENT_STATE_DIR/$STATE_CURRENT"
  mkdir -p "$current_state_dir"

  cat <<EOF >"$current_state_dir/state.env"
STATE_CURRENT_CHANNEL_RESULTS='$(printf "%s" "$STATE_CURRENT_CHANNEL_RESULTS" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_CHANNEL_RESULTS_URL='$(printf "%s" "$STATE_CURRENT_CHANNEL_RESULTS_URL" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_CHANNEL_RESULTS_START='$STATE_CURRENT_CHANNEL_RESULTS_START'
STATE_CURRENT_CHANNEL_RESULTS_END='$STATE_CURRENT_CHANNEL_RESULTS_END'
STATE_CURRENT_CHANNEL_URL='$(printf "%s" "$STATE_CURRENT_CHANNEL_URL" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_CHANNEL_TITLE='$(printf "%s" "$STATE_CURRENT_CHANNEL_TITLE" | sed "s/'/'\\\\''/g")'

STATE_CURRENT_PLAYLISTS_RESULTS='$(printf "%s" "$STATE_CURRENT_PLAYLISTS_RESULTS" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_PLAYLISTS_RESULTS_URL='$(printf "%s" "$STATE_CURRENT_PLAYLISTS_RESULTS_URL" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_PLAYLISTS_RESULTS_TITLE='$(printf "%s" "$STATE_CURRENT_PLAYLISTS_RESULTS_TITLE" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_PLAYLISTS_RESULTS_START='$STATE_CURRENT_PLAYLISTS_RESULTS_START'
STATE_CURRENT_PLAYLISTS_RESULTS_END='$STATE_CURRENT_PLAYLISTS_RESULTS_END'

STATE_CURRENT_PLAYLIST_RESULTS='$(printf "%s" "$STATE_CURRENT_PLAYLIST_RESULTS" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_PLAYLIST_URL='$(printf "%s" "$STATE_CURRENT_PLAYLIST_URL" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_PLAYLIST_TITLE='$(printf "%s" "$STATE_CURRENT_PLAYLIST_TITLE" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_PLAYLIST_START='$STATE_CURRENT_PLAYLIST_START'
STATE_CURRENT_PLAYLIST_END='$STATE_CURRENT_PLAYLIST_END'

STATE_CURRENT_VIDEO='$(printf "%s" "$STATE_CURRENT_VIDEO" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_VIDEO_URL='$(printf "%s" "$STATE_CURRENT_VIDEO_URL" | sed "s/'/'\\\\''/g")'
STATE_CURRENT_VIDEO_TITLE='$(printf "%s" "$STATE_CURRENT_VIDEO_TITLE" | sed "s/'/'\\\\''/g")'
EOF
}

_state_pop() {
  local current_state_dir
  local pop_count
  local i
  i=0
  pop_count="${1:-1}"

  [ "$pop_count" -le 0 ] && return

  while [ "$i" -lt "$pop_count" ]; do
    __state_clean_prev
    STATE_CURRENT="$((STATE_CURRENT - 1))"
    i=$((i + 1))
  done

  [ "$((STATE_CURRENT))" -lt 1 ] && return

  current_state_dir="$CLI_CURRENT_STATE_DIR/$STATE_CURRENT"
  ! [ -d "$current_state_dir" ] && ui_notify_critical "$TXT_STATE_MALFORMED_CURRENT"

  # shellcheck disable=SC1091
  . "$current_state_dir/state.env" || ui_notify_critical "$TXT_STATE_MALFORMED_CURRENT"
}

# ==============================================================================
# Data Fetching
# ==============================================================================
__fetch_yt_dlp() {
  local url
  local playlist_start
  local playlist_end
  local custom_opts

  url="$1"
  playlist_start="$2"
  playlist_end="$3"
  shift
  shift
  shift
  custom_opts="$*"

  # shellcheck disable=SC2086
  ui_load yt-dlp "$url" \
    --dump-single-json \
    --flat-playlist \
    --playlist-start "$playlist_start" \
    --playlist-end "$playlist_end" \
    $custom_opts
}

__fetch_video_url() {
  local url
  url="$1"

  ui_load yt-dlp "$url" --quiet --no-warnings --get-url | head -n 1
}

__fetch_audio_url() {
  local url
  url="$1"

  ui_load yt-dlp "$url" --quiet --no-warnings --get-url | tail -n 1
}

__fetch_video_info() {
  local url
  url="$1"

  yt-dlp --dump-json "$url"
}

__fetch_video_info_by_field() {
  local url
  local field
  url="$1"
  field="$2"

  yt-dlp --print "$field" "$url"
}

__fetch_video_oembed() {
  local url
  url="$1"

  curl -s "https://www.youtube.com/oembed?url=$url&format=json"
}

_fetch_media() {
  local url
  local playlist_start
  local playlist_end
  local custom_opts

  [ -n "$CONFIG_BROWSER" ] && custom_opts="--cookies-from-browser $CONFIG_BROWSER"
  [ -n "$CONFIG_YT_DLP_OPTS" ] && custom_opts="$custom_opts $CONFIG_YT_DLP_OPTS"

  url="$1"
  playlist_start="$2"
  playlist_end="$3"
  shift
  shift
  shift
  custom_opts="$custom_opts $*"

  # shellcheck disable=SC2086
  __fetch_yt_dlp "$url" "$playlist_start" "$playlist_end" --extractor-args youtubetab:approximate_date $custom_opts
}

_fetch_playlist() {
  local url
  local custom_opts
  url="$1"
  shift
  custom_opts="$*"

  # shellcheck disable=SC2086
  STATE_CURRENT_PLAYLIST_RESULTS="$(
    _fetch_media "$url" "$STATE_CURRENT_PLAYLIST_START" "$STATE_CURRENT_PLAYLIST_END" $custom_opts |
      jq "
          .entries=(.entries 
          | to_entries
          | map(.value.title=\"\(.key+1|tostring| if (.|length) < 2 then \"0\" + . else . end) \"+.value.title) 
          |map(.value))
          "
  )"

  STATE_CURRENT_PLAYLIST_URL="$url"
  STATE_CURRENT_PLAYLIST_TITLE="$(printf '%s' "$STATE_CURRENT_PLAYLIST_RESULTS" | jq '.title' -r)"
}

fetch_playlist() {
  local url

  case "$1" in
  http://* | https://*) url="$1" ;;
  *) url="https://youtube.com/$1" ;;
  esac

  STATE_CURRENT_PLAYLIST_START=1
  STATE_CURRENT_PLAYLIST_END="$CONFIG_PER_PAGE"

  _fetch_playlist "$url"
  _state_push
}

fetch_playlist_next() {
  STATE_CURRENT_PLAYLIST_START="$((STATE_CURRENT_PLAYLIST_END + 1))"
  STATE_CURRENT_PLAYLIST_END="$((STATE_CURRENT_PLAYLIST_END + CONFIG_PER_PAGE))"

  _fetch_playlist "$STATE_CURRENT_PLAYLIST_URL"
  _state_push
}

fetch_playlist_prev() {
  [ "$((STATE_CURRENT_PLAYLIST_START - 1))" -gt 1 ] && _state_pop
}

_fetch_playlists() {
  local url
  local custom_opts
  url="$1"
  shift
  custom_opts="$*"

  # shellcheck disable=SC2086
  STATE_CURRENT_PLAYLISTS_RESULTS="$(
    _fetch_media "$url" "$STATE_CURRENT_PLAYLISTS_RESULTS_START" "$STATE_CURRENT_PLAYLISTS_RESULTS_END" $custom_opts |
      jq "
          .entries=(.entries 
          | to_entries
          | map(.value.title=\"\(.key+1|tostring| if (.|length) < 2 then \"0\" + . else . end) \"+.value.title) 
          |map(.value))
          "
  )"

  STATE_CURRENT_PLAYLISTS_RESULTS_URL="$(printf '%s' "$STATE_CURRENT_PLAYLISTS_RESULTS" | jq '.original_url' -r)"
  STATE_CURRENT_PLAYLISTS_RESULTS_TITLE="$(printf '%s' "$STATE_CURRENT_PLAYLISTS_RESULTS" | jq '.title' -r)"
}

fetch_playlists() {
  local url

  case "$1" in
  http://* | https://*) url="$1" ;;
  *) url="https://youtube.com/$1" ;;
  esac

  STATE_CURRENT_PLAYLISTS_RESULTS_START=1
  STATE_CURRENT_PLAYLISTS_RESULTS_END="$CONFIG_PER_PAGE"

  _fetch_playlists "$url"
  _state_push
}

fetch_playlists_next() {
  STATE_CURRENT_PLAYLISTS_RESULTS_START="$((STATE_CURRENT_PLAYLISTS_RESULTS_END + 1))"
  STATE_CURRENT_PLAYLISTS_RESULTS_END="$((STATE_CURRENT_PLAYLISTS_RESULTS_END + CONFIG_PER_PAGE))"

  _fetch_playlists "$STATE_CURRENT_PLAYLISTS_RESULTS_URL"
  _state_push
}

fetch_playlists_prev() {
  [ "$((STATE_CURRENT_PLAYLISTS_RESULTS_START - 1))" -gt 1 ] && _state_pop
}

_fetch_channels() {
  local url
  url="$1"

  STATE_CURRENT_CHANNEL_RESULTS="$(
    _fetch_media "$url" "$STATE_CURRENT_CHANNEL_RESULTS_START" "$STATE_CURRENT_CHANNEL_RESULTS_END" |
      jq "
          .entries=(.entries 
          | to_entries
          | map(.value.title=\"\(.key+1|tostring| if (.|length) < 2 then \"0\" + . else . end) \"+.value.title) 
          |map(.value))
          "
  )"

  STATE_CURRENT_CHANNEL_RESULTS_URL="$(printf '%s' "$STATE_CURRENT_CHANNEL_RESULTS" | jq '.original_url' -r)"
}

fetch_channels() {
  local url

  case "$1" in
  http://* | https://*) url="$1" ;;
  *) url="https://youtube.com/$1" ;;
  esac

  STATE_CURRENT_CHANNEL_RESULTS_START=1
  STATE_CURRENT_CHANNEL_RESULTS_END="$CONFIG_PER_PAGE"

  _fetch_channels "$url"
  _state_push
}

fetch_channels_next() {
  STATE_CURRENT_CHANNEL_RESULTS_START="$((STATE_CURRENT_CHANNEL_RESULTS_END + 1))"
  STATE_CURRENT_CHANNEL_RESULTS_END="$((STATE_CURRENT_CHANNEL_RESULTS_END + CONFIG_PER_PAGE))"

  _fetch_channels "$STATE_CURRENT_CHANNEL_RESULTS_URL"
  _state_push
}

fetch_channels_prev() {
  [ "$((STATE_CURRENT_CHANNEL_RESULTS_START - 1))" -gt 1 ] && _state_pop
}

_fetch_yt_subs() {
  local channels_data

  if [ -z "$CONFIG_BROWSER" ]; then
    ui_notify_warning "$TXT_CONFIG_BROWSER_NOT_SET"
    return
  fi
  if ui_confirm "$TXT_MENU_MISC_SYNC_SUBS_CONFIRM"; then
    ui_notify "$TXT_MENU_MISC_SYNC_SUBS_START"

    channels_data=$(yt-dlp "https://www.youtube.com/feed/channels" --flat-playlist --dump-single-json --cookies-from-browser "$CONFIG_BROWSER")
    if [ -n "$channels_data" ]; then
      printf '%s' "$channels_data" >"$CLI_SUBSCRIPTIONS_FILE"
    else
      ui_notify_error "$TXT_MENU_MISC_SYNC_SUBS_FAILED"
    fi
  fi

}

fetch_yt_subs() {
  _fetch_yt_subs
}

# ==============================================================================
# Data parsers
# ==============================================================================
_parse_search_filter() {
  case "$1" in
  hour) printf '%s' "EgIIAQ%253D%253D" ;;
  today) printf '%s' "EgIIAg%253D%253D" ;;
  week) printf '%s' "EgIIAw%253D%253D" ;;
  month) printf '%s' "EgIIBA%253D%253D" ;;
  year) printf '%s' "EgIIBQ%253D%253D" ;;
  video) printf '%s' "EgIQAQ%253D%253D" ;;
  movie) printf '%s' "EgIQBA%253D%253D" ;;
  live) printf '%s' "EgJAAQ%253D%253D" ;;
  short) printf '%s' "EgQQARgB" ;;
  long) printf '%s' "EgQQARgC" ;;
  4k) printf '%s' "EgJwAQ%253D%253D" ;;
  hd) printf '%s' "EgIgAQ%253D%253D" ;;
  subtitles) printf '%s' "EgIoAQ%253D%253D" ;;
  360) printf '%s' "EgJ4AQ%253D%253D" ;;
  vr) printf '%s' "EgLIAQ%253D%253D" ;;
  3d) printf '%s' "EgI4AQ%253D%253D" ;;
  hdr) printf '%s' "EgPIAQ%253D%253D" ;;
  local) printf '%s' "EgO4AQ%253D%253D" ;;
  newest) printf '%s' "CAISAhAB" ;;
  views) printf '%s' "CAMSAhAB" ;;
  rating) printf '%s' "CAESAhAB" ;;
  *) return 1 ;;
  esac
}

parse_search_filter() {
  _parse_search_filter "$@"
}

# ==============================================================================
# Data downloading
# ==============================================================================
__download_yt_dlp() {
  local url

  url="$1"
  shift

  # shellcheck disable=SC2086
  yt-dlp "$url" "$@"
}

_download_media() {
  local url
  local custom_opts

  url="$1"
  shift

  [ -n "$CONFIG_BROWSER" ] && custom_opts="--cookies-from-browser $CONFIG_BROWSER"

  # shellcheck disable=SC2086
  __download_yt_dlp "$url" $custom_opts "$@"
}

_download_video() {
  _download_media "$STATE_CURRENT_VIDEO_URL" \
    --output "$CONFIG_DOWNLOAD_DIR/video/individual/%(channel)s/%(title)s.%(ext)s"
}

download_video() {
  _download_video
}

_download_audio() {
  _download_media "$STATE_CURRENT_VIDEO_URL" \
    --extract-audio --audio-format mp3 \
    --output "$CONFIG_DOWNLOAD_DIR/audio/individual/%(channel)s/%(title)s.%(ext)s"
}

download_audio() {
  _download_audio
}

_download_playlist_video() {
  local download_archive
  local enumerate_playlist
  local playlist_title

  playlist_title="$(printf '%s' "$STATE_CURRENT_PLAYLIST_TITLE" | ui_prompt "$TXT_ENTER_PLAYLIST_TITLE" "$STATE_CURRENT_PLAYLIST_TITLE")"
  [ -z "$playlist_title" ] && return

  [ "$CONFIG_DOWNLOADS_ENUMERATE" = "true" ] && enumerate_playlist="%(playlist_index)s - "

  # TODO: could be more unique by passing in user specified yt-dlp opts
  download_archive="$(_util_generate_hash "video - $STATE_CURRENT_PLAYLIST_URL")"

  _download_media "$STATE_CURRENT_PLAYLIST_URL" \
    --output "$CONFIG_DOWNLOAD_DIR/video/$playlist_title/%(channel)s/$enumerate_playlist%(title)s.%(ext)s" \
    --download-archive "$CLI_DOWNLOAD_ARCHIVE_DIR/$download_archive"
}

download_playlist_video() {
  _download_playlist_video
}

_download_playlist_audio() {
  local download_archive
  local enumerate_playlist
  local playlist_title

  playlist_title="$(printf '%s' "$STATE_CURRENT_PLAYLIST_TITLE" | ui_prompt "$TXT_ENTER_PLAYLIST_TITLE" "$STATE_CURRENT_PLAYLIST_TITLE")"
  [ -z "$playlist_title" ] && return

  [ "$CONFIG_DOWNLOADS_ENUMERATE" = "true" ] && enumerate_playlist="%(playlist_index)s - "

  # TODO: could be more unique
  download_archive="$(_util_generate_hash "audio - $STATE_CURRENT_PLAYLIST_URL")"

  _download_media "$STATE_CURRENT_PLAYLIST_URL" \
    --extract-audio --audio-format mp3 \
    --output "$CONFIG_DOWNLOAD_DIR/audio/$playlist_title/%(channel)s/$enumerate_playlist%(title)s.%(ext)s" \
    --download-archive "$CLI_DOWNLOAD_ARCHIVE_DIR/$download_archive"
}

download_playlist_audio() {
  _download_playlist_audio
}

# ==============================================================================
# Data Caching
# ==============================================================================
__cached_mix_path() {
  local url
  local video_id
  local _mix_data
  local cached_mix_path

  url="$1"
  video_id=$(printf '%s' "$url" | sed 's/.*RD//g')

  cached_mix_path="$CLI_AUTO_GEN_PLAYLISTS/$(_util_generate_hash "https://www.youtube.com/watch?v=${video_id}&list=RD$video_id").m3u8"

  if ! [ -s "$cached_mix_path" ]; then
    _mix_data="$(yt-dlp "https://www.youtube.com/watch?v=${video_id}&list=RD$video_id" --flat-playlist --dump-single-json 2>/dev/null)"
    [ -z "$_mix_data" ] && printf '%s' "$url" && return

    # NOTE: the extended metadata below is not available in the mix data of yt
    # but other sites may have them. You could easily inject them using jq like:
    # "#EXTALB:\(.album)\n#EXTGENRE:\(.genre)\n#EXTGRP:\(.group)\n"

    printf '%s' "$_mix_data" | jq -r '
      "#EXTM3U",
      (.entries[]? | "#EXTINF:-1,\(.title)\n\(.url)\n")
    ' >"$cached_mix_path"
  fi

  printf '%s' "$cached_mix_path"
}

_cached_mix_path_or_url() {
  local url
  url="$1"

  if printf '%s' "$url" | grep -q "list=RD"; then
    __cached_mix_path "$url"
  else
    printf '%s' "$url"
  fi
}

# ==============================================================================
# Data Viewers
# ==============================================================================
__player_mpv() {
  local url
  local mode
  local opts
  local mpv_cmd

  mode="$2"
  opts="$CONFIG_MPV_ARGS"

  case "$CLI_PLATFORM" in
  android)
    case "$mode" in
    listen)
      url="$(__fetch_audio_url "$1")"
      ;;
    *)
      url="$(__fetch_video_url "$1")"
      ;;
    esac
    nohup am start --user 0 -a android.intent.action.VIEW -d "$url" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 &
    ;;
  *)
    url="$1"
    case "$mode" in
    listen)
      opts="$opts --no-video --force-window=no"
      ;;
    esac

    if _dep_ch mpv; then
      mpv_cmd="mpv"
    elif _dep_ch "mpv.exe"; then
      mpv_cmd="mpv.exe"
    else
      ui_notify_critical "$TXT_PLAYER_NOT_FOUND"
    fi

    if [ "$CONFIG_DISOWN_PLAYER" = "true" ]; then
      # shellcheck disable=SC2086
      nohup $mpv_cmd "$url" $opts &
    else
      # shellcheck disable=SC2086
      $mpv_cmd "$url" $opts
    fi
    ;;
  esac

  # shellcheck disable=SC2181
  if [ "$?" = "0" ]; then
    clear
  fi
}

__player_vlc() {
  local url
  local mode
  local opts
  local vlc_cmd

  mode="$2"
  opts="$CONFIG_VLC_ARGS"

  case "$CLI_PLATFORM" in
  android)
    case "$mode" in
    listen)
      url="$(__fetch_audio_url "$1")"
      ;;
    *)
      url="$(__fetch_video_url "$1")"
      ;;
    esac
    nohup am start --user 0 -a android.intent.action.VIEW -d "$url" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "$STATE_CURRENT_VIDEO_TITLE" >/dev/null 2>&1 &
    ;;
  *)
    url="$1"

    if _dep_ch vlc; then
      vlc_cmd="vlc"
    elif _dep_ch "vlc.exe"; then
      vlc_cmd="vlc.exe"
    else
      ui_notify_critical "$TXT_PLAYER_NOT_FOUND"
    fi

    if [ "$CONFIG_DISOWN_PLAYER" = "true" ]; then
      # shellcheck disable=SC2086
      nohup $vlc_cmd "$url" $opts &
    else
      # shellcheck disable=SC2086
      $vlc_cmd "$url" $opts
    fi
    ;;
  esac

  # shellcheck disable=SC2181
  if [ "$?" = "0" ]; then
    clear
  fi
}

__player_tplay() {
  local url
  local mode
  local opts
  local tplay_cmd

  mode="$2"
  opts="$CONFIG_TPLAY_ARGS"

  case "$CLI_PLATFORM" in
  android)
    case "$mode" in
    listen)
      url="$(__fetch_audio_url "$1")"
      ;;
    *)
      url="$(__fetch_video_url "$1")"
      ;;
    esac
    nohup am start --user 0 -a android.intent.action.VIEW -d "$url" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 &
    ;;
  *)
    url="$1"

    if _dep_ch tplay; then
      tplay_cmd="tplay"
    else
      ui_notify_critical "$TXT_PLAYER_NOT_FOUND"
    fi

    # shellcheck disable=SC2086
    $tplay_cmd "$url" $opts
    ;;
  esac

  # shellcheck disable=SC2181
  if [ "$?" = "0" ]; then
    clear
  fi
}

_play() {
  case "$CONFIG_PLAYER" in
  mpv) __player_mpv "$1" "$2" ;;
  vlc) __player_vlc "$1" "$2" ;;
  tplay) __player_tplay "$1" "$2" ;;
  *) ui_notify_critical "$TXT_PLAYER_NOT_FOUND" ;;
  esac
}

play() {
  _play "$@"
}

# ==============================================================================
# Data Local
# ==============================================================================
_update_recent() {
  local current_recent
  local id
  local video_entry

  current_recent='{"entries":[]}'
  [ -s "$CLI_RECENT_FILE" ] && current_recent=$(cat "$CLI_RECENT_FILE")

  id=$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.id' -r)
  video_entry="$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.title |= sub("^[0-9]+ "; "")')"

  printf '%s' "$current_recent" |
    jq --arg id "$id" --argjson video_entry "$video_entry" '
    {
      "entries":[
        .entries[]
        | select(.id != $id)
        ]
    }
    |.entries+=[$video_entry]
    ' | jq --argjson limit "$CONFIG_NO_OF_RECENT" '
      {"entries":.entries[-$limit:]}
      ' >"$CLI_RECENT_FILE"
}

_update_saved_videos() {
  local current_saved_videos
  local id
  local video_entry

  current_saved_videos='{"entries":[]}'
  [ -s "$CLI_SAVED_VIDEOS_FILE" ] && current_saved_videos=$(cat "$CLI_SAVED_VIDEOS_FILE")

  id=$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.id' -r)
  video_entry="$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.title |= sub("^[0-9]+ "; "")')"

  printf '%s' "$current_saved_videos" |
    jq --arg id "$id" --argjson video_entry "$video_entry" '
    {
      "entries":[
        .entries[]
        | select(.id != $id)
        ]
    }
    |.entries+=[$video_entry]
    ' >"$CLI_SAVED_VIDEOS_FILE"
}

_update_saved_playlists() {
  local custom_playlist
  local custom_playlists
  local playlist_name
  local playlist_id

  custom_playlists="[]"
  [ -s "$CLI_CUSTOM_PLAYLISTS_FILE" ] && custom_playlists=$(cat "$CLI_CUSTOM_PLAYLISTS_FILE")

  playlist_name=$(printf '%s' "$STATE_CURRENT_PLAYLIST_TITLE" | ui_prompt "$TXT_MENU_PLAYLIST_ACTIONS_PROMPT_SAVE_PLAYLIST" "$STATE_CURRENT_PLAYLIST_TITLE")
  [ -z "$playlist_name" ] && return
  playlist_id=$(printf '%s' "$STATE_CURRENT_PLAYLIST_RESULTS" | jq -r ".id")

  printf '%s' "$custom_playlists" | jq \
    --arg id "$playlist_id" \
    --arg name "$playlist_name" \
    --arg url "$STATE_CURRENT_PLAYLIST_URL" '
      .+=[
        {
          "id": $id,
          "name": $name,
          "url": $url
        }
      ]
      ' >"$CLI_CUSTOM_PLAYLISTS_FILE"
}

_remove_recent() {
  local current_recent
  local id

  current_recent='{"entries":[]}'
  [ -s "$CLI_RECENT_FILE" ] && current_recent=$(cat "$CLI_RECENT_FILE")

  id=$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.id' -r)

  printf '%s' "$current_recent" |
    jq --arg id "$id" '{
    "entries":[
      .entries[]
      | select(.id != $id)
      ]
    }' >"$CLI_RECENT_FILE"
}

_remove_saved_video() {
  local current_saved_videos
  local id

  current_saved_videos='{"entries":[]}'
  [ -s "$CLI_SAVED_VIDEOS_FILE" ] && current_saved_videos=$(cat "$CLI_SAVED_VIDEOS_FILE")

  id=$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.id' -r)

  printf '%s' "$current_saved_videos" |
    jq --arg id "$id" '{
    "entries":[
      .entries[]
      | select(.id != $id)
      ]
    }' >"$CLI_SAVED_VIDEOS_FILE"
}

_remove_saved_playlist() {
  local custom_playlist
  local playlist_id

  custom_playlists="[]"
  [ -s "$CLI_CUSTOM_PLAYLISTS_FILE" ] && custom_playlists=$(cat "$CLI_CUSTOM_PLAYLISTS_FILE")

  playlist_id=$(printf '%s' "$STATE_CURRENT_PLAYLIST_URL" | sed 's/.*list=//g')

  printf '%s' "$custom_playlists" |
    jq --arg id "$playlist_id" 'select(.id != $id)' >"$CLI_CUSTOM_PLAYLISTS_FILE"
}

_get_search_history_entry() {
  local n
  local total
  local line

  n="$1"

  total=$(wc -l <"$CLI_SEARCH_HISTORY_FILE")
  n=$((total - n + 1))
  [ "$n" -lt 1 ] && return 1
  sed -n "${n}p" "$CLI_SEARCH_HISTORY_FILE"
}

# ==============================================================================
# Menus: playlist actions
# ==============================================================================
__menu_playlist_actions_watch() {
  printf "${THEME_PLAYER_START}$TXT_PLAYER_START_WATCHING:${THEME_RESET} %s\n" "$STATE_CURRENT_VIDEO_TITLE"

  play "$(_cached_mix_path_or_url "$STATE_CURRENT_VIDEO_URL")" "watch"
  _update_recent
}

__menu_playlist_actions_watch_all() {
  printf "${THEME_PLAYER_START}$TXT_PLAYER_START_WATCHING:${THEME_RESET} %s\n" "$STATE_CURRENT_PLAYLIST_TITLE"

  play "$(_cached_mix_path_or_url "$STATE_CURRENT_PLAYLIST_URL")" "watch"
  _update_recent
}

__menu_playlist_actions_listen() {
  printf "${THEME_PLAYER_START}$TXT_PLAYER_START_LISTENING:${THEME_RESET} %s\n" "$STATE_CURRENT_VIDEO_TITLE"

  play "$(_cached_mix_path_or_url "$STATE_CURRENT_VIDEO_URL")" "listen"
  _update_recent
}

__menu_playlist_actions_listen_all() {
  printf "${THEME_PLAYER_START}$TXT_PLAYER_START_LISTENING:${THEME_RESET} %s\n" "$STATE_CURRENT_PLAYLIST_TITLE"

  play "$(_cached_mix_path_or_url "$STATE_CURRENT_PLAYLIST_URL")" "listen"
  _update_recent
}

__menu_playlist_actions_mix() {
  local video_id
  local url

  video_id=$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.id' -r)
  url="watch?v=${video_id}&list=RD$video_id"

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_playlist_actions_download() {
  download_video
  ui_notify "$TXT_DOWNLOAD_COMPLETED $STATE_CURRENT_VIDEO_TITLE"
}

__menu_playlist_actions_download_all() {
  download_playlist_video
  ui_notify "$TXT_DOWNLOAD_COMPLETED $STATE_CURRENT_PLAYLIST_TITLE"
}

__menu_playlist_actions_download_audio() {
  download_audio
  ui_notify "$TXT_DOWNLOAD_COMPLETED $STATE_CURRENT_VIDEO_TITLE"
}

__menu_playlist_actions_download_all_audio() {
  download_playlist_audio
  ui_notify "$TXT_DOWNLOAD_COMPLETED $STATE_CURRENT_PLAYLIST_TITLE"
}

__menu_playlist_actions_save() {
  # TODO: integerate with watch later maybe
  # though i doubt anyone wants make theirs any longer lol
  # For now a pseudo like feature is just as useful
  _update_saved_videos
}

__menu_playlist_actions_unsave() {
  _remove_saved_video
}

__menu_playlist_actions_save_playlist() {
  # TODO: integerate with yt playlists maybe
  # For now a pseudo like feature is just as useful
  _update_saved_playlists
}

__menu_playlist_actions_unsave_playlist() {
  _remove_saved_playlist
}

__menu_playlist_actions_shell() {
  local init_text
  local user_shell

  export \
    STATE_CURRENT_CHANNEL_RESULTS \
    STATE_CURRENT_CHANNEL_RESULTS_START \
    STATE_CURRENT_CHANNEL_RESULTS_END \
    STATE_CURRENT_CHANNEL_URL \
    STATE_CURRENT_CHANNEL_TITLE \
    STATE_CURRENT_PLAYLISTS_RESULTS \
    STATE_CURRENT_PLAYLISTS_RESULTS_URL \
    STATE_CURRENT_PLAYLISTS_RESULTS_TITLE \
    STATE_CURRENT_PLAYLISTS_RESULTS_START \
    STATE_CURRENT_PLAYLISTS_RESULTS_END \
    STATE_CURRENT_PLAYLIST_START \
    STATE_CURRENT_PLAYLIST_END \
    STATE_CURRENT_PLAYLIST_RESULTS \
    STATE_CURRENT_PLAYLIST_URL \
    STATE_CURRENT_PLAYLIST_TITLE \
    STATE_CURRENT_VIDEO \
    STATE_CURRENT_VIDEO_URL \
    STATE_CURRENT_VIDEO_TITLE

  export CLI_NAME CONFIG_DOWNLOAD_DIR CLI_DOWNLOAD_ARCHIVE_DIR

  init_text="\
 $CLI_HEADER
 $TXT_MENU_PLAYLIST_ACTIONS_SHELL_WELCOME
 $TXT_MENU_PLAYLIST_ACTIONS_SHELL_INTRO
  $TXT_MENU_PLAYLIST_ACTIONS_SHELL_VARIABLES
  - CLI_ARCHIVE_DIR
  - CONFIG_DOWNLOAD_DIR
  - STATE_CURRENT_CHANNEL_RESULTS
  - STATE_CURRENT_CHANNEL_RESULTS_START
  - STATE_CURRENT_CHANNEL_RESULTS_END
  - STATE_CURRENT_CHANNEL_URL
  - STATE_CURRENT_CHANNEL_TITLE
  - STATE_CURRENT_PLAYLISTS_RESULTS
  - STATE_CURRENT_PLAYLISTS_RESULTS_URL
  - STATE_CURRENT_PLAYLISTS_RESULTS_TITLE
  - STATE_CURRENT_PLAYLISTS_RESULTS_START
  - STATE_CURRENT_PLAYLISTS_RESULTS_END 
  - STATE_CURRENT_PLAYLIST_START
  - STATE_CURRENT_PLAYLIST_END
  - STATE_CURRENT_PLAYLIST_RESULTS
  - STATE_CURRENT_PLAYLIST_URL
  - STATE_CURRENT_PLAYLIST_TITLE
  - STATE_CURRENT_VIDEO
  - STATE_CURRENT_VIDEO_URL
  - STATE_CURRENT_VIDEO_TITLE
"
  user_shell="$(ps -o comm= -p "$PPID")"
  if [ "$user_shell" = "fish" ]; then
    _util_terminal_exec fish
  else
    local init_file
    init_file="$(mktemp /tmp/yt_x_sh_init.XXXXXX)"

    cat <<EOF >"$init_file"
printf "%s" "$init_text"
EOF
    ENV="$init_file" _util_terminal_exec sh -i
  fi

}

__menu_playlist_actions_open_in_browser() {
  _util_open "$STATE_CURRENT_VIDEO_URL" || ui_notify_error "$TXT_MENU_PLAYLIST_ACTIONS_OPEN_IN_BROWSER_ERROR"
}

__menu_playlist_actions_visit_channel() {
  local channel_url
  local channel_id
  local uploader_id
  local channel_entry

  channel_url="$(printf '%s' "$STATE_CURRENT_VIDEO" | jq -r ".channel_url // .uploader_url // empty")"
  case "$channel_url" in
  //*) channel_url="https:$channel_url" ;;
  esac

  if [ -z "$channel_url" ]; then
    channel_id="$(printf '%s' "$STATE_CURRENT_VIDEO" | jq -r ".channel_id // empty")"
    uploader_id="$(printf '%s' "$STATE_CURRENT_VIDEO" | jq -r ".uploader_id // empty")"

    if [ -n "$channel_id" ]; then
      channel_url="https://www.youtube.com/channel/$channel_id"
    elif [ -n "$uploader_id" ]; then
      case "$uploader_id" in
      @*) channel_url="https://www.youtube.com/$uploader_id" ;;
      *) channel_url="https://www.youtube.com/channel/$uploader_id" ;;
      esac
    fi
  fi

  if [ -z "$channel_url" ] || [ "$channel_url" = "null" ]; then
    if [ -n "$STATE_CURRENT_PLAYLIST_RESULTS" ]; then
      channel_url="$(printf '%s' "$STATE_CURRENT_PLAYLIST_RESULTS" | jq -r ".channel_url // .uploader_url // empty")"
    fi
  fi

  if [ -z "$channel_url" ] || [ "$channel_url" = "null" ]; then
    channel_url="$(__fetch_video_oembed "$STATE_CURRENT_VIDEO_URL" | jq -r ".author_url // empty")"
  fi

  if [ -z "$channel_url" ] || [ "$channel_url" = "null" ]; then
    channel_url="$(__fetch_video_info_by_field "$STATE_CURRENT_VIDEO_URL" "channel_url")"
  fi

  if [ -z "$channel_url" ]; then
    ui_notify_error "$TXT_MENU_PLAYLIST_ACTIONS_CHANNEL_NOT_FOUND"
    return
  fi

  if ! channel_entry="$(
    printf '%s' "$STATE_CURRENT_VIDEO" |
      jq -c --arg url "$channel_url" '{
      "id": (.channel_id // .uploader_id // ""),
      "title": (.channel // .uploader // $url),
      "url": $url
    }'
  )"; then
    ui_notify_error "$TXT_MENU_PLAYLIST_ACTIONS_CHANNEL_NOT_FOUND"
    return
  fi
  if ! printf '%s' "$channel_entry" | jq -e ". != null" >/dev/null 2>&1; then
    ui_notify_error "$TXT_MENU_PLAYLIST_ACTIONS_CHANNEL_NOT_FOUND"
    return
  fi

  STATE_CURRENT_CHANNEL_URL="$channel_url"
  STATE_CURRENT_CHANNEL_TITLE="$(printf '%s' "$channel_entry" | jq -r ".title")"
  if ! STATE_CURRENT_CHANNEL_RESULTS="$(jq -n --argjson channel "$channel_entry" '{"entries":[$channel]}')"; then
    ui_notify_error "$TXT_MENU_PLAYLIST_ACTIONS_CHANNEL_NOT_FOUND"
    return
  fi
  _state_push
  menu_channel_actions
}

__menu_playlist_actions_subscribe_to_channel() {
  # TODO: use youtube oembed and yt-dlp to enable channel subscriptions
  ui_notify_error "$TXT_MENU_PLAYLIST_ACTIONS_SUBSCRIBE_UNSUPPORTED"
}

__menu_playlist_actions_toggle_enumerate_downloads() {
  if $CONFIG_DOWNLOADS_ENUMERATE; then
    CONFIG_DOWNLOADS_ENUMERATE=false
  else
    CONFIG_DOWNLOADS_ENUMERATE=true
  fi
}

# shellcheck disable=SC2120
_menu_playlist_actions() {
  local actions
  local media_action
  local sort
  local menu_options_filter
  local extra_menu_options
  local extra_menu_options_handler

  sort="$1"
  menu_options_filter="$2"
  extra_menu_options="$3"
  extra_menu_options_handler="$4"

  actions="\
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_WATCH}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_WATCH}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_WATCH_ALL}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_WATCH_ALL}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_LISTEN}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_LISTEN}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_LISTEN_ALL}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_LISTEN_ALL}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_MIX}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_MIX}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_SAVE}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_SAVE}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_UNSAVE}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_UNSAVE}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_SAVE_PLAYLIST}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_SAVE_PLAYLIST}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_UNSAVE_PLAYLIST}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_UNSAVE_PLAYLIST}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_VISIT_CHANNEL}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_VISIT_CHANNEL}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_SUBSCRIBE}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_SUBSCRIBE}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD_AUDIO}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_AUDIO}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL_AUDIO}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL_AUDIO}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_BROWSER}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_BROWSER}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_TOGGLE_ENUM}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_TOGGLE_ENUM}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_SHELL}${THEME_RESET}  ${TXT_MENU_PLAYLIST_ACTIONS_SHELL}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_PLAYLIST_ACTIONS_BACK}${THEME_RESET}  ${TXT_MENU_BACK}
 ${THEME_FZF_ICON_COLOR_ERROR}${TXT_ICON_MENU_PLAYLIST_ACTIONS_EXIT}${THEME_RESET}  ${TXT_MENU_EXIT}"

  if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
    actions="$(printf '%s\n%s' "$actions" "$extra_menu_options")"
  fi

  if [ -n "$menu_options_filter" ]; then
    actions="$(printf '%s' "$actions" | grep --invert-match "$menu_options_filter")"
  fi

  if [ -n "$sort" ]; then
    actions="$(_util_menu_sort "$actions" "$sort")"
  fi

  while true; do
    if [ -n "$CMD_MEDIA_ACTION" ]; then
      media_action="$CMD_MEDIA_ACTION"
      unset -v CMD_MEDIA_ACTION
    else
      media_action="$(
        printf "%s" "$actions" |
          ui_launcher "$TXT_MENU_PLAYLIST_PROMPT_ACTION" | sed 's/.*  //g'
      )"
    fi
    case "$media_action" in
    "$TXT_MENU_PLAYLIST_ACTIONS_WATCH") __menu_playlist_actions_watch ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_WATCH_ALL") __menu_playlist_actions_watch_all ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_LISTEN") __menu_playlist_actions_listen ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_LISTEN_ALL") __menu_playlist_actions_listen_all ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_MIX") __menu_playlist_actions_mix ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD") __menu_playlist_actions_download ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_AUDIO") __menu_playlist_actions_download_audio ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL") __menu_playlist_actions_download_all ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL_AUDIO") __menu_playlist_actions_download_all_audio ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_SAVE") __menu_playlist_actions_save ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_UNSAVE") __menu_playlist_actions_unsave ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_SAVE_PLAYLIST") __menu_playlist_actions_save_playlist ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_UNSAVE_PLAYLIST") __menu_playlist_actions_unsave_playlist ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_VISIT_CHANNEL") __menu_playlist_actions_visit_channel ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_SUBSCRIBE") __menu_playlist_actions_subscribe_to_channel ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_BROWSER") __menu_playlist_actions_open_in_browser ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_TOGGLE_ENUM") __menu_playlist_actions_toggle_enumerate_downloads ;;
    "$TXT_MENU_PLAYLIST_ACTIONS_SHELL") __menu_playlist_actions_shell ;;
    "$TXT_MENU_BACK" | "") _state_pop && break ;;
    "$TXT_MENU_EXIT") _util_byebye ;;
    *)
      if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
        if ! $extra_menu_options_handler "$media_action"; then
          _ui_notify_error "${TXT_MENU_INVALID_ACTION}: $media_action"
        fi
      fi
      ;;
    esac
    if [ "$CMD_MEDIA_EXIT" = true ]; then
      _util_byebye
    fi
  done
}

menu_playlist_actions() {
  _menu_playlist_actions
}

# ==============================================================================
# Menus: playlist explorer
# ==============================================================================
__menu_playlist_explorer_next() {
  fetch_playlist_next
}

__menu_playlist_explorer_previous() {
  fetch_playlist_prev
}

__menu_playlist_explorer() {
  local title
  local id
  local video

  title="$1"
  # TODO: consider not using -E since its not yet posix
  # though apparently its suppose to be made standard soon
  # and most environments support it, so it should be fine for now
  id=$(printf "%s" "$title" | sed -E 's/^0*([0-9]+) .*/\1/')

  STATE_CURRENT_VIDEO="$(
    printf '%s' "$STATE_CURRENT_PLAYLIST_RESULTS" |
      jq --argjson id "$((id - 1))" '.entries[$id]'
  )"
  STATE_CURRENT_VIDEO_URL="$(printf '%s' "$STATE_CURRENT_VIDEO" | jq '.url' -r)"
  STATE_CURRENT_VIDEO_TITLE="$(printf '%s' "$title" | sed -E 's/^[0-9]+ //g')"
  _state_push

  menu_playlist_actions
}

_menu_playlist_explorer() {
  local other_choices
  local titles
  local choice
  local base_state

  base_state="$STATE_CURRENT"

  other_choices="\
$TXT_MENU_NEXT
$TXT_MENU_PREVIOUS
$TXT_MENU_BACK
$TXT_MENU_EXIT"

  while true; do
    titles=$(printf '%s\n' "$STATE_CURRENT_PLAYLIST_RESULTS" | jq '.entries[].title' -r)

    if [ "$CMD_PLAYLIST_RESULTS_SKIP" = "true" ]; then
      choice="$(printf '%s' "$titles" | head -n 1)"
      unset -v CMD_PLAYLIST_RESULTS_SKIP
    else
      choice="$(
        printf "%s\n%s" "$titles" "$other_choices" |
          preview "$STATE_CURRENT_PLAYLIST_RESULTS" |
          ui_launcher_with_preview "$TXT_MENU_PLAYLIST_EXPLORER_PROMPT"
      )"
    fi

    case "$choice" in
    "$TXT_MENU_NEXT") __menu_playlist_explorer_next ;;
    "$TXT_MENU_PREVIOUS") __menu_playlist_explorer_previous ;;
    "$TXT_MENU_BACK" | "") _state_pop "$((STATE_CURRENT - base_state + 1))" && break ;;
    "$TXT_MENU_EXIT") _util_byebye ;;
    *) __menu_playlist_explorer "$choice" ;;
    esac
  done
}

menu_playlist_explorer() {
  _menu_playlist_explorer
}

# ==============================================================================
# Menus: playlists explorer
# ==============================================================================
__menu_playlists_explorer_next() {
  fetch_playlists_next
}

__menu_playlists_explorer_previous() {
  fetch_playlists_prev
}

__menu_playlists_explorer() {
  local title
  local id
  local current_playlist
  local url

  title="$1"
  # TODO: consider not using -E since its not yet posix
  # though apparently its suppose to be made standard soon
  # and most environments support it, so it should be fine for now
  id=$(printf "%s" "$title" | sed -E 's/^0*([0-9]+) .*/\1/')

  current_playlist="$(
    printf '%s' "$STATE_CURRENT_PLAYLISTS_RESULTS" |
      jq --argjson id "$((id - 1))" '.entries[$id]'
  )"
  url="$(printf '%s' "$current_playlist" | jq '.url' -r)"

  fetch_playlist "$url"
  menu_playlist_explorer
}

_menu_playlists_explorer() {
  local other_choices
  local titles
  local choice
  local base_state

  base_state="$STATE_CURRENT"

  other_choices="\
$TXT_MENU_NEXT
$TXT_MENU_PREVIOUS
$TXT_MENU_BACK
$TXT_MENU_EXIT"

  while true; do
    titles=$(printf '%s' "$STATE_CURRENT_PLAYLISTS_RESULTS" | jq '.entries[].title' -r)
    choice="$(
      printf "%s\n%s" "$titles" "$other_choices" |
        preview "$STATE_CURRENT_PLAYLISTS_RESULTS" |
        ui_launcher_with_preview "$TXT_MENU_PLAYLISTS_EXPLORER_PROMPT"
    )"

    case "$choice" in
    "$TXT_MENU_NEXT") __menu_playlists_explorer_next ;;
    "$TXT_MENU_PREVIOUS") __menu_playlists_explorer_previous ;;
    "$TXT_MENU_BACK" | "") _state_pop "$((STATE_CURRENT - base_state + 1))" && break ;;
    "$TXT_MENU_EXIT") _util_byebye ;;
    *) __menu_playlists_explorer "$choice" ;;
    esac
  done
}

menu_playlists_explorer() {
  _menu_playlists_explorer
}

# ==============================================================================
# Menus: channels actions
# ==============================================================================
__menu_channel_actions_subscribe() {
  local _channels_data
  local channel
  local id

  if ! [ -s "$CLI_SUBSCRIPTIONS_FILE" ]; then
    if ui_confirm "$TXT_MENU_CHANNEL_ACTIONS_SUBSCRIBE_CONFIRM"; then
      fetch_yt_subs
      _channels_data="$(cat "$CLI_SUBSCRIPTIONS_FILE")"
    else
      _channels_data='{"entries":[]}'
    fi
  else
    _channels_data="$(cat "$CLI_SUBSCRIPTIONS_FILE")"
  fi

  channel="$(
    printf '%s' "$STATE_CURRENT_CHANNEL_RESULTS" |
      jq --arg title "$STATE_CURRENT_CHANNEL_TITLE" '.entries | map(select(.title == $title)) | .[0]'
  )"
  channel="$(printf '%s' "$channel" | jq '.title |= sub("^[0-9]+ "; "")')"

  id=$(printf '%s' "$channel" | jq '.id' -r)

  printf '%s' "$_channels_data" |
    jq --arg id "$id" --argjson channel "$channel" '{
    "entries":[
      .entries[]
        | select(.id != $id)
    ]}|.entries+=[$channel]' >"$CLI_SUBSCRIPTIONS_FILE"
}

# shellcheck disable=SC2120
_menu_channel_actions() {
  local url
  local actions
  local sort
  local menu_options_filter
  local extra_menu_options
  local extra_menu_options_handler

  sort="$1"
  menu_options_filter="$2"
  extra_menu_options="$3"
  extra_menu_options_handler="$4"

  actions="\
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_VIDEOS}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_VIDEOS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_FEATURED}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_FEATURED}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_SEARCH}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_SEARCH}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_PLAYLISTS}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_PLAYLISTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_SHORTS}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_SHORTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_STREAMS}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_STREAMS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_PODCASTS}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_PODCASTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_SUBSCRIBE}${THEME_RESET}  ${TXT_MENU_CHANNEL_ACTIONS_SUBSCRIBE}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_CHANNEL_ACTIONS_BACK}${THEME_RESET}  ${TXT_MENU_BACK}
 ${THEME_FZF_ICON_COLOR_ERROR}${TXT_ICON_MENU_CHANNEL_ACTIONS_EXIT}${THEME_RESET}  ${TXT_MENU_EXIT}"

  if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
    actions="$(printf '%s\n%s' "$actions" "$extra_menu_options")"
  fi

  if [ -n "$menu_options_filter" ]; then
    actions="$(printf '%s' "$actions" | grep --invert-match "$menu_options_filter")"
  fi

  if [ -n "$sort" ]; then
    actions="$(_util_menu_sort "$actions" "$sort")"
  fi

  while true; do
    if [ -n "$CMD_CHANNEL" ] && [ -n "$CMD_CHANNEL_ACTION" ]; then
      action="$CMD_CHANNEL_ACTION"
      unset -v CMD_CHANNEL
    else
      action="$(
        printf "%s" "$actions" |
          ui_launcher "$TXT_MENU_CHANNEL_ACTIONS_PROMPT" |
          sed 's/.*  //g'
      )"
    fi

    case "$action" in
    "$TXT_MENU_CHANNEL_ACTIONS_VIDEOS")
      if [ -n "$CMD_CHANNEL_ACTION" ]; then
        unset -v CMD_CHANNEL_ACTION
      fi

      url="$STATE_CURRENT_CHANNEL_URL/videos"
      fetch_playlist "$url"
      menu_playlist_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_STREAMS")
      if [ -n "$CMD_CHANNEL_ACTION" ]; then
        unset -v CMD_CHANNEL_ACTION
      fi

      url="$STATE_CURRENT_CHANNEL_URL/streams"
      fetch_playlist "$url"
      menu_playlist_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_PODCASTS")
      if [ -n "$CMD_CHANNEL_ACTION" ]; then
        unset -v CMD_CHANNEL_ACTION
      fi

      url="$STATE_CURRENT_CHANNEL_URL/podcasts"
      fetch_playlist "$url"
      menu_playlist_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_SHORTS")
      if [ -n "$CMD_CHANNEL_ACTION" ]; then
        unset -v CMD_CHANNEL_ACTION
      fi

      url="$STATE_CURRENT_CHANNEL_URL/shorts"
      fetch_playlist "$url"
      menu_playlist_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_FEATURED")
      if [ -n "$CMD_CHANNEL_ACTION" ]; then
        unset -v CMD_CHANNEL_ACTION
      fi

      url="$STATE_CURRENT_CHANNEL_URL/featured"
      fetch_playlists "$url"
      menu_playlists_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_PLAYLISTS")
      if [ -n "$CMD_CHANNEL_ACTION" ]; then
        unset -v CMD_CHANNEL_ACTION
      fi

      url="$STATE_CURRENT_CHANNEL_URL/playlists"
      fetch_playlists "$url"
      menu_playlists_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_SEARCH")
      if [ -n "$CMD_CHANNEL_ACTION" ] && [ -n "$CMD_INPUT" ]; then
        search_term="$(printf '%s' "$CMD_INPUT" | jq -Rr '@uri')"
        unset -v CMD_CHANNEL_ACTION CMD_INPUT
      else
        local search_term
        search_term="$(ui_prompt "$TXT_MENU_CHANNEL_ACTIONS_SEARCH_PROMPT" | jq -Rr '@uri')"
        [ -z "$search_term" ] && continue
      fi

      url="$STATE_CURRENT_CHANNEL_URL/search?query=$search_term"
      fetch_playlist "$url"
      menu_playlist_explorer
      ;;
    "$TXT_MENU_CHANNEL_ACTIONS_SUBSCRIBE") __menu_channel_actions_subscribe ;;
    "$TXT_MENU_BACK" | "")
      if [ "$CMD_EXIT" = "true" ]; then
        _util_byebye
      else
        _state_pop && break
      fi
      ;;
    "$TXT_MENU_EXIT") _util_byebye ;;
    *)
      if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
        if ! $extra_menu_options_handler "$action"; then
          _ui_notify_error "${TXT_MENU_INVALID_ACTION}: $action"
        fi
      fi
      ;;
    esac
  done

}

menu_channel_actions() {
  _menu_channel_actions
}

# ==============================================================================
# Menus: channels explorer
# ==============================================================================
__menu_channels_explorer_next() {
  fetch_channels_next
}

__menu_channels_explorer_previous() {
  fetch_channels_prev
}

__menu_channels_explorer() {
  local channel
  local channel_name

  channel_name="$1"

  channel="$(
    printf '%s' "$STATE_CURRENT_CHANNEL_RESULTS" |
      jq --arg name "$channel_name" '.entries|map(select(.title == $name))|.[0]'
  )"

  STATE_CURRENT_CHANNEL_URL="$(printf '%s' "$channel" | jq '.url' -r)"
  STATE_CURRENT_CHANNEL_TITLE="$(printf '%s' "$channel" | jq '.title' -r)"
  _state_push

  menu_channel_actions
}

_menu_channels_explorer() {
  local other_choices
  local titles
  local choice
  local base_state

  base_state="$STATE_CURRENT"

  other_choices="\
$TXT_MENU_NEXT
$TXT_MENU_PREVIOUS
$TXT_MENU_BACK
$TXT_MENU_EXIT"

  while true; do
    channels=$(printf '%s' "$STATE_CURRENT_CHANNEL_RESULTS" | jq '.entries[].title' -r)
    choice="$(
      printf "%s\n%s" "$channels" "$other_choices" |
        preview "$STATE_CURRENT_CHANNEL_RESULTS" |
        ui_launcher_with_preview "$TXT_MENU_CHANNELS_EXPLORER_PROMPT"
    )"
    case "$choice" in
    "$TXT_MENU_NEXT") __menu_channels_explorer_next ;;
    "$TXT_MENU_PREVIOUS") __menu_channels_explorer_previous ;;
    "$TXT_MENU_BACK" | "") _state_pop "$((STATE_CURRENT - base_state + 1))" && break ;;
    "$TXT_MENU_EXIT") _util_byebye ;;
    *) __menu_channels_explorer "$choice" ;;
    esac
  done
}

menu_channels_explorer() {
  _menu_channels_explorer
}

# ==============================================================================
# Menus: miscellaneous
# ==============================================================================
__menu_miscellaneous_explore_channels() {
  local search_term
  local url

  if [ -n "$CMD_INPUT" ]; then
    search_term="$(printf '%s' "$CMD_INPUT" | jq -Rr '@uri')"
    unset -v CMD_MAIN_ACTION CMD_MISC_ACTION CMD_INPUT
  else
    search_term=$(ui_prompt "$TXT_MENU_MISC_EXPLORE_CHANNELS_PROMPT" | jq -Rr '@uri')
    [ -z "$search_term" ] && return
  fi

  url="results?search_query=$search_term&sp=EgIQAg%253D%253D"

  fetch_channels "$url"
  menu_channels_explorer
  if [ "$CMD_EXIT" = "true" ]; then
    _util_byebye
  fi
}

__menu_miscellaneous_explore_playlists() {
  local search_term
  local url

  if [ -n "$CMD_INPUT" ]; then
    search_term="$(printf '%s' "$CMD_INPUT" | jq -Rr '@uri')"
    unset -v CMD_MAIN_ACTION CMD_MISC_ACTION CMD_INPUT
  else
    search_term=$(ui_prompt "$TXT_MENU_MISC_EXPLORE_PLAYLISTS_PROMPT" | jq -Rr '@uri')
    [ -z "$search_term" ] && return
  fi

  url="results?search_query=$search_term&sp=EgIQAw%253D%253D"

  fetch_playlists "$url"
  menu_playlists_explorer
  if [ "$CMD_EXIT" = "true" ]; then
    _util_byebye
  fi
}

__menu_miscellaneous_explore_shorts() {
  local search_term
  local url

  if [ -n "$CMD_INPUT" ]; then
    search_term="$(printf '%s' "$CMD_INPUT" | jq -Rr '@uri')"
    unset -v CMD_INPUT
  else
    search_term=$(ui_prompt "$TXT_MENU_MISC_EXPLORE_SHORTS_PROMPT" | jq -Rr '@uri')
    [ -z "$search_term" ] && return
  fi

  url="results?search_query=$search_term&sp=EgIQCQ%253D%253D"

  fetch_playlist "$url"
  menu_playlist_explorer
  if [ "$CMD_EXIT" = "true" ]; then
    _util_byebye
  fi
}

__menu_miscellaneous_explore_movies() {
  local search_term
  local url

  if [ -n "$CMD_INPUT" ]; then
    search_term="$(printf '%s' "$CMD_INPUT" | jq -Rr '@uri')"
    unset -v CMD_INPUT
  else
    search_term=$(ui_prompt "$TXT_MENU_MISC_EXPLORE_MOVIES_PROMPT" | jq -Rr '@uri')
    [ -z "$search_term" ] && return
  fi

  url="results?search_query=$search_term&sp=EgIQBA%253D%253D"

  fetch_playlist "$url"
  menu_playlist_explorer
  if [ "$CMD_EXIT" = "true" ]; then
    _util_byebye
  fi
}

__menu_miscellaneous_new_custom_command() {
  local custom_cmd_name
  local custom_cmd_url
  local custom_cmd_yt_dlp_opts
  local custom_cmds

  printf "%s" "$TXT_MENU_MISC_NEW_CUSTOM_CMD_DESC"

  custom_cmd_name=$(ui_prompt "$TXT_MENU_MISC_NEW_CUSTOM_CMD_NAME_PROMPT")
  custom_cmd_url=$(ui_prompt "$TXT_MENU_MISC_NEW_CUSTOM_CMD_URL_PROMPT")
  custom_cmd_yt_dlp_opts=$(ui_prompt "$TXT_MENU_MISC_NEW_CUSTOM_CMD_YT_DLP_OPTS_PROMPT")

  if [ -n "$custom_cmd_name" ] && [ -n "$custom_cmd_url" ]; then
    custom_cmds="[]"
    [ -s "$CLI_CUSTOM_CMDS_FILE" ] && custom_cmds=$(cat "$CLI_CUSTOM_CMDS_FILE")

    # TODO: maybe switch to a map
    printf '%s' "$custom_cmds" | jq \
      --arg name "$custom_cmd_name" \
      --arg url "$custom_cmd_url" \
      --arg opts "$custom_cmd_yt_dlp_opts" '
      .+=[
        {
          "name": $name,
          "url": $url,
          "yt-dlp-opts": $opts
        }
      ]' >"$CLI_CUSTOM_CMDS_FILE"

    # shellcheck disable=SC2086
    _fetch_playlist "$custom_cmd_url" $custom_cmd_yt_dlp_opts
    STATE_CURRENT_PLAYLIST_START=1
    STATE_CURRENT_PLAYLIST_END="$CONFIG_PER_PAGE"
    _state_push

    menu_playlist_explorer
  fi
}

__menu_miscellaneous_custom_commands() {
  local custom_cmd_name
  local url
  local yt_dlp_opts

  if [ -s "$CLI_CUSTOM_CMDS_FILE" ]; then
    while true; do
      if [ -n "$CMD_INPUT" ]; then
        custom_cmd_name="$CMD_INPUT"
        unset -v CMD_INPUT
      else
        custom_cmd_name=$(
          printf "%s\n$TXT_MENU_BACK" "$(jq -r '.[].name' "$CLI_CUSTOM_CMDS_FILE")" |
            ui_launcher "$TXT_MENU_MISC_CUSTOM_CMDS_PROMPT"
        )
      fi
      case "$custom_cmd_name" in
      "$TXT_MENU_BACK" | "")
        if [ "$CMD_EXIT" = "true" ]; then
          _util_byebye
        else
          break
        fi
        ;;
      *)
        custom_cmd=$(
          jq -r --arg name "$custom_cmd_name" '
          .
          | map(select(.name == $name))
          | .[0]' "$CLI_CUSTOM_CMDS_FILE"
        )

        url="$(printf '%s' "$custom_cmd" | jq -r ".url")"
        yt_dlp_opts="$(printf '%s' "$custom_cmd" | jq -r '.["yt-dlp-opts"]')"

        # shellcheck disable=SC2086
        _fetch_playlist "$url" $yt_dlp_opts
        STATE_CURRENT_PLAYLIST_START=1
        STATE_CURRENT_PLAYLIST_END="$CONFIG_PER_PAGE"
        _state_push

        menu_playlist_explorer
        ;;
      esac
    done
  else
    ui_notify_warning "$TXT_CUSTOM_CMDS_FILE_NOT_FOUND"
  fi
}

__menu_miscellaneous_search_history() {
  local search_term
  local url

  if [ -s "$CLI_SEARCH_HISTORY_FILE" ]; then
    while true; do
      search_term="$(
        printf "%s\n%s" "$(
          awk '{a[i++]=$0} END {for (j=i-1; j>=0; j--) print a[j]}' "$CLI_SEARCH_HISTORY_FILE"
        )" "$TXT_MENU_BACK" |
          ui_launcher "$TXT_MENU_MISC_SEARCH_HISTORY_PROMPT" | jq -Rr '@uri'
      )"
      case "$search_term" in
      "$TXT_MENU_BACK" | "")
        if [ "$CMD_EXIT" = "true" ]; then
          _util_byebye
        else
          break
        fi
        ;;
      *)
        url="results?search_query=$search_term&sp=EgIQAQ%253D%253D"

        fetch_playlist "$url"
        menu_playlist_explorer
        ;;
      esac
    done
  else
    ui_notify_warning "$TXT_SEARCH_HISTORY_FILE_NOT_FOUND"
  fi
}

__menu_miscellaneous_sync_youtube_subscriptions() {
  fetch_yt_subs
}

__menu_miscellaneous_clear_search_history() {
  if ui_confirm "$TXT_MENU_MISC_CLEAR_SEARCH_HISTORY_PROMPT"; then
    rm "$CLI_SEARCH_HISTORY_FILE"
  fi
}

__menu_miscellaneous_edit_search_history() {
  _util_file_edit "$CLI_SEARCH_HISTORY_FILE"
}

__menu_miscellaneous_edit_custom_playlists() {
  _util_file_edit "$CLI_CUSTOM_PLAYLISTS_FILE"
}

__menu_miscellaneous_edit_custom_commands() {
  _util_file_edit "$CLI_CUSTOM_CMDS_FILE"
}

__menu_miscellaneous_edit_mpv_config() {
  _util_file_edit "$XDG_CONFIG_HOME/mpv/mpv.conf"
}

__menu_miscellaneous_edit_yt_dlp_config() {
  _util_file_edit "$XDG_CONFIG_HOME/yt-dlp/config"
}

# shellcheck disable=SC2120
_menu_miscellaneous() {
  local actions
  local sort
  local menu_options_filter
  local extra_menu_options
  local extra_menu_options_handler

  sort="$1"
  menu_options_filter="$2"
  extra_menu_options="$3"
  extra_menu_options_handler="$4"

  actions="\
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EXPLORE_CHANNELS}${THEME_RESET}  ${TXT_MENU_MISC_EXPLORE_CHANNELS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EXPLORE_PLAYLISTS}${THEME_RESET}  ${TXT_MENU_MISC_EXPLORE_PLAYLISTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EXPLORE_SHORTS}${THEME_RESET}  ${TXT_MENU_MISC_EXPLORE_SHORTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EXPLORE_MOVIES}${THEME_RESET}  ${TXT_MENU_MISC_EXPLORE_MOVIES}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_SEARCH_HISTORY}${THEME_RESET}  ${TXT_MENU_MISC_SEARCH_HISTORY}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_NEW_CUSTOM_CMD}${THEME_RESET}  ${TXT_MENU_MISC_NEW_CUSTOM_CMD}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_CUSTOM_CMDS}${THEME_RESET}  ${TXT_MENU_MISC_CUSTOM_CMDS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EDIT_SEARCH_HISTORY}${THEME_RESET}  ${TXT_MENU_MISC_EDIT_SEARCH_HISTORY}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EDIT_CUSTOM_PLAYLISTS}${THEME_RESET}  ${TXT_MENU_MISC_EDIT_CUSTOM_PLAYLISTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EDIT_MPV_CONFIG}${THEME_RESET}  ${TXT_MENU_MISC_EDIT_MPV_CONFIG}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EDIT_YTDLP_CONFIG}${THEME_RESET}  ${TXT_MENU_MISC_EDIT_YTDLP_CONFIG}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_EDIT_CUSTOM_CMDS}${THEME_RESET}  ${TXT_MENU_MISC_EDIT_CUSTOM_CMDS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_SYNC_SUBS}${THEME_RESET}  ${TXT_MENU_MISC_SYNC_SUBS}
 ${THEME_FZF_ICON_COLOR_ERROR}${TXT_ICON_MENU_MISC_CLEAR_SEARCH_HISTORY}${THEME_RESET}  ${TXT_MENU_MISC_CLEAR_SEARCH_HISTORY}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MISC_BACK}${THEME_RESET}  ${TXT_MENU_BACK}
 ${THEME_FZF_ICON_COLOR_ERROR}${TXT_ICON_MENU_MISC_EXIT}${THEME_RESET}  ${TXT_MENU_EXIT}"

  if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
    actions="$(printf '%s\n%s' "$actions" "$extra_menu_options")"
  fi

  if [ -n "$menu_options_filter" ]; then
    actions="$(printf '%s' "$actions" | grep --invert-match "$menu_options_filter")"
  fi

  if [ -n "$sort" ]; then
    actions="$(_util_menu_sort "$actions" "$sort")"
  fi

  while true; do
    if [ -n "$CMD_MISC_ACTION" ]; then
      action="$CMD_MISC_ACTION"
      unset -v CMD_MISC_ACTION
    else
      action="$(printf "%s" "$actions" | ui_launcher "$TXT_MENU_MISC_PROMPT" | sed 's/.*  //g')"
    fi

    case "$action" in
    "$TXT_MENU_MISC_EXPLORE_CHANNELS") __menu_miscellaneous_explore_channels ;;
    "$TXT_MENU_MISC_EXPLORE_PLAYLISTS") __menu_miscellaneous_explore_playlists ;;
    "$TXT_MENU_MISC_EXPLORE_SHORTS") __menu_miscellaneous_explore_shorts ;;
    "$TXT_MENU_MISC_EXPLORE_MOVIES") __menu_miscellaneous_explore_movies ;;
    "$TXT_MENU_MISC_SEARCH_HISTORY") __menu_miscellaneous_search_history ;;
    "$TXT_MENU_MISC_NEW_CUSTOM_CMD") __menu_miscellaneous_new_custom_command ;;
    "$TXT_MENU_MISC_CUSTOM_CMDS") __menu_miscellaneous_custom_commands ;;
    "$TXT_MENU_MISC_EDIT_SEARCH_HISTORY") __menu_miscellaneous_edit_search_history ;;
    "$TXT_MENU_MISC_EDIT_CUSTOM_PLAYLISTS") __menu_miscellaneous_edit_custom_playlists ;;
    "$TXT_MENU_MISC_EDIT_MPV_CONFIG") __menu_miscellaneous_edit_mpv_config ;;
    "$TXT_MENU_MISC_EDIT_YTDLP_CONFIG") __menu_miscellaneous_edit_yt_dlp_config ;;
    "$TXT_MENU_MISC_EDIT_CUSTOM_CMDS") __menu_miscellaneous_edit_custom_commands ;;
    "$TXT_MENU_MISC_SYNC_SUBS") __menu_miscellaneous_sync_youtube_subscriptions ;;
    "$TXT_MENU_MISC_CLEAR_SEARCH_HISTORY") __menu_miscellaneous_clear_search_history ;;
    "$TXT_MENU_BACK" | "") break ;;
    "$TXT_MENU_EXIT") _util_byebye ;;
    *)
      if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
        if ! $extra_menu_options_handler "$action"; then
          _ui_notify_error "${TXT_MENU_INVALID_ACTION}: $action"
        fi
      fi
      ;;
    esac
  done

}

menu_miscellaneous() {
  _menu_miscellaneous
}

# ==============================================================================
# Menus: main
# ==============================================================================
__menu_main_feed() {
  local url
  url=""

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_main_subscriptions_feed() {
  local url
  url="feed/subscriptions"

  fetch_playlist "$url"
  menu_playlist_explorer
}

_search_expand_history_recall() {
  local term
  term="$1"
  case "$term" in
  ![!0-9]* | !)
    printf '%s\n' "$term"
    ;;
  !*)
    local n
    n="${term#!}"
    n="${n%%[!0-9]*}"
    local rest
    rest="${term#!"$n"}"
    rest="${rest# }"
    local recalled
    recalled=$(_get_search_history_entry "$n" 2>/dev/null)
    if [ -n "$recalled" ]; then
      term="$recalled $rest"
      term=$(printf '%s\n' "$term" | sed 's/^[ 	]*//;s/[ 	]*$//')
    fi
    printf '%s\n' "$term"
    ;;
  *)
    printf '%s\n' "$term"
    ;;
  esac
}

_search_apply_filters() {
  local term
  term="$1"
  local sp
  sp="EgIQAQ%253D%253D"
  local orig
  orig="$term"
  local filter_applied
  filter_applied=0
  local colon_part
  local cand
  local maybe_sp
  local suffix

  case "$term" in
  :*)
    colon_part="${term%%[ 	]*}"
    cand="${colon_part#:}"
    maybe_sp=$(_parse_search_filter "$cand" 2>/dev/null)
    if [ $? -eq 0 ]; then
      sp="$maybe_sp"
      if [ "$colon_part" = "$term" ]; then
        term=""
      else
        term=$(printf '%s\n' "$term" | sed 's/^[^ 	]\{1,\}[ 	]*//')
      fi
      filter_applied=1
    fi
    ;;
  esac

  if [ "$filter_applied" -eq 0 ]; then
    suffix=$(printf '%s\n' "$term" | sed -n 's/.*[ 	]\{1,\}:\([^ 	]\{1,\}\)$/\1/p')
    if [ -n "$suffix" ]; then
      maybe_sp=$(_parse_search_filter "$suffix" 2>/dev/null)
      if [ $? -eq 0 ]; then
        sp="$maybe_sp"
        term=$(printf '%s\n' "$term" | sed 's/[ 	]\{1,\}:[^ 	]\{1,\}$//')
        filter_applied=1
      fi
    fi
  fi

  if [ "$filter_applied" -eq 1 ] && [ -z "$term" ]; then
    term="$orig"
    sp="EgIQAQ%253D%253D"
  fi

  printf '%s\n%s\n' "$term" "$sp"
}

__menu_main_search() {
  local raw_search_term
  local history_search_term
  local use_history
  local header
  local hist_list
  local recent
  local choice
  local sp
  local encoded_search_term
  local url
  local filter_output

  use_history=0

  if [ -n "$CMD_INPUT" ]; then
    raw_search_term=$(printf "%s" "$CMD_INPUT" | sed 's/^[ 	]*//;s/[ 	]*$//')
    unset -v CMD_INPUT
  else
    if [ "$CONFIG_ENABLE_SEARCH_HISTORY" = "true" ] && [ -s "$CLI_SEARCH_HISTORY_FILE" ]; then
      hist_list=$(tail -n 10 "$CLI_SEARCH_HISTORY_FILE" | awk '{a[i++]=$0} END {for (j=i-1; j>=0; j--) print a[j]}')
      recent=$(printf "%s\n" "$hist_list" | head -n 5)
      header="$TXT_MENU_MAIN_RECENT_SEARCHES
${recent}

"
      choice=$(printf "%s\n%s" "$TXT_MENU_MAIN_NEW_SEARCH" "$hist_list" | ui_launcher "$TXT_MENU_MAIN_SEARCH_OR_HISTORY_PROMPT" || true)
      if [ -z "$choice" ]; then
        return
      elif [ "$choice" != "$TXT_MENU_MAIN_NEW_SEARCH" ]; then
        raw_search_term="$choice"
        use_history=1
      fi
    fi

    header="${header}${TXT_SEARCH_FILTER_HELP}"

    if [ "$use_history" -eq 0 ]; then
      raw_search_term=$(ui_prompt "$TXT_MENU_MAIN_SEARCH_PROMPT" "$header" | sed 's/^[ 	]*//;s/[ 	]*$//')
      [ -z "$raw_search_term" ] && return
    fi
  fi

  raw_search_term=$(_search_expand_history_recall "$raw_search_term")

  history_search_term="$raw_search_term"

  filter_output=$(_search_apply_filters "$raw_search_term")
  sp=$(printf '%s\n' "$filter_output" | sed -n '$p')
  raw_search_term=$(printf '%s\n' "$filter_output" | sed '$d')

  if [ "$CONFIG_ENABLE_SEARCH_HISTORY" = "true" ]; then
    if [ -f "$CLI_SEARCH_HISTORY_FILE" ]; then
      grep -v -x -F -e "$history_search_term" "$CLI_SEARCH_HISTORY_FILE" >"${CLI_SEARCH_HISTORY_FILE}.tmp" 2>/dev/null || true
      tail -n 99 "${CLI_SEARCH_HISTORY_FILE}.tmp" >"$CLI_SEARCH_HISTORY_FILE" 2>/dev/null || true
      rm -f "${CLI_SEARCH_HISTORY_FILE}.tmp"
    fi
    printf "%s\n" "$history_search_term" >>"$CLI_SEARCH_HISTORY_FILE"
  fi

  encoded_search_term=$(printf '%s' "$raw_search_term" | jq -Rr '@uri')
  url="results?search_query=$encoded_search_term&sp=$sp"

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_main_watch_later() {
  local url
  url="playlist?list=WL"

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_main_playlists() {
  local url
  url="feed/playlists"

  fetch_playlists "$url"
  menu_playlists_explorer
}

__menu_main_liked_videos() {
  local url
  url="playlist?list=LL"

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_main_watch_history() {
  local url
  url="feed/history"

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_main_clips() {
  local url
  url="feed/clips"

  fetch_playlist "$url"
  menu_playlist_explorer
}

# NOTE: youtube currently no longer has a trending page
# but other sites may have one so...
__menu_main_trending() {
  local url
  url="trending"

  fetch_playlist "$url"
  menu_playlist_explorer
}

__menu_main_channels_explorer() {
  local channel
  local channels
  local url

  if [ -s "$CLI_SUBSCRIPTIONS_FILE" ]; then
    channels="$(cat "$CLI_SUBSCRIPTIONS_FILE")"
    while true; do
      if [ -n "$CMD_CHANNEL" ]; then
        channel="$CMD_CHANNEL"
        if [ -z "$CMD_CHANNEL_ACTION" ]; then
          unset -v CMD_CHANNEL
        fi
      else
        channel=$(
          printf "%s\n%s\n" "$(printf '%s' "$channels" | jq -r '.entries[].title')" "$TXT_MENU_BACK" |
            preview "$channels" |
            ui_launcher_with_preview "$TXT_MENU_MAIN_CHANNELS_PROMPT"
        )
      fi

      case "$channel" in
      "$TXT_MENU_BACK" | "") break ;;
      *)
        url=$(
          jq -r --arg channel "$channel" '
          .entries
          | map(select(.title == $channel ))
          | .[0].url' "$CLI_SUBSCRIPTIONS_FILE"
        )

        STATE_CURRENT_CHANNEL_URL="$url"
        STATE_CURRENT_CHANNEL_TITLE="$channel"

        _state_push
        menu_channel_actions
        ;;
      esac
    done
  else
    ui_notify_warning "$TXT_SUBS_FILE_NOT_FOUND"
  fi
}

__menu_main_custom_playlists() {
  local playlist_title
  local url

  if [ -s "$CLI_CUSTOM_PLAYLISTS_FILE" ]; then
    while true; do
      if [ -n "$CMD_INPUT" ]; then
        playlist_title="$CMD_INPUT"
        unset -v CMD_INPUT
      else
        playlist_title=$(
          printf "%s\n%s" "$(jq -r '.|reverse|.[].name' "$CLI_CUSTOM_PLAYLISTS_FILE")" "$TXT_MENU_BACK" |
            ui_launcher "$TXT_MENU_MAIN_CUSTOM_PLAYLISTS_PROMPT"
        )
      fi

      case "$playlist_title" in
      "$TXT_MENU_BACK" | "") break ;;
      *)
        url=$(
          jq -r --arg title "$playlist_title" '
          .
          | map(select(.name == $title))
          | .[0].url' "$CLI_CUSTOM_PLAYLISTS_FILE"
        )

        _fetch_playlist "$url"
        STATE_CURRENT_PLAYLIST_START=1
        STATE_CURRENT_PLAYLIST_END="$CONFIG_PER_PAGE"
        _state_push

        menu_playlist_explorer
        ;;
      esac
      if [ "$CMD_EXIT" = true ]; then
        _util_byebye
      fi
    done
  else
    ui_notify_warning "$TXT_CUSTOM_PLAYLISTS_FILE_NOT_FOUND"
  fi
}

__menu_main_saved_videos() {
  local video
  local saved_videos
  local selection

  if [ -s "$CLI_SAVED_VIDEOS_FILE" ]; then
    if [ -n "$CMD_INPUT" ]; then
      saved_videos="$(cat "$CLI_SAVED_VIDEOS_FILE")"
    else
      saved_videos="$(
        cat "$CLI_SAVED_VIDEOS_FILE" |
          jq '
          .entries=(
            .entries
            |reverse
            | to_entries
            | map(.value.title="\(.key+1|tostring| if (.|length) < 2 then "0" + . else . end) " + .value.title)
            |map(.value)
          )
          '
      )"
    fi

    while true; do
      if [ -n "$CMD_INPUT" ]; then
        selection="$CMD_INPUT"
        unset -v CMD_INPUT
      else
        selection=$(
          printf "%s\n%s" "$(printf '%s' "$saved_videos" | jq -r '.entries[].title')" "$TXT_MENU_BACK" |
            preview "$saved_videos" |
            ui_launcher_with_preview "$TXT_MENU_MAIN_SAVED_PROMPT"
        )
      fi

      case "$selection" in
      "$TXT_MENU_BACK" | "") break ;;
      *)
        video=$(
          printf '%s' "$saved_videos" | jq -r --arg title "$selection" '
          .entries
          | map(select(.title == $title ))[0]
          '
        )

        STATE_CURRENT_VIDEO="$video"
        STATE_CURRENT_VIDEO_URL="$(printf '%s' "$video" | jq -r ".url")"
        STATE_CURRENT_VIDEO_TITLE="$(printf '%s' "$selection" | sed -E 's/^[0-9]+ //g')"

        _state_push
        menu_playlist_actions
        ;;
      esac
    done
    if [ -n "$CMD_EXIT" ]; then
      _util_byebye
    fi
  else
    ui_notify_warning "$TXT_SAVED_VIDEOS_FILE_NOT_FOUND"
  fi
}

__menu_main_recent() {
  local video
  local saved_videos
  local selection

  if [ -s "$CLI_RECENT_FILE" ]; then
    saved_videos="$(
      cat "$CLI_RECENT_FILE" |
        jq '
          .entries=(
            .entries
            |reverse
            | to_entries
            | map(.value.title="\(.key+1|tostring| if (.|length) < 2 then "0" + . else . end) " + .value.title)
            |map(.value)
          )
          '
    )"

    while true; do
      selection=$(
        printf "%s\n%s" "$(printf '%s' "$saved_videos" | jq -r '.entries[].title')" "$TXT_MENU_BACK" |
          preview "$saved_videos" |
          ui_launcher_with_preview "$TXT_MENU_MAIN_SAVED_PROMPT"
      )

      case "$selection" in
      "$TXT_MENU_BACK" | "") break ;;
      *)
        video=$(
          printf '%s' "$saved_videos" | jq -r --arg title "$selection" '
          .entries
          | map(select(.title == $title ))[0]
          '
        )

        STATE_CURRENT_VIDEO="$video"
        STATE_CURRENT_VIDEO_URL="$(printf '%s' "$video" | jq -r ".url")"
        STATE_CURRENT_VIDEO_TITLE="$(printf '%s' "$selection" | sed -E 's/^[0-9]+ //g')"

        _state_push
        menu_playlist_actions
        ;;
      esac
    done
  else
    ui_notify_warning "$TXT_RECENT_FILE_NOT_FOUND"
  fi
}

__menu_main_edit_config() {
  _util_file_edit "$CLI_CONFIG_FILE"
  _load_config
}

# shellcheck disable=SC2120
_menu_main() {
  local actions
  local sort
  local menu_options_filter
  local extra_menu_options
  local extra_menu_options_handler

  sort="$1"
  menu_options_filter="$2"
  extra_menu_options="$3"
  extra_menu_options_handler="$4"

  actions="\
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_FEED}${THEME_RESET}  ${TXT_MENU_MAIN_FEED}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_SEARCH}${THEME_RESET}  ${TXT_MENU_MAIN_SEARCH}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_SUBS_FEED}${THEME_RESET}  ${TXT_MENU_MAIN_SUBS_FEED}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_WATCH_LATER}${THEME_RESET}  ${TXT_MENU_MAIN_WATCH_LATER}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_PLAYLISTS}${THEME_RESET}  ${TXT_MENU_MAIN_PLAYLISTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_CHANNELS}${THEME_RESET}  ${TXT_MENU_MAIN_CHANNELS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_CUSTOM_PLAYLISTS}${THEME_RESET}  ${TXT_MENU_MAIN_CUSTOM_PLAYLISTS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_SAVED}${THEME_RESET}  ${TXT_MENU_MAIN_SAVED}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_RECENT}${THEME_RESET}  ${TXT_MENU_MAIN_RECENT}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_LIKED}${THEME_RESET}  ${TXT_MENU_MAIN_LIKED}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_HISTORY}${THEME_RESET}  ${TXT_MENU_MAIN_HISTORY}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_CLIPS}${THEME_RESET}  ${TXT_MENU_MAIN_CLIPS}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_MISC}${THEME_RESET}  ${TXT_MENU_MAIN_MISC}
 ${THEME_FZF_ICON_COLOR_PRIMARY}${TXT_ICON_MENU_MAIN_EDIT_CONFIG}${THEME_RESET}  ${TXT_MENU_MAIN_EDIT_CONFIG}
 ${THEME_FZF_ICON_COLOR_ERROR}${TXT_ICON_MENU_MAIN_EXIT}${THEME_RESET}  ${TXT_MENU_EXIT}"

  if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
    actions="$(printf '%s\n%s' "$actions" "$extra_menu_options")"
  fi

  if [ -n "$menu_options_filter" ]; then
    actions="$(printf '%s' "$actions" | grep --invert-match "$menu_options_filter")"
  fi

  if [ -n "$sort" ]; then
    actions="$(_util_menu_sort "$actions" "$sort")"
  fi

  while true; do
    if [ -n "$CMD_MAIN_ACTION" ]; then
      action="$CMD_MAIN_ACTION"
      unset -v CMD_MAIN_ACTION
    else
      action="$(printf "%s" "$actions" | ui_launcher "$TXT_MENU_MAIN_PROMPT_ACTION" | sed 's/.*  //g')"
    fi

    case "$action" in
    "$TXT_MENU_MAIN_FEED") __menu_main_feed ;;
    "$TXT_MENU_MAIN_SEARCH") __menu_main_search ;;
    "$TXT_MENU_MAIN_SUBS_FEED") __menu_main_subscriptions_feed ;;
    "$TXT_MENU_MAIN_WATCH_LATER") __menu_main_watch_later ;;
    "$TXT_MENU_MAIN_PLAYLISTS") __menu_main_playlists ;;
    "$TXT_MENU_MAIN_CHANNELS") __menu_main_channels_explorer ;;
    "$TXT_MENU_MAIN_CUSTOM_PLAYLISTS") __menu_main_custom_playlists ;;
    "$TXT_MENU_MAIN_SAVED") __menu_main_saved_videos ;;
    "$TXT_MENU_MAIN_RECENT") __menu_main_recent ;;
    "$TXT_MENU_MAIN_LIKED") __menu_main_liked_videos ;;
    "$TXT_MENU_MAIN_HISTORY") __menu_main_watch_history ;;
    "$TXT_MENU_MAIN_CLIPS") __menu_main_clips ;;
    "$TXT_MENU_MAIN_MISC") menu_miscellaneous ;;
    "$TXT_MENU_MAIN_EDIT_CONFIG") __menu_main_edit_config ;;
    "$TXT_MENU_EXIT" | "") _util_byebye ;;
    *)
      if [ -n "$extra_menu_options" ] && [ -n "$extra_menu_options_handler" ]; then
        if ! $extra_menu_options_handler "$action"; then
          _ui_notify_error "${TXT_MENU_INVALID_ACTION}: $action"
        fi
      fi
      ;;
    esac
  done
}

menu_main() {
  _state_init
  _menu_main
}

# ==============================================================================
# APP: utils
# ==============================================================================
_app_update_script() {
  local update

  if ! [ -w "$CLI_PATH" ]; then
    ui_confirm "$TXT_UPDATE_ROOT_WARNING" || exit 1
    if _dep_ch sudo; then
      exec sudo -s "$CLI_PATH" "-u"
    else
      ui_notify_critical "$TXT_UPDATE_SUDO_MISSING"
    fi
  fi

  update="$1"
  [ -z "$update" ] && ui_notify_critical "$TXT_UPDATE_FETCH_FAILED"

  if printf "%s\n" "$update" >"$CLI_PATH"; then
    rm "$CLI_FZF_PREVIEW_SCRIPT" >/dev/null 2>&1
    if ui_confirm "$TXT_UPDATE_SCRIPT_REEXECUTE"; then
      # shellcheck disable=SC2086
      exec "$CLI_PATH" $CLI_ARGS
    else
      exit 0
    fi
  else
    ui_notify_critical "$TXT_UPDATE_FAILED"
  fi
}

_app_update_check() {
  local update
  local update_diff
  local latest_version

  latest_version=$(curl -s "$CLI_VERSION_URL")
  [ -z "$latest_version" ] && return 1

  if [ "$latest_version" != "$CLI_VERSION" ]; then
    update="$(curl -sL "${CLI_RELEASES_BASE}/v${latest_version}/$CLI_NAME")"
    [ -z "$update" ] && return 1

    if _dep_ch diff; then
      update_diff="$(printf "%s\n" "$update" | diff -u "$CLI_PATH" -)"
      ui_confirm "$TXT_UPDATE_FOUND" && printf '%s\n' "$update_diff" | ui_pager
    else
      _ui_notify_warning "$TXT_DEP_MISSING_DIFF"
    fi

    ui_confirm "$TXT_UPDATE_CONFIRM" && _app_update_script "$update"
  else
    return 1
  fi
}

_app_auto_update() {
  local timestamp_file
  local interval
  local last_check_time

  if [ "$CONFIG_CHECK_FOR_UPDATES" = "true" ]; then
    timestamp_file="$CLI_CACHE_DIR/.last_update_check"

    interval=$((12 * 60 * 60))

    last_check_time=$(cat "$timestamp_file" 2>/dev/null || printf '0')

    if [ "$((CLI_START_TIME - last_check_time))" -ge "$interval" ]; then
      _app_cache_clean_up &

      _app_update_check
      printf '%s' "$CLI_START_TIME" >"$timestamp_file"
    fi
  fi
}

_app_edit_config() {
  _util_file_edit "$CLI_CONFIG_FILE"
  _util_byebye
}

_app_generate_desktop_entry() {
  local entry

  if ui_confirm "$TXT_DESKTOP_ENTRY_PROMPT" "$TXT_LAUNCHER_LABEL_ROFI" "$TXT_LAUNCHER_LABEL_ROFI" "$TXT_LAUNCHER_LABEL_FZF"; then
    entry="\
[Desktop Entry]
Name=$CLI_NAME
Type=Application
Version=$CLI_VERSION
Path=$HOME
Comment=$TXT_DESKTOP_ENTRY_COMMENT
Terminal=false
Exec=$CLI_PATH --launcher rofi --no-disown-player
Categories=Entertainment"
  else
    entry="\
[Desktop Entry]
Name=$CLI_NAME
Type=Application
Version=$CLI_VERSION
Path=$HOME
Comment=$TXT_DESKTOP_ENTRY_COMMENT
Terminal=true
Exec=$CLI_PATH --launcher fzf
Categories=Entertainment"
  fi

  printf "%s\n" "$entry"
  exit 0
}

_app_core_dep_ch() {
  ! _dep_ch "yt-dlp" >/dev/null 2>&1 && ui_notify_critical "$TXT_DEP_MISSING_YTDLP"
  ! _dep_ch "jq" && ui_notify_critical "$TXT_DEP_MISSING_JQ"
  ! _dep_ch "fzf" && ui_notify_critical "$TXT_DEP_MISSING_FZF"
}

_app_cache_clean_up() {
  local retention_days
  retention_days="${CONFIG_CACHE_RETENTION_DAYS:-3}"

  if [ -d "$CLI_PREVIEW_DIR" ]; then
    find "$CLI_PREVIEW_DIR" -type f -mtime "+$retention_days" -exec rm {} + 2>/dev/null
  fi

  if [ -d "$CLI_AUTO_GEN_PLAYLISTS" ]; then
    find "$CLI_AUTO_GEN_PLAYLISTS" -type f -mtime "+$retention_days" -exec rm {} + 2>/dev/null
  fi

  if [ -d "$CLI_LOG_DIR" ]; then
    find "$CLI_LOG_DIR" -type f -mtime "+$retention_days" -exec rm {} + 2>/dev/null
  fi
}

_app_runtime_clean_up() {
  rm -r "$CLI_CURRENT_STATE_DIR" 2>/dev/null
  job_pids=$(jobs -p)

  # shellcheck disable=SC2086
  [ -n "$job_pids" ] && kill $job_pids 2>/dev/null
}

# ==============================================================================
# APP: usage
# ==============================================================================
_app_usage() {
  printf "%s\n" "$TXT_APP_USAGE"
  exit "${1:-0}"
}

_app_usage_channels() {
  printf "%s\n" "$TXT_APP_USAGE_CHANNELS"
  exit "${1:-0}"
}

_app_usage_completions() {
  printf "%s\n" "$TXT_APP_USAGE_COMPLETIONS"
  exit "${1:-0}"
}

# ==============================================================================
# APP: completions
# ==============================================================================
_app_completions_fish() {
  local ext_dir
  ext_dir="$HOME/.config/$CLI_NAME/extensions"

  cat <<EOF
# Fish completions for $CLI_NAME

# ==========================================================================
# Global options 
# ==========================================================================
complete -c $CLI_NAME -n "__fish_use_subcommand" -s h -l help -d "Show help and exit"
complete -c $CLI_NAME -n "__fish_use_subcommand" -s v -l version -d "Show version and exit"
complete -c $CLI_NAME -n "__fish_use_subcommand" -s e -l edit-config -d "Edit config file"
complete -c $CLI_NAME -n "__fish_use_subcommand" -s E -l generate-desktop-entry -d "Print desktop entry info"
complete -c $CLI_NAME -n "__fish_use_subcommand" -s U -l update -d "Update the script"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l config-write -d "Write current config to file"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l launcher -s l -d "Preferred launcher" --require-parameter --no-files -a "fzf rofi gum"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l preview -d "Enable preview window"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l no-preview -d "Disable preview window"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l preview-images -d "Enable image previews"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l no-preview-images -d "Disable image previews"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l player -s p -d "Media player" --require-parameter --no-files -a "mpv vlc tplay"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l mpv-args -d "Pass custom mpv args at runtime"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l vlc-args -d "Pass custom vlc args at runtime"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l tplay-args -d "Pass custom tplay args at runtime"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l disown-player -d "Disown player"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l no-disown-player -d "Do not disown player"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l rofi-theme-main -d "Rofi main theme path" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -l rofi-theme-preview -d "Rofi preview theme path" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -l rofi-theme-prompt -d "Rofi prompt theme path" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -l rofi-theme-confirm -d "Rofi confirm theme path" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -l rofi-theme-pager -d "Rofi pager theme path" --require-parameter

complete -c $CLI_NAME -n "__fish_use_subcommand" -s x -l extension -d "Load extension" --require-parameter --no-files -a '(find $ext_dir -follow -maxdepth 3 -type f 2>/dev/null | string replace "$ext_dir/" "" | sort)'
complete -c $CLI_NAME -n "__fish_use_subcommand" -o xargs -l extension-arguments -d "The arguments to parse to cmd extension" --require-parameter --no-files

# ==========================================================================
# Menu shortcuts
# ==========================================================================
complete -c $CLI_NAME -n "__fish_use_subcommand" -o ce -l cmd-exit -d "Exit after shortcut menu commandline options"
complete -c $CLI_NAME -n "__fish_use_subcommand" -o ps -l playlist-skip -d "Skip item selection and auto‑pick first entry"
complete -c $CLI_NAME -n "__fish_use_subcommand" -o me -l media-exit -d "Exit after performing a media action"

complete -c $CLI_NAME -n "__fish_use_subcommand" -l play -d "Watch selected video"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l play-all -d "Play whole playlist"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l listen -d "Listen to selected video"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l listen-all -d "Listen to whole playlist"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l download -d "Download selected video"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l download-all -d "Download whole playlist (video)"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l download-audio -d "Download audio only"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l download-audio-all -d "Download whole playlist as audio"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l save -d "Save video to saved list"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l save-playlist -d "Save playlist to custom list"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l shell -d "Open subshell with context"

complete -c $CLI_NAME -n "__fish_use_subcommand" -s s -l search -d "Search for videos" --require-parameter --no-files -a '(cat "'$CLI_SEARCH_HISTORY_FILE'" 2>/dev/null)'
complete -c $CLI_NAME -n "__fish_use_subcommand" -o sp -l search-playlist -d "Search for playlists" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -o sc -l search-channel -d "Search for channels" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -o ss -l search-short -d "Search for shorts" --require-parameter
complete -c $CLI_NAME -n "__fish_use_subcommand" -o sm -l search-movie -d "Search for movies" --require-parameter

complete -c $CLI_NAME -n "__fish_use_subcommand" -o sv -l saved-video -d "Open a specific saved video" --require-parameter --no-files -a '(jq -r '.entries[].title' "'$CLI_SAVED_VIDEOS_FILE'" 2>/dev/null)'
complete -c $CLI_NAME -n "__fish_use_subcommand" -o cp -l custom-playlist -d "Open a saved custom playlist" --require-parameter --no-files -a '(jq -r '.[]?.name' "'$CLI_CUSTOM_PLAYLISTS_FILE'" 2>/dev/null)'
complete -c $CLI_NAME -n "__fish_use_subcommand" -o cc -l custom-cmd -d "Execute a specific custom command" --require-parameter --no-files -a '(jq -r '.[]?.name' "'$CLI_CUSTOM_CMDS_FILE'" 2>/dev/null)'

complete -c $CLI_NAME -n "__fish_use_subcommand" -l feed -d "Open your personalised feed"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l subscriptions-feed -d "Show latest videos from subscriptions"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l watch-later -d "Open Watch Later playlist"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l playlists -d "Show saved YouTube playlists"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l custom-playlists -d "Browse custom playlists"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l saved -d "Open saved videos"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l recent -d "Show recently watched videos"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l liked -d "Open Liked Videos playlist"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l watch-history -d "Show watch history"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l clips -d "Browse your clips"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l new-custom-cmd -d "Create a new custom command"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l custom-cmds -d "Execute an existing custom command"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l search-history -d "Show search history"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l edit-search-history -d "Edit search history file"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l edit-custom-playlists -d "Edit custom playlists file"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l edit-mpv-config -d "Edit mpv configuration"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l edit-yt-dlp-config -d "Edit yt‑dlp configuration"
complete -c $CLI_NAME -n "__fish_use_subcommand" -l edit-custom-cmds -d "Edit custom commands file"

# ==========================================================================
# Channels subcommand
# ==========================================================================
complete -c $CLI_NAME -f -n "__fish_use_subcommand" -a channels -d "Browse or search within a specific channel"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o n -l name -d "Channel name" --require-parameter --no-files -a '(jq -r '.entries[].title' "'$CLI_SUBSCRIPTIONS_FILE'" 2>/dev/null)'
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o v -l videos -d "List channel videos"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o f -l featured -d "Show featured playlists"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o s -l search -d "Search within channel" --require-parameter
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o p -l playlists -d "List channel playlists"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o sh -l shorts -d "Show channel shorts"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o st -l streams -d "Show live streams & past broadcasts"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from channels" -o po -l podcasts -d "Show channel podcasts"

# ==========================================================================
# Completions subcommand
# ==========================================================================
complete -c $CLI_NAME -f -n "__fish_use_subcommand" -a completions -d "Generate shell completions"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from completions" -s f -l fish -d "Print fish completions"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from completions" -s b -l bash -d "Print bash completions"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from completions" -s z -l zsh -d "Print zsh completions"
complete -c $CLI_NAME -n "__fish_seen_subcommand_from completions" -s h -l help -d "Show help for completions"
EOF

  exit 0
}

# ==============================================================================
# APP:cmdline parser
# ==============================================================================
_app_cmd_line_parser() {
  local shift_count
  while [ $# -gt 0 ]; do
    shift_count=1
    case "$1" in
    -h | --help) _app_usage ;;
    -v | --version) printf '%s v%s MIT Copyright © 2024 %s\n' "$CLI_NAME" "$CLI_VERSION" "$CLI_AUTHOR" && exit 0 ;;
    -U | --update) _app_update_check || printf '%s\n' "$TXT_UPDATE_NOT_FOUND" && exit 1 ;;
    -e | --edit-config) _app_edit_config ;;
    -E | --generate-desktop-entry) _app_generate_desktop_entry ;;
    -ce | --cmd-exit) CMD_EXIT=true ;;
    -ps | --playlist-skip) CMD_PLAYLIST_RESULTS_SKIP=true ;;
    -me | --media-exit) CMD_MEDIA_EXIT=true ;;
    --config-write) _print_config >"$CLI_CONFIG_FILE" ;;
    --preview) CONFIG_ENABLE_PREVIEW=true ;;
    --no-preview) CONFIG_ENABLE_PREVIEW=false ;;
    --preview-images) CONFIG_ENABLE_PREVIEW_IMAGES=true ;;
    --no-preview-images) CONFIG_ENABLE_PREVIEW_IMAGES=false ;;
    -x | --extension)
      [ -n "$2" ] || _app_usage 1
      if [ "${2#/}" != "$2" ]; then
        . "$2"
      else
        . "$CLI_EXTENSIONS_DIR/$2"
      fi

      shift_count=2
      ;;
    -xargs | --extension-arguments)
      shift
      readonly CLI_EXTENSION_CMDLINE_ARGS="$*"
      return
      ;;
    -l | --launcher)
      [ -n "$2" ] || _app_usage 1
      CONFIG_LAUNCHER="$2"
      shift_count=2
      ;;
    -p | --player)
      [ -n "$2" ] || _app_usage 1
      CONFIG_PLAYER="$2"
      shift_count=2
      ;;
    --mpv-args)
      [ -n "$2" ] || _app_usage 1
      CONFIG_MPV_ARGS="$2"
      shift_count=2
      ;;
    --vlc-args)
      [ -n "$2" ] || _app_usage 1
      CONFIG_VLC_ARGS="$2"
      shift_count=2
      ;;
    --tplay-args)
      [ -n "$2" ] || _app_usage 1
      CONFIG_TPLAY_ARGS="$2"
      shift_count=2
      ;;
    --disown-player) CONFIG_DISOWN_PLAYER=true ;;
    --no-disown-player) CONFIG_DISOWN_PLAYER=false ;;
    --rofi-theme-main)
      [ -n "$2" ] || _app_usage 1
      CONFIG_ROFI_THEME_MAIN="$2"
      shift_count=2
      ;;
    --rofi-theme-preview)
      [ -n "$2" ] || _app_usage 1
      CONFIG_ROFI_THEME_PREVIEW="$2"
      shift_count=2
      ;;
    --rofi-theme-prompt)
      [ -n "$2" ] || _app_usage 1
      CONFIG_ROFI_THEME_PROMPT="$2"
      shift_count=2
      ;;
    --rofi-theme-confirm)
      [ -n "$2" ] || _app_usage 1
      CONFIG_ROFI_THEME_CONFIRM="$2"
      shift_count=2
      ;;
    --rofi-theme-pager)
      [ -n "$2" ] || _app_usage 1
      CONFIG_ROFI_THEME_PAGER="$2"
      shift_count=2
      ;;
    --play) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_WATCH" ;;
    --play-all) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_WATCH_ALL" && CMD_PLAYLIST_RESULTS_SKIP=true ;;
    --listen) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_LISTEN" ;;
    --listen-all) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_LISTEN_ALL" && CMD_PLAYLIST_RESULTS_SKIP=true ;;
    --download) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD" ;;
    --download-all) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL" && CMD_PLAYLIST_RESULTS_SKIP=true ;;
    --download-audio) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_AUDIO" ;;
    --download-audio-all) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_DOWNLOAD_ALL_AUDIO" && CMD_PLAYLIST_RESULTS_SKIP=true ;;
    --save) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_SAVE" ;;
    --save-playlist) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_SAVE_PLAYLIST" && CMD_PLAYLIST_RESULTS_SKIP=true ;;
    --shell) CMD_MEDIA_ACTION="$TXT_MENU_PLAYLIST_ACTIONS_SHELL" ;;
    --feed) CMD_MAIN_ACTION="$TXT_MENU_MAIN_FEED" ;;
    --subscriptions-feed) CMD_MAIN_ACTION="$TXT_MENU_MAIN_SUBS_FEED" ;;
    --watch-later) CMD_MAIN_ACTION="$TXT_MENU_MAIN_WATCH_LATER" ;;
    --playlists) CMD_MAIN_ACTION="$TXT_MENU_MAIN_PLAYLISTS" ;;
    --custom-playlists) CMD_MAIN_ACTION="$TXT_MENU_MAIN_CUSTOM_PLAYLISTS" ;;
    --saved) CMD_MAIN_ACTION="$TXT_MENU_MAIN_SAVED" ;;
    --recent) CMD_MAIN_ACTION="$TXT_MENU_MAIN_RECENT" ;;
    --liked) CMD_MAIN_ACTION="$TXT_MENU_MAIN_LIKED" ;;
    --watch-history) CMD_MAIN_ACTION="$TXT_MENU_MAIN_HISTORY" ;;
    --clips) CMD_MAIN_ACTION="$TXT_MENU_MAIN_CLIPS" ;;
    --new-custom-cmd) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_NEW_CUSTOM_CMD" ;;
    --custom-cmds) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_CUSTOM_CMDS" ;;
    --search-history) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_SEARCH_HISTORY" ;;
    --edit-search-history) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_EDIT_SEARCH_HISTORY" ;;
    --edit-custom-playlists) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_EDIT_CUSTOM_PLAYLISTS" ;;
    --edit-mpv-config) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_EDIT_MPV_CONFIG" ;;
    --edit-yt-dlp-config) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_EDIT_YTDLP_CONFIG" ;;
    --edit-custom-cmds) CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC" && CMD_MISC_ACTION="$TXT_MENU_MISC_EDIT_CUSTOM_CMDS" ;;
    -cp | --custom-playlist)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_CUSTOM_PLAYLISTS"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MAIN_CUSTOM_PLAYLISTS_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -sv | --saved-video)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_SAVED"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MAIN_SAVED_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -cc | --custom-cmd)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC"
      CMD_MISC_ACTION="$TXT_MENU_MISC_CUSTOM_CMDS"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MISC_CUSTOM_CMDS_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -s | --search)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_SEARCH"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MAIN_SEARCH_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -sp | --search-playlist)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC"
      CMD_MISC_ACTION="$TXT_MENU_MISC_EXPLORE_PLAYLISTS"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MISC_EXPLORE_PLAYLISTS_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -sc | --search-channel)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC"
      CMD_MISC_ACTION="$TXT_MENU_MISC_EXPLORE_CHANNELS"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MISC_EXPLORE_CHANNELS_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -ss | --search-short)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC"
      CMD_MISC_ACTION="$TXT_MENU_MISC_EXPLORE_SHORTS"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MISC_EXPLORE_SHORTS_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    -sm | --search-movie)
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_MISC"
      CMD_MISC_ACTION="$TXT_MENU_MISC_EXPLORE_MOVIES"
      if [ -z "$2" ]; then
        CMD_INPUT="$(ui_prompt "$TXT_MENU_MISC_EXPLORE_MOVIES_PROMPT")"
        [ -n "$CMD_INPUT" ] || _app_usage 1
      else
        CMD_INPUT="$2"
        shift_count=2
      fi
      ;;
    c | channels)
      shift
      CMD_MAIN_ACTION="$TXT_MENU_MAIN_CHANNELS"
      while [ $# -gt 0 ]; do
        shift_count=1
        case "$1" in
        -n | --name)
          if [ -z "$2" ]; then
            CMD_CHANNEL="$(ui_prompt "$TXT_MENU_MAIN_CHANNELS_PROMPT")"
            [ -n "$CMD_CHANNEL" ] || _app_usage_channels 1
          else
            CMD_CHANNEL="$2"
            shift_count=2
          fi
          ;;
        -s | --search)
          CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_SEARCH"
          if [ -z "$2" ]; then
            CMD_INPUT="$(ui_prompt "$TXT_MENU_CHANNEL_ACTIONS_SEARCH_PROMPT")"
            [ -n "$CMD_INPUT" ] || _app_usage_channels 1
          else
            CMD_INPUT="$2"
            shift_count=2
          fi
          ;;
        -v | --videos) CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_VIDEOS" ;;
        -f | --featured) CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_FEATURED" ;;
        -p | --playlists) CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_PLAYLISTS" ;;
        -sh | --shorts) CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_SHORTS" ;;
        -st | --streams) CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_STREAMS" ;;
        -po | --podcasts) CMD_CHANNEL_ACTION="$TXT_MENU_CHANNEL_ACTIONS_PODCASTS" ;;
        -h | --help) _app_usage_channels ;;
        *) _app_usage_channels 1 ;;
        esac
        shift $shift_count
      done
      return
      ;;
    completions)
      [ -n "$2" ] || _app_usage 1
      case "$2" in
      -f | --fish) _app_completions_fish ;;
      -b | --bash) printf '%s\n' "Contribute to $CLI_NAME by writing bash completions" && exit 1 ;;
      -z | --zsh) printf '%s\n' "Contribute to $CLI_NAME by writing zsh completions" && exit 1 ;;
      -h | --help) _app_usage_completions ;;
      *) _app_usage_completions 1 ;;
      esac
      shift_count=2
      ;;
    *) _app_usage 1 ;;
    esac
    shift $shift_count
  done
}

# ==============================================================================
# App
# ==============================================================================
main() {
  trap _app_runtime_clean_up EXIT INT TERM
  _load_config
  _app_cmd_line_parser "$@"
  _app_auto_update
  _app_core_dep_ch
  menu_main
}

main "$@"

# ==============================================================================
# MIT License
# ==============================================================================
# Copyright (c) 2024-2026 Benexl
# ==============================================================================
# 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.
# ==============================================================================
# くそ、長いな
# ==============================================================================
