#!/bin/bash

# omarchy:summary=Launch the Quickshell Omarchy menu or jump straight to a submenu/action
# omarchy:args=[menu-or-action|--json|--provider <provider> [menu-id]]
# omarchy:examples=omarchy menu | omarchy menu setup | omarchy menu style.theme | omarchy menu --json | omarchy menu --provider fonts style.font

set -o pipefail

OMARCHY_PATH=${OMARCHY_PATH:-$HOME/.local/share/omarchy}
DEFAULT_MENU="$OMARCHY_PATH/default/omarchy/omarchy-menu.jsonc"
USER_MENU="$HOME/.config/omarchy/extensions/omarchy-menu.jsonc"

# Menu hooks users may edit
# Menu rows live in default/omarchy/omarchy-menu.jsonc.
# Keep Bash here only for multi-step actions or truly runtime-generated lists.

# Action helpers available to omarchy-menu.jsonc

terminal() {
  xdg-terminal-exec --app-id=org.omarchy.terminal "$@"
}

present_terminal() {
  omarchy-launch-floating-terminal-with-presentation "$*"
}

open_in_editor() {
  omarchy-notification-send -u low "Editing config file" "$1"
  omarchy-launch-editor "$1"
}

hypr_config_file() {
  local name="$1"
  printf '%s/.config/hypr/%s.lua' "$HOME" "$name"
}

install() {
  present_terminal "echo 'Installing $1...'; omarchy-pkg-add $2"
}

install_and_launch() {
  present_terminal "echo 'Installing $1...'; omarchy-pkg-add $2 && setsid gtk-launch $3"
}

install_font() {
  present_terminal "echo 'Installing $1...'; omarchy-pkg-add $2 && sleep 2 && omarchy-font-set '$3'"
}

install_terminal() {
  present_terminal "omarchy-install-terminal $1"
}

show_custom_reminder_input() {
  local minutes
  minutes=$(omarchy-menu-input "Remind in minutes")

  if [[ $minutes =~ ^[0-9]+$ ]] && (( minutes > 0 )); then
    show_reminder_message_input "$minutes"
  elif [[ -n $minutes ]]; then
    omarchy-notification-send -g 󰔛 "Invalid reminder" "Enter the number of minutes" -u critical
    show_custom_reminder_input
  fi
}

show_reminder_message_input() {
  local minutes="$1"
  local message
  message=$(omarchy-menu-input "Reminder message")

  if [[ -n $message ]]; then
    omarchy-reminder "$minutes" "$message"
  else
    omarchy-reminder "$minutes"
  fi
}

get_webcam_list() {
  v4l2-ctl --list-devices 2>/dev/null | while IFS= read -r line; do
    if [[ $line != $'\t'* && -n $line ]]; then
      local name="$line"
      local device
      IFS= read -r device || break
      device=$(tr -d '\t' <<<"$device" | head -1)
      [[ -n $device ]] && echo "$device  $name"
    fi
  done
}

show_webcam_select_menu() {
  local devices
  local count
  devices=$(get_webcam_list)
  if [[ -z $devices ]]; then
    count=0
  else
    count=$(grep -c . <<<"$devices")
  fi

  if [[ -z $devices ]] || (( count == 0 )); then
    omarchy-notification-send "No webcam devices found" -u critical -t 3000
    return 1
  fi

  if (( count == 1 )); then
    printf '%s\n' "${devices%%[[:space:]]*}"
  else
    local selection
    selection=$(omarchy-menu-select "Select Webcam" "$devices") || return 1
    printf '%s\n' "${selection%%[[:space:]]*}"
  fi
}

stop_active_screenrecording() {
  omarchy-capture-screenrecording --stop-recording
}

screenrecord_with_webcam() {
  local device
  device=$(show_webcam_select_menu) || return 1
  omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio --with-webcam --webcam-device="$device"
}


# Runtime-generated providers users may edit
# Use provider:"fonts" only when a submenu calls provider_fonts (or a command)
# to return JSON. Normal submenus are inferred from dotted JSONC ids.

provider_fonts() {
  local menu_id="$1"
  local current_font
  local index=0
  local font
  local label
  current_font=$(omarchy-font-current 2>/dev/null)

  while IFS= read -r font; do
    [[ -n $font ]] || continue
    (( index++ ))
    label="$font$(current_marker "$font" "$current_font")"
    json_item "$menu_id.$index-$(slugify "$font")" "" "$label" "omarchy-font-set $(shell_quote "$font")" "$font typeface"
  done < <(omarchy-font-list 2>/dev/null)
}

provider_power_profiles() {
  local menu_id="$1"
  local current_profile
  local profile
  current_profile=$(powerprofilesctl get 2>/dev/null)

  while IFS= read -r profile; do
    [[ -n $profile ]] || continue
    json_item "$menu_id.$(slugify "$profile")" "󱐋" "$profile$(current_marker "$profile" "$current_profile")" "powerprofilesctl set $(shell_quote "$profile")" "$profile power profile"
  done < <(omarchy-powerprofiles-list 2>/dev/null)
}


# Framework helpers below this line
# Most menu changes should not require editing below here.

# Generic helpers

shell_quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

slugify() {
  local value="${1,,}"
  value="${value//[^a-z0-9]/-}"

  while [[ $value == *--* ]]; do
    value="${value//--/-}"
  done

  while [[ $value == -* ]]; do
    value="${value#-}"
  done

  while [[ $value == *- ]]; do
    value="${value%-}"
  done

  printf '%s' "${value:-item}"
}

current_marker() {
  if [[ $1 == $2 ]]; then
    printf ' ✓'
  fi
}

declare -A CMD_PRESENT_CACHE=()
declare -A STATUS_CACHE=()

cached_status() {
  local key="$1"
  shift

  if [[ -z ${STATUS_CACHE[$key]+x} ]]; then
    "$@" >/dev/null 2>&1
    STATUS_CACHE[$key]=$?
  fi

  return "${STATUS_CACHE[$key]}"
}

omarchy-cmd-present() {
  local cmd

  for cmd in "$@"; do
    if [[ -z ${CMD_PRESENT_CACHE[$cmd]+x} ]]; then
      if command -v "$cmd" >/dev/null 2>&1; then
        CMD_PRESENT_CACHE[$cmd]=0
      else
        CMD_PRESENT_CACHE[$cmd]=1
      fi
    fi

    (( CMD_PRESENT_CACHE[$cmd] == 0 )) || return 1
  done

  return 0
}

omarchy-toggle-enabled() {
  cached_status "toggle:$*" command omarchy-toggle-enabled "$@"
}

omarchy-hibernation-available() {
  cached_status hibernation command omarchy-hibernation-available
}

omarchy-hw-hybrid-gpu() {
  cached_status hw-hybrid-gpu command omarchy-hw-hybrid-gpu
}

omarchy-hw-touchpad() {
  cached_status hw-touchpad command omarchy-hw-touchpad
}

omarchy-hw-touchscreen() {
  cached_status hw-touchscreen command omarchy-hw-touchscreen
}

omarchy-hw-dell-xps-haptic-touchpad() {
  cached_status hw-dell-xps-haptic-touchpad command omarchy-hw-dell-xps-haptic-touchpad
}

default_browser_is() {
  if [[ -z ${DEFAULT_BROWSER_CACHE+x} ]]; then
    DEFAULT_BROWSER_CACHE=$(command omarchy-default-browser 2>/dev/null || true)
  fi

  [[ $DEFAULT_BROWSER_CACHE == "$1" ]]
}

default_terminal_is() {
  if [[ -z ${DEFAULT_TERMINAL_CACHE+x} ]]; then
    DEFAULT_TERMINAL_CACHE=$(command omarchy-default-terminal 2>/dev/null || true)
  fi

  [[ $DEFAULT_TERMINAL_CACHE == "$1" ]]
}

default_editor_is() {
  if [[ -z ${DEFAULT_EDITOR_CACHE+x} ]]; then
    DEFAULT_EDITOR_CACHE=$(command omarchy-default-editor 2>/dev/null || true)
  fi

  [[ $DEFAULT_EDITOR_CACHE == "$1" ]]
}

haptic_touchpad_is() {
  if [[ -z ${HAPTIC_TOUCHPAD_CACHE+x} ]]; then
    HAPTIC_TOUCHPAD_CACHE=$(command dell-xps-touchpad-haptics get 2>/dev/null || true)
  fi

  [[ $HAPTIC_TOUCHPAD_CACHE == "$1" ]]
}

json_string() {
  local value="${1-}"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  value="${value//$'\t'/\\t}"
  value="${value//$'\r'/\\r}"
  value="${value//$'\n'/\\n}"
  printf '"%s"' "$value"
}

json_item() {
  local id="$1"
  local icon="$2"
  local label="$3"
  local action="${4:-}"
  local keywords="${5:-}"
  local description="${6:-}"
  local target="${7:-}"
  local provider="${8:-}"

  printf '{"id": '
  json_string "$id"
  printf ', "icon": '
  json_string "$icon"
  printf ', "label": '
  json_string "$label"

  if [[ -n $action ]]; then
    printf ', "action": '
    json_string "$action"
  fi

  if [[ -n $keywords ]]; then
    printf ', "keywords": '
    json_string "$keywords"
  fi

  if [[ -n $description ]]; then
    printf ', "description": '
    json_string "$description"
  fi

  if [[ -n $target ]]; then
    printf ', "target": '
    json_string "$target"
  fi

  if [[ -n $provider ]]; then
    printf ', "provider": '
    json_string "$provider"
  fi

  printf '}\n'
}


# Menu definition loading
menu_sources() {
  local path

  for path in "$DEFAULT_MENU" "$USER_MENU"; do
    [[ -f $path ]] || continue
    jq -Rrs '
      # Our menu files are JSONC: full-line // comments plus trailing commas.
      # Keep parsing in jq so the menu build path stays fast and dependency-free.
      gsub("(?m)^\\s*//[^\\n]*(\\n|$)"; "") |
      gsub(",(?<tail>\\s*[}\\]])"; "\(.tail)") |
      fromjson |
      if type == "object" then
        (if has("items") and (.items | type) == "object" then .items else . end)
      else
        {}
      end |
      to_entries[] |
      select(.value | type == "object") |
      .value + { id: .key }
    ' "$path"
  done
}

normalize_menu_json() {
  jq -sc '
    def aliases_array:
      if ((.aliases // []) | type) == "array" then (.aliases // [])
      elif ((.aliases // "") != "") then [(.aliases // "")]
      else [] end;

    def normalize_item:
      def inferred_parent:
        if has("parent") then .parent
        elif (.id | contains(".")) then (.id | split(".")[:-1] | join("."))
        else "root" end;

      def inferred_kind:
        if ((.action // "") != "") then "action"
        elif ((.target // "") != "") then "link"
        else "menu" end;

      def id_keywords:
        .id | gsub("[._-]+"; " ");

      def alias_keywords:
        aliases_array | map(gsub("[._-]+"; " ")) | join(" ");

      def keyword_string:
        ([id_keywords, alias_keywords, (.keywords // "")] | join(" ") | split(" ") | map(select(. != ""))) as $terms |
        reduce $terms[] as $term ([]; if index($term) then . else . + [$term] end) |
        join(" ");

      {
        id,
        parent: (if .id == "root" then "" else inferred_parent end),
        kind: inferred_kind,
        icon: (.icon // ""),
        label: (.label // .id),
        target: (.target // ""),
        keywords: keyword_string,
        description: (.description // ""),
        action: (.action // ""),
        provider: (.provider // ""),
        aliases: aliases_array,
        when: (.when // ""),
        checked: (.checked // "")
      };

    reduce (.[] | select(.id)) as $item ({ order: [], by_id: {} };
      if .by_id[$item.id] == null then .order += [$item.id] else . end |
      .by_id[$item.id] = ((.by_id[$item.id] // {}) + $item)
    ) |
    { items: [ .order[] as $id | (.by_id[$id] + { id: $id }) | normalize_item ] }
  '
}

menu_all_json() {
  if [[ -z ${MENU_ALL_JSON_CACHE+x} ]]; then
    MENU_ALL_JSON_CACHE=$(menu_sources | normalize_menu_json) || return
  fi

  printf '%s\n' "$MENU_ALL_JSON_CACHE"
}

menu_static_json() {
  if [[ -z ${MENU_STATIC_JSON_CACHE+x} ]]; then
    MENU_STATIC_JSON_CACHE=$(menu_all_json | jq '
    (.items | map({ key: .id, value: . }) | from_entries) as $by_id |
    def ancestor_ids($id):
      ($id | split(".")) as $parts |
      [range(1; $parts | length) | $parts[0:.] | join(".")];
    def has_provider_ancestor($id):
      any(ancestor_ids($id)[]; (($by_id[.] // {}).provider // "") != "");

    { items: [.items[] | select(has_provider_ancestor(.id) | not)] }
  ') || return
  fi

  printf '%s\n' "$MENU_STATIC_JSON_CACHE"
}

emit_config_items() {
  local fields
  local id
  local icon
  local label
  local action
  local keywords
  local description
  local target
  local provider
  local when
  local checked

  while IFS= read -r fields; do
    eval "set -- $fields"
    id="$1"
    icon="$2"
    label="$3"
    action="$4"
    keywords="$5"
    description="$6"
    target="$7"
    provider="$8"
    when="$9"
    checked="${10}"

    [[ -n $id ]] || continue
    if [[ -n $when ]] && ! eval "$when" >/dev/null 2>&1; then
      continue
    fi

    if [[ -n $checked ]] && eval "$checked" >/dev/null 2>&1; then
      label="$label ✓"
    fi

    json_item "$id" "$icon" "$label" "$action" "$keywords" "$description" "$target" "$provider"
  done < <(jq -r '.items[] | [
    (.id // ""),
    (.icon // ""),
    (.label // .id // ""),
    (.action // ""),
    (.keywords // ""),
    (.description // ""),
    (.target // ""),
    (.provider // ""),
    (.when // ""),
    (.checked // "")
  ] | @sh')
}

menu_evaluated_json() {
  if [[ -z ${MENU_EVALUATED_JSON_CACHE+x} ]]; then
    MENU_EVALUATED_JSON_CACHE=$(menu_all_json | emit_config_items | normalize_menu_json) || return
  fi

  printf '%s\n' "$MENU_EVALUATED_JSON_CACHE"
}

menu_json() {
  if [[ -z ${MENU_JSON_CACHE+x} ]]; then
    MENU_JSON_CACHE=$(menu_static_json | emit_config_items | normalize_menu_json) || return
  fi

  printf '%s\n' "$MENU_JSON_CACHE"
}

# Provider dispatch
provider_mount_id() {
  local provider="$1"

  menu_all_json | jq -r --arg provider "$provider" '.items[] | select(.provider == $provider) | .id' | head -n 1
}

provider_json() {
  local provider="$1"
  local menu_id="${2:-}"
  local provider_function

  [[ -n $provider ]] || return 1
  menu_id=${menu_id:-$(provider_mount_id "$provider")}
  [[ -n $menu_id ]] || return 1

  if [[ $provider =~ ^[a-z0-9-]+$ ]]; then
    provider_function="provider_${provider//-/_}"
    case "$provider_function" in
    provider_mount_id | provider_json) ;;
    *)
      if declare -F "$provider_function" >/dev/null; then
        "$provider_function" "$menu_id" | normalize_menu_json
        return
      fi
      ;;
    esac

    if omarchy-cmd-present "$provider"; then
      "$provider" "$menu_id" | normalize_menu_json
      return
    fi
  fi

  echo "omarchy-menu: unknown provider '$provider'" >&2
  return 1
}


# Routes and actions
route_target() {
  local route="${1,,}"
  local target
  local all_json
  route="${route//_/-}"

  if [[ -z $route || $route == "go" || $route == "menu" ]]; then
    printf 'root'
    return
  fi

  all_json=$(menu_all_json) || return
  target=$(jq -r --arg route "$route" '
    def aliases:
      if (.aliases | type) == "array" then .aliases
      elif (.aliases | type) == "string" then [.aliases]
      else [] end;

    first(.items[] | select(any(aliases[]?; (ascii_downcase | gsub("_"; "-")) == $route)) | .id) // empty
  ' <<<"$all_json") || return

  printf '%s' "${target:-$route}"
}

route_field() {
  local target="$1"
  local field="$2"
  local all_json
  local fields
  local when
  local value

  all_json=$(menu_all_json) || return
  fields=$(jq -r --arg id "$target" --arg field "$field" '
    first(.items[] | select(.id == $id) | [(.when // ""), (.[$field] // "")] | @sh) // empty
  ' <<<"$all_json") || return
  [[ -n $fields ]] || return

  eval "set -- $fields"
  when="$1"
  value="$2"

  if [[ -n $when ]] && ! eval "$when" >/dev/null 2>&1; then
    return
  fi

  printf '%s' "$value"
}

route_kind() {
  local target="$1"

  if [[ $target == "root" ]]; then
    printf 'menu'
    return
  fi

  route_field "$target" kind
}

route_link_target() {
  route_field "$1" target
}

route_action() {
  route_field "$1" action
}

run_action() {
  local id="$1"
  local action="${2:-}"

  if [[ -z $action ]]; then
    action=$(route_action "$id") || return 0
  fi

  [[ -n $action ]] || return 0
  eval "$action"
}


# Quickshell launcher
ensure_menu_style_file() {
  local toggles_dir="$HOME/.local/state/omarchy/toggles"
  local style_file="$toggles_dir/quickshell-menu.json"
  local radius=0

  [[ -f $style_file ]] && return

  mkdir -p "$toggles_dir"
  if [[ -f $toggles_dir/walker.css ]] && grep -q 'border-radius: 6px;' "$toggles_dir/walker.css"; then
    radius=6
  fi

  printf '{ "radius": %s }\n' "$radius" >"$style_file"
}

usage() {
  cat <<USAGE
Usage: omarchy-menu [menu-or-action]
       omarchy-menu --json
       omarchy-menu --provider <provider> [menu-id]

Launch the Quickshell Omarchy menu. Pass a menu name (install, style, system,
capture, toggle, etc.) to open there, or an action id to run it directly.

Options:
  --json                       Print the static menu JSON used by Quickshell
  --provider <provider> [id]   Print JSON rows from a runtime provider
  -h, --help                   Show this help
USAGE
}

menu_layer_visible() {
  hyprctl layers -j 2>/dev/null | jq -e '.. | objects | select(.namespace? == "omarchy-menu")' >/dev/null
}

menu_lock_path() {
  printf '%s/omarchy-menu.lock' "${XDG_RUNTIME_DIR:-/run/user/$UID}"
}

menu_payload_json() {
  local menu_file="$1"
  local initial_menu="$2"
  local selection_file="$3"
  local done_file="$4"
  local colors_payload_base64="$5"
  local font_family="$6"

  jq -nc \
    --arg menu_file "$menu_file" \
    --arg initial_menu "$initial_menu" \
    --arg selection_file "$selection_file" \
    --arg done_file "$done_file" \
    --arg colors_payload_base64 "$colors_payload_base64" \
    --arg font_family "$font_family" \
    '{
      menuJsonFile: $menu_file,
      initialMenu: $initial_menu,
      selectionFile: $selection_file,
      doneFile: $done_file,
      colorsRawBase64: $colors_payload_base64,
      fontFamily: $font_family
    }'
}

close_visible_quickshell_menu() {
  menu_layer_visible || return 1
  omarchy-shell-ipc shell hide omarchy.menu >/dev/null 2>&1 || return 1
}

open_quickshell_menu() {
  local initial_menu="$1"
  local menu_payload
  local colors_file="$HOME/.config/omarchy/current/theme/colors.toml"
  local colors_payload_base64=""
  local font_family
  local menu_file=""
  local selection_file
  local done_file
  local selected_id
  local selected_action
  local menu_started=false
  local lock_path
  local lock_fd
  local summon_result
  local payload

  lock_path=$(menu_lock_path)
  exec {lock_fd}>"$lock_path" || return 1
  if ! flock -n "$lock_fd"; then
    close_visible_quickshell_menu && return 0
    flock "$lock_fd" || return 1
  fi

  if close_visible_quickshell_menu; then
    return 0
  fi

  ensure_menu_style_file

  selection_file=$(mktemp)
  done_file=$(mktemp)
  rm -f "$done_file"
  trap "rm -f $(shell_quote "$selection_file") $(shell_quote "$done_file")" EXIT

  menu_payload=$(menu_json) || return
  if [[ $initial_menu != "root" ]] && ! jq -e --arg id "$initial_menu" '.items[] | select(.id == $id)' <<<"$menu_payload" >/dev/null; then
    menu_payload=$(menu_evaluated_json) || return
  fi

  if [[ -f $colors_file ]]; then
    colors_payload_base64=$(base64 -w0 "$colors_file")
  fi

  font_family=$(omarchy-font-current 2>/dev/null || true)
  font_family=${font_family:-monospace}

  menu_file=$(mktemp)
  printf '%s' "$menu_payload" >"$menu_file"
  trap "rm -f $(shell_quote "$menu_file") $(shell_quote "$selection_file") $(shell_quote "$done_file")" EXIT
  payload=$(menu_payload_json "$menu_file" "$initial_menu" "$selection_file" "$done_file" "$colors_payload_base64" "$font_family") || return

  summon_result=$(omarchy-shell-ipc shell summon omarchy.menu "$payload" 2>/dev/null || true)
  if [[ $summon_result != "ok" ]]; then
    echo "omarchy-menu: Omarchy shell did not accept the menu request" >&2
    return 1
  fi

  for (( i = 0; i < 500; i++ )); do
    if [[ -e $done_file ]] || menu_layer_visible; then
      menu_started=true
      break
    fi
    sleep 0.01
  done

  if [[ $menu_started != true ]]; then
    omarchy-shell-ipc shell hide omarchy.menu >/dev/null 2>&1 || true
    echo "omarchy-menu: Quickshell menu did not open" >&2
    return 1
  fi

  while [[ ! -e $done_file ]]; do
    menu_layer_visible || break
    sleep 0.01
  done

  if [[ -s $selection_file ]]; then
    IFS=$'\t' read -r selected_id selected_action <"$selection_file"
    exec {lock_fd}>&-
    run_action "$selected_id" "$selected_action"
  fi
}

# CLI dispatch
case "${1:-}" in
--help | -h)
  usage
  exit 0
  ;;
--json)
  menu_json || exit $?
  exit 0
  ;;
--provider)
  provider_json "${2:-}" "${3:-}"
  exit $?
  ;;
screenrecord | screenrecording | screen-record)
  close_visible_quickshell_menu || true
  stop_active_screenrecording && exit 0
  ;;
esac

if [[ -z ${1:-} ]]; then
  open_quickshell_menu root
  exit $?
fi

target=$(route_target "${1:-}") || exit $?
kind=$(route_kind "$target") || exit $?

case "$kind" in
action)
  close_visible_quickshell_menu || true
  run_action "$target"
  ;;
menu)
  open_quickshell_menu "$target"
  ;;
link)
  open_quickshell_menu "$(route_link_target "$target")"
  ;;
*)
  open_quickshell_menu root
  ;;
esac
