#!/usr/bin/env bash
# streamdeck-go installer — Linux and macOS
# Handles dotfiles-aware installation with interactive prompts.
set -euo pipefail

BINARY="streamdeck-go"
XDG_CONFIG="${HOME}/.config"
DEFAULT_DOTFILES="${HOME}/dotfiles"

# ── OS detection ───────────────────────────────────────────────────────────────
OS="$(uname -s)"
IS_MAC=false
[[ "$OS" == "Darwin" ]] && IS_MAC=true

if $IS_MAC; then
  BIN_DIR="${HOME}/go/bin"
  LAUNCHAGENTS_DIR="${HOME}/Library/LaunchAgents"
  PLIST_LABEL="com.woodarddigital.streamdeck-go"
  PLIST_DST="${LAUNCHAGENTS_DIR}/${PLIST_LABEL}.plist"
else
  BIN_DIR="${HOME}/.local/bin"
  SYSTEMD_USER="${HOME}/.config/systemd/user"
  UDEV_RULE="/etc/udev/rules.d/99-streamdeck.rules"
fi

# ── Colours ────────────────────────────────────────────────────────────────────
if [[ -t 1 ]]; then
  BOLD='\033[1m'; DIM='\033[2m'
  RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
  BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'
  NC='\033[0m'
else
  BOLD=''; DIM=''; RED=''; GREEN=''; YELLOW=''
  BLUE=''; CYAN=''; MAGENTA=''; NC=''
fi

CHECK="${GREEN}✓${NC}"
CROSS="${RED}✗${NC}"
ARROW="${CYAN}❯${NC}"
WARN="${YELLOW}⚠${NC}"
DOT="${DIM}·${NC}"

# ── Helpers ────────────────────────────────────────────────────────────────────
nl()    { echo ""; }
step()  { echo -e "  ${ARROW} $1"; }
ok()    { echo -e "  ${CHECK} $1"; }
warn()  { echo -e "  ${WARN} $1"; }
info()  { echo -e "  ${DOT} $1"; }
error() { echo -e "  ${CROSS} ${RED}$1${NC}"; }
dim()   { echo -e "  ${DIM}$1${NC}"; }

prompt_yn() {
  local msg="$1" default="${2:-y}"
  local opts; [[ $default == "y" ]] && opts="Y/n" || opts="y/N"
  echo -ne "  ${ARROW} ${msg} ${DIM}[${opts}]${NC} "
  read -r _answer </dev/tty
  _answer="${_answer:-$default}"
  [[ $_answer =~ ^[Yy] ]]
}

prompt_input() {
  local msg="$1" default="$2"
  echo -ne "  ${ARROW} ${msg} ${DIM}[${default}]${NC}: " >/dev/tty
  read -r _input </dev/tty
  echo "${_input:-$default}"
}

pick_directory() {
  local default="$1"
  local result=""

  if command -v fzf &>/dev/null; then
    echo -e "\n  ${DIM}arrow keys to navigate · enter to select · ctrl-c to type path manually${NC}\n" >/dev/tty
    result=$(
      find "$HOME" -maxdepth 4 -type d 2>/dev/null | sort |
      fzf --height=50% \
          --reverse \
          --border=rounded \
          --prompt="  ❯ " \
          --header=" Select dotfiles directory" \
          --preview="ls -1 {} 2>/dev/null | head -30" \
          --preview-window="right:35%:border-left" \
          --query="dotfiles" \
          --bind="ctrl-c:abort"
    ) || result=""
  fi

  if [[ -z "$result" ]]; then
    echo -ne "  ${ARROW} Path to dotfiles repo ${DIM}[${default}]${NC}: " >/dev/tty
    read -r result </dev/tty
    result="${result:-$default}"
  fi

  echo "$result"
}

abspath() {
  echo "${1/#\~/$HOME}"
}

# Portable realpath: macOS ships without readlink -f unless coreutils is installed.
realpath_portable() {
  local path="$1"
  if command -v realpath &>/dev/null; then
    realpath "$path"
  elif command -v python3 &>/dev/null; then
    python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$path"
  else
    echo "${path/#\~/$HOME}"
  fi
}

# Portable install with parent dir creation.
# macOS install(1) lacks -D; use explicit mkdir -p instead.
install_file() {
  local mode="$1" src="$2" dst="$3"
  mkdir -p "$(dirname "$dst")"
  install -m "$mode" "$src" "$dst"
}

# ── Dependency detection ───────────────────────────────────────────────────────

detect_pkg_manager() {
  if   $IS_MAC;                          then echo "brew"
  elif command -v pacman &>/dev/null;    then echo "pacman"
  elif command -v apt    &>/dev/null;    then echo "apt"
  elif command -v dnf    &>/dev/null;    then echo "dnf"
  elif command -v zypper &>/dev/null;    then echo "zypper"
  else echo "unknown"
  fi
}

hidapi_present() {
  if $IS_MAC; then
    # On macOS hidapi is a formula; check pkg-config or the dylib directly.
    pkg-config --exists hidapi 2>/dev/null ||
    [ -f /usr/local/lib/libhidapi.dylib ] ||
    [ -f /opt/homebrew/lib/libhidapi.dylib ]
  else
    pkg-config --exists hidapi-hidraw 2>/dev/null ||
    pkg-config --exists hidapi        2>/dev/null ||
    ldconfig -p 2>/dev/null | grep -q libhidapi
  fi
}

install_pkg() {
  local pm="$1"; shift
  local pkgs=("$@")
  case "$pm" in
    brew)   brew install "${pkgs[@]}" ;;
    pacman) sudo pacman -S --needed --noconfirm "${pkgs[@]}" ;;
    apt)    sudo apt-get install -y "${pkgs[@]}" ;;
    dnf)    sudo dnf install -y "${pkgs[@]}" ;;
    zypper) sudo zypper install -y "${pkgs[@]}" ;;
  esac
}

# ── Header ─────────────────────────────────────────────────────────────────────
nl
echo -e "  ${BOLD}${CYAN}streamdeck-go${NC}  ${DIM}installer${NC}"
echo -e "  ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
nl

# ── 1. System dependencies ─────────────────────────────────────────────────────
step "Checking system dependencies..."
PM="$(detect_pkg_manager)"

# Go
if ! command -v go &>/dev/null; then
  warn "Go not found."
  if [[ "$PM" == "unknown" ]]; then
    error "Could not detect a package manager — install Go manually: https://go.dev/dl/"
    exit 1
  fi
  if prompt_yn "Install Go now?" "y"; then
    case "$PM" in
      brew)   install_pkg brew go ;;
      pacman) install_pkg pacman go ;;
      apt)    install_pkg apt golang-go ;;
      dnf)    install_pkg dnf golang ;;
      zypper) install_pkg zypper go ;;
    esac
    ok "Go installed: $(go version)"
  else
    error "Go is required to build — aborting."
    exit 1
  fi
else
  ok "Go found: $(go version | awk '{print $3}')"
fi

# hidapi
if hidapi_present; then
  ok "hidapi found"
else
  warn "hidapi not found (required for USB HID communication)."
  if [[ "$PM" == "unknown" ]]; then
    warn "Could not detect a package manager — install hidapi manually, then re-run."
    if $IS_MAC; then
      warn "  macOS:  brew install hidapi"
    else
      warn "  Arch:   sudo pacman -S hidapi"
      warn "  Debian: sudo apt install libhidapi-dev libhidapi-hidraw0"
      warn "  Fedora: sudo dnf install hidapi hidapi-devel"
    fi
    exit 1
  fi
  if prompt_yn "Install hidapi now?" "y"; then
    case "$PM" in
      brew)   install_pkg brew hidapi ;;
      pacman) install_pkg pacman hidapi ;;
      apt)    install_pkg apt libhidapi-dev libhidapi-hidraw0 ;;
      dnf)    install_pkg dnf hidapi hidapi-devel ;;
      zypper) install_pkg zypper hidapi-devel ;;
    esac
    ok "hidapi installed"
  else
    error "hidapi is required — aborting."
    exit 1
  fi
fi

# obs-cmd (optional — required for OBS module)
if command -v obs-cmd &>/dev/null; then
  ok "obs-cmd found"
else
  info "obs-cmd not found (required for OBS Studio module)"
  if prompt_yn "Install obs-cmd now?" "y"; then
    OBS_CMD_VERSION=$(curl -sL "https://api.github.com/repos/grigio/obs-cmd/releases/latest" | grep '"tag_name"' | head -1 | sed 's/.*"v\(.*\)".*/\1/')
    if [[ -z "$OBS_CMD_VERSION" ]]; then
      warn "Could not fetch obs-cmd release — install manually: cargo install obs-cmd"
    elif $IS_MAC; then
      # Detect architecture — Apple Silicon vs Intel
      ARCH="$(uname -m)"
      if [[ "$ARCH" == "arm64" ]]; then
        OBS_CMD_ASSET="obs-cmd-arm64-macos.tar.gz"
      else
        OBS_CMD_ASSET="obs-cmd-x64-macos.tar.gz"
      fi
      step "Downloading obs-cmd v${OBS_CMD_VERSION} (${ARCH})..."
      curl -sL "https://github.com/grigio/obs-cmd/releases/download/v${OBS_CMD_VERSION}/${OBS_CMD_ASSET}" \
        | tar xz -C /tmp
      install -m 755 /tmp/obs-cmd /usr/local/bin/obs-cmd
      rm -f /tmp/obs-cmd
    else
      # Linux — x86_64 musl static binary
      step "Downloading obs-cmd v${OBS_CMD_VERSION}..."
      curl -sL "https://github.com/grigio/obs-cmd/releases/download/v${OBS_CMD_VERSION}/obs-cmd-v${OBS_CMD_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
        | tar xz -C /tmp
      sudo install -m 755 /tmp/obs-cmd /usr/local/bin/obs-cmd
      rm -f /tmp/obs-cmd
    fi
    if command -v obs-cmd &>/dev/null; then
      ok "obs-cmd installed"
    else
      warn "obs-cmd install may have failed — OBS module won't work until it's available"
    fi
  else
    info "Skipped — OBS module will not work until obs-cmd is installed"
  fi
fi
nl

# ── 2. Build ───────────────────────────────────────────────────────────────────
step "Building ${BOLD}${BINARY}${NC}..."
if go build -o "${BINARY}" ./cmd/streamdeck/ 2>&1; then
  ok "Build complete"
else
  error "Build failed — aborting."
  exit 1
fi
nl

# ── 3. Device permissions ──────────────────────────────────────────────────────
if $IS_MAC; then
  step "Device permissions (macOS)..."
  info "macOS grants HID access via IOKit — no udev rule needed."
  info "On first run the OS may show an Input Monitoring prompt; grant access if asked."
  ok "No action required"
else
  step "Checking udev rule..."
  if [[ -f "${UDEV_RULE}" ]]; then
    ok "udev rule already installed — skipping"
  else
    info "Installing udev rule (requires sudo):"
    dim "  ${UDEV_RULE}"
    echo 'KERNEL=="hidraw*", ATTRS{idVendor}=="0fd9", MODE="0666"' \
      | sudo tee "${UDEV_RULE}" >/dev/null
    sudo udevadm control --reload
    sudo udevadm trigger
    ok "udev rule installed — device accessible without root"
  fi
fi
nl

# ── 4. Binary ──────────────────────────────────────────────────────────────────
step "Installing binary to ${BOLD}${BIN_DIR}/${BINARY}${NC}..."
mkdir -p "${BIN_DIR}"
install -m 755 "${BINARY}" "${BIN_DIR}/${BINARY}"
ok "Binary installed"
if $IS_MAC && [[ ":$PATH:" != *":${BIN_DIR}:"* ]]; then
  warn "${BIN_DIR} is not in your PATH — add it to your shell profile:"
  dim "  export PATH=\"\$HOME/go/bin:\$PATH\""
fi
nl

# ── 5. Dotfiles ────────────────────────────────────────────────────────────────
echo -e "  ${BOLD}Config location${NC}"
echo -e "  ${DIM}─────────────────────────────────────────────${NC}"
nl
info "streamdeck-go stores its config and icons in a single directory."
info "You can keep that directory inside your dotfiles repo and symlink it"
info "into ~/.config — the same pattern used by Hyprland, Waybar, etc."
nl

USE_DOTFILES=false
CONFIG_DIR=""

if prompt_yn "Use a dotfiles directory?" "y"; then
  nl
  DOTFILES_RAW="$(pick_directory "${DEFAULT_DOTFILES}")"
  DOTFILES="$(abspath "${DOTFILES_RAW}")"

  if [[ ! -d "${DOTFILES}" ]]; then
    warn "Directory ${DOTFILES} does not exist — it will be created."
  fi

  CONFIG_DIR="${DOTFILES}/.config/streamdeck-go"
  SYMLINK_TARGET="${XDG_CONFIG}/streamdeck-go"

  nl
  echo -e "  ${DIM}─────────────────────────────────────────────${NC}"
  info "${BOLD}Will create:${NC}"
  dim "    ${CONFIG_DIR}/"
  dim "    ${CONFIG_DIR}/config.yaml  ${DIM}(if absent)${NC}"
  dim "    ${CONFIG_DIR}/icons/"
  nl
  info "${BOLD}Will symlink:${NC}"
  dim "    ${SYMLINK_TARGET}"
  dim "    └─▶ ${CONFIG_DIR}"
  echo -e "  ${DIM}─────────────────────────────────────────────${NC}"
  nl

  if ! prompt_yn "Confirm?" "y"; then
    nl
    warn "Aborted — nothing written."
    exit 0
  fi

  USE_DOTFILES=true
else
  nl
  CONFIG_DIR="${XDG_CONFIG}/streamdeck-go"
  info "Config will go directly in ${CONFIG_DIR}"
fi

nl

# ── 6. Create config directory ─────────────────────────────────────────────────
step "Setting up config directory..."
mkdir -p "${CONFIG_DIR}/icons"
ok "Created ${CONFIG_DIR}/icons/"

if [[ ! -f "${CONFIG_DIR}/config.yaml" ]]; then
  install -m 644 config.example.yaml "${CONFIG_DIR}/config.yaml"
  ok "Default config written to config.yaml"
else
  ok "config.yaml already exists — not overwritten"
fi

# Always install/update modules.yaml — this is a registry of available module
# definitions, not user data. User customisations go in config.yaml (params
# overrides per key). Keeping modules.yaml current ensures new modules (OBS,
# Slack, etc.) are available immediately after upgrade.
install -m 644 modules.example.yaml "${CONFIG_DIR}/modules.yaml"
ok "modules.yaml installed (updated to latest)"

# Copy bundled icons (never overwrite existing ones the user may have customised).
if [[ -d "icons" ]]; then
  copied=0
  for f in icons/*; do
    [[ -f "$f" ]] || continue
    dest="${CONFIG_DIR}/icons/$(basename "$f")"
    if [[ ! -f "$dest" ]]; then
      cp "$f" "$dest"
      (( copied++ )) || true
    fi
  done
  if (( copied > 0 )); then
    ok "Copied ${copied} bundled icon(s) to ${CONFIG_DIR}/icons/"
  else
    ok "Bundled icons already present — not overwritten"
  fi
fi

# ── 7. Symlink (dotfiles mode only) ───────────────────────────────────────────
if [[ "${USE_DOTFILES}" == "true" ]]; then
  nl
  step "Creating symlink..."
  SYMLINK_TARGET="${XDG_CONFIG}/streamdeck-go"

  if [[ -L "${SYMLINK_TARGET}" ]]; then
    existing="$(realpath_portable "${SYMLINK_TARGET}")"
    if [[ "${existing}" == "${CONFIG_DIR}" ]]; then
      ok "Symlink already correct — skipping"
    else
      warn "Symlink exists but points to ${existing}"
      if prompt_yn "Replace it?" "y"; then
        rm "${SYMLINK_TARGET}"
        ln -s "${CONFIG_DIR}" "${SYMLINK_TARGET}"
        ok "Symlink updated → ${CONFIG_DIR}"
      else
        warn "Symlink not updated — you may need to fix this manually."
      fi
    fi
  elif [[ -d "${SYMLINK_TARGET}" ]]; then
    warn "${SYMLINK_TARGET} is a real directory (not a symlink)."
    info "Move its contents to ${CONFIG_DIR} first, then re-run install."
    warn "Skipping symlink — config will work from ${CONFIG_DIR} but is not linked."
  else
    mkdir -p "${XDG_CONFIG}"
    ln -s "${CONFIG_DIR}" "${SYMLINK_TARGET}"
    ok "Symlinked ${SYMLINK_TARGET} → ${CONFIG_DIR}"
  fi
fi

# ── 8. Service ─────────────────────────────────────────────────────────────────
nl
if $IS_MAC; then
  step "Installing launchd agent..."
  mkdir -p "${LAUNCHAGENTS_DIR}"
  LOG_PATH="${HOME}/Library/Logs/streamdeck-go.log"
  # Substitute binary path and log path into the plist template.
  sed \
    -e "s|STREAMDECK_BINARY_PATH|${BIN_DIR}/${BINARY}|g" \
    -e "s|STREAMDECK_LOG_PATH|${LOG_PATH}|g" \
    launchd/com.woodarddigital.streamdeck-go.plist \
    > "${PLIST_DST}"
  # Unload first in case an old version is already loaded.
  launchctl unload "${PLIST_DST}" 2>/dev/null || true
  launchctl load "${PLIST_DST}"
  ok "launchd agent loaded — will start at login"
else
  step "Installing systemd user service..."
  mkdir -p "${SYSTEMD_USER}"
  install_file 644 systemd/streamdeck-go.service "${SYSTEMD_USER}/streamdeck-go.service"
  systemctl --user daemon-reload
  systemctl --user enable --now streamdeck-go.service
  ok "Service enabled and started"
fi

# ── Done ───────────────────────────────────────────────────────────────────────
nl
echo -e "  ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "  ${GREEN}${BOLD}All done.${NC}"
nl

if [[ "${USE_DOTFILES}" == "true" ]]; then
  info "Your config lives in your dotfiles repo:"
  dim "    ${CONFIG_DIR}/config.yaml"
  dim "    ${CONFIG_DIR}/icons/"
else
  info "Your config:"
  dim "    ${CONFIG_DIR}/config.yaml"
  dim "    ${CONFIG_DIR}/icons/"
fi

nl
info "Edit and save config.yaml — the deck reloads automatically."
info "For privileged commands (suspend, reboot, etc): ${BOLD}make install-helper${NC}"
nl
if $IS_MAC; then
  info "Logs: ${DIM}tail -f ~/Library/Logs/streamdeck-go.log${NC}"
else
  info "Logs: ${DIM}journalctl --user -u streamdeck-go -f${NC}"
fi
nl
