#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -x "$SCRIPT_DIR/bin/kinect_voice" ]]; then
  DEFAULT_RUNTIME_DIR="$SCRIPT_DIR"
else
  DEFAULT_RUNTIME_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
fi
RUNTIME_DIR="${KINECTVISION_RUNTIME_DIR:-$DEFAULT_RUNTIME_DIR}"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/kinectvision"
STATE_FILE="$STATE_DIR/kinectvoice-global-mic.env"
FIFO_PATH="${KINECTVOICE_FIFO:-/tmp/kinectvoice-beamformed-mic.pcm}"
SOURCE_NAME="kinectvoice_beamformed_mic"
SOURCE_DESC="KinectVoice Beamformed Mic"
GAIN_DB="${KINECTVOICE_INPUT_GAIN_DB:-12}"
LOG_PATH="$STATE_DIR/kinectvoice-live.log"
PID_FILE="$STATE_DIR/kinectvoice-live.pid"
PERSISTENCE_SERVICE="kinectvoice-global-mic.service"
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
PERSISTENCE_SERVICE_PATH="$SYSTEMD_USER_DIR/$PERSISTENCE_SERVICE"

mkdir -p "$STATE_DIR"

kinect_voice_bin() {
  if [[ -x "$RUNTIME_DIR/bin/kinect_voice" ]]; then
    echo "$RUNTIME_DIR/bin/kinect_voice"
  elif [[ -x "$RUNTIME_DIR/build/kinect_voice" ]]; then
    echo "$RUNTIME_DIR/build/kinect_voice"
  else
    return 1
  fi
}

run_voice() {
  local voice_bin
  voice_bin="$(kinect_voice_bin)" || { echo "error: kinect_voice binary not found under $RUNTIME_DIR/bin or $RUNTIME_DIR/build" >&2; return 10; }
  export LD_LIBRARY_PATH="$RUNTIME_DIR/lib:$RUNTIME_DIR/build/libfreenect2/lib:${LD_LIBRARY_PATH:-}"
  "$voice_bin" "$@"
}

have_pactl() { command -v pactl >/dev/null 2>&1; }
have_wpctl() { command -v wpctl >/dev/null 2>&1; }

save_previous_default() {
  local previous=""
  local existing_module_id=""
  if [[ -f "$STATE_FILE" ]]; then
    # Preserve fields that are unrelated to the default-source checkpoint.
    # shellcheck disable=SC1090
    source "$STATE_FILE"
    existing_module_id="${MODULE_ID:-}"
  fi
  if have_pactl; then
    previous="$(pactl info | awk -F': ' '/Default Source:/ {print $2; exit}')"
  elif have_wpctl; then
    previous="$(wpctl status | awk '/Sources:/{inside=1; next} inside && /Source endpoints:/{exit} inside && /\*/{sub(/^.*\* +[0-9]+\. /, ""); sub(/ \[vol:.*$/, ""); print; exit}')"
  fi
  {
    echo "PREVIOUS_DEFAULT_SOURCE=${previous@Q}"
    echo "SOURCE_NAME=${SOURCE_NAME@Q}"
    echo "FIFO_PATH=${FIFO_PATH@Q}"
    echo "LOG_PATH=${LOG_PATH@Q}"
    echo "RUNTIME_DIR=${RUNTIME_DIR@Q}"
    echo "PERSISTENCE_SERVICE=${PERSISTENCE_SERVICE@Q}"
    echo "PERSIST_DEFAULT_SOURCE=${PERSIST_DEFAULT_SOURCE:-0}"
    if [[ -n "$existing_module_id" ]]; then
      echo "MODULE_ID=${existing_module_id@Q}"
    fi
  } > "$STATE_FILE"
}

load_state() {
  if [[ -f "$STATE_FILE" ]]; then
    # shellcheck disable=SC1090
    source "$STATE_FILE"
  fi
}

status() {
  echo "KinectVoice global mic status"
  echo "runtime: $RUNTIME_DIR"
  echo "state: $STATE_FILE"
  if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
    echo "live_processing: running pid=$(cat "$PID_FILE")"
  else
    echo "live_processing: stopped"
  fi
  if have_pactl; then
    echo "audio_control: pactl available"
    pactl list short sources | grep -E "$SOURCE_NAME|KinectVoice" || true
    pactl info | awk -F': ' '/Default Source:/ {print "default_source: "$2}'
  else
    echo "audio_control: pactl missing; virtual mic creation through module-pipe-source is unavailable"
  fi
  if have_wpctl; then
    wpctl status | sed -n '/Sources:/,/Source endpoints:/p'
  fi
}

ensure_fifo() {
  rm -f "$FIFO_PATH"
  mkfifo "$FIFO_PATH"
}

enable_global_mic() {
  if ! have_pactl; then
    echo "error: pactl is required to create '$SOURCE_DESC' via module-pipe-source on this runtime." >&2
    echo "On Ubuntu, pactl is usually provided by pulseaudio-utils. Suggested command after review: sudo apt install pulseaudio-utils" >&2
    echo "No packages were installed and no audio settings were changed." >&2
    return 3
  fi
  save_previous_default
  ensure_fifo
  local module_id
  module_id="$(pactl load-module module-pipe-source source_name="$SOURCE_NAME" file="$FIFO_PATH" format=s16le rate=16000 channels=1 source_properties="device.description='$SOURCE_DESC'")"
  echo "MODULE_ID=${module_id@Q}" >> "$STATE_FILE"
  echo "created virtual microphone: $SOURCE_DESC (module $module_id)"
}

start_live() {
  if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
    echo "KinectVoice live processing already running pid=$(cat "$PID_FILE")"
    return 0
  fi
  if [[ ! -p "$FIFO_PATH" ]]; then
    echo "error: FIFO does not exist at $FIFO_PATH. Run kinectvoicectl enable-global-mic first." >&2
    echo "No live process was started, to avoid writing endless PCM into a regular file." >&2
    return 5
  fi
  local voice_bin
  voice_bin="$(kinect_voice_bin)" || { echo "error: kinect_voice binary not found under $RUNTIME_DIR/bin or $RUNTIME_DIR/build" >&2; return 10; }
  export LD_LIBRARY_PATH="$RUNTIME_DIR/lib:$RUNTIME_DIR/build/libfreenect2/lib:${LD_LIBRARY_PATH:-}"
  nohup "$voice_bin" --live --input-gain-db "$GAIN_DB" --pcm-output "$FIFO_PATH" --log "$LOG_PATH" > "$STATE_DIR/kinectvoice-live.stdout" 2>&1 &
  echo $! > "$PID_FILE"
  echo "started KinectVoice live processing pid=$(cat "$PID_FILE")"
}

stop_live() {
  if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
    kill "$(cat "$PID_FILE")" || true
    sleep 1
    if kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
      kill -TERM "$(cat "$PID_FILE")" || true
    fi
    echo "stopped KinectVoice live processing"
  else
    echo "KinectVoice live processing not running"
  fi
  rm -f "$PID_FILE"
}

make_default() {
  if ! have_pactl; then
    echo "error: pactl is required to set the default source by source name. No default was changed." >&2
    return 3
  fi
  save_previous_default
  pactl set-default-source "$SOURCE_NAME"
  echo "set default microphone to: $SOURCE_DESC"
}

restore_default() {
  load_state
  if ! have_pactl; then
    echo "error: pactl is required to restore the previous default source. No default was changed." >&2
    return 3
  fi
  if [[ -n "${PREVIOUS_DEFAULT_SOURCE:-}" ]]; then
    pactl set-default-source "$PREVIOUS_DEFAULT_SOURCE"
    echo "restored previous default microphone: $PREVIOUS_DEFAULT_SOURCE"
  else
    echo "no previous default source recorded in $STATE_FILE" >&2
    return 4
  fi
}

matching_module_ids() {
  have_pactl || return 0
  pactl list short modules | awk -v source_name="$SOURCE_NAME" '
    $2 == "module-pipe-source" && index($0, "source_name=" source_name) { print $1 }
  '
}

source_exists() {
  have_pactl || return 1
  pactl list short sources | awk -v source_name="$SOURCE_NAME" '$2 == source_name { found=1 } END { exit found ? 0 : 1 }'
}

kinectvoice_module_exists() {
  have_pactl || return 1
  [[ -n "$(matching_module_ids)" ]]
}

unload_kinectvoice_modules() {
  if ! have_pactl; then
    echo "no loaded pactl module recorded, or pactl unavailable"
    return 0
  fi

  local ids=()
  local id
  if [[ -n "${MODULE_ID:-}" ]]; then
    ids+=("$MODULE_ID")
  fi
  while IFS= read -r id; do
    [[ -n "$id" ]] || continue
    ids+=("$id")
  done < <(matching_module_ids)

  if [[ ${#ids[@]} -eq 0 ]]; then
    if source_exists || kinectvoice_module_exists; then
      echo "warning: $SOURCE_DESC source/module still present but no unloadable module id was found" >&2
      return 6
    fi
    echo "KinectVoice virtual microphone already removed"
    echo "confirmed KinectVoice virtual microphone source removed"
    return 0
  fi

  local seen=" "
  local unloaded=0
  local failed=0
  for id in "${ids[@]}"; do
    [[ -n "$id" ]] || continue
    if [[ "$seen" == *" $id "* ]]; then
      continue
    fi
    seen+="$id "
    local unload_error=""
    if unload_error="$(pactl unload-module "$id" 2>&1)"; then
      echo "unloaded KinectVoice virtual microphone module: $id"
      unloaded=$((unloaded + 1))
    else
      if [[ "$unload_error" == *"No such entity"* ]]; then
        echo "notice: KinectVoice virtual microphone module already gone: $id"
      else
        echo "notice: KinectVoice virtual microphone module could not be unloaded: $id" >&2
        [[ -n "$unload_error" ]] && echo "pactl: $unload_error" >&2
      fi
      failed=$((failed + 1))
    fi
  done

  if source_exists || kinectvoice_module_exists; then
    echo "warning: $SOURCE_DESC source/module is still present after unload attempt" >&2
    return 6
  fi

  if [[ $unloaded -eq 0 && $failed -gt 0 ]]; then
    echo "KinectVoice virtual microphone already removed"
  fi
  echo "confirmed KinectVoice virtual microphone source removed"
  return 0
}

disable_global_mic() {
  load_state
  stop_live || true
  unload_kinectvoice_modules
  local unload_result=$?
  rm -f "$FIFO_PATH"
  return "$unload_result"
}

service_helper_path() {
  if [[ -x "$RUNTIME_DIR/install/kinectvoice-session-start.sh" ]]; then
    echo "$RUNTIME_DIR/install/kinectvoice-session-start.sh"
  elif [[ -x "$RUNTIME_DIR/kinectvoice-session-start.sh" ]]; then
    echo "$RUNTIME_DIR/kinectvoice-session-start.sh"
  else
    return 1
  fi
}

control_script_path() {
  if [[ -x "$RUNTIME_DIR/kinectvoicectl" ]]; then
    echo "$RUNTIME_DIR/kinectvoicectl"
  elif [[ -x "$RUNTIME_DIR/install/kinectvoicectl" ]]; then
    echo "$RUNTIME_DIR/install/kinectvoicectl"
  else
    return 1
  fi
}

have_systemctl_user() {
  command -v systemctl >/dev/null 2>&1
}

write_state_with_persistence() {
  local persist_default="$1"
  load_state
  {
    local previous_value="${PREVIOUS_DEFAULT_SOURCE:-}"
    echo "PREVIOUS_DEFAULT_SOURCE=${previous_value@Q}"
    echo "SOURCE_NAME=${SOURCE_NAME@Q}"
    echo "FIFO_PATH=${FIFO_PATH@Q}"
    echo "LOG_PATH=${LOG_PATH@Q}"
    echo "RUNTIME_DIR=${RUNTIME_DIR@Q}"
    echo "PERSISTENCE_SERVICE=${PERSISTENCE_SERVICE@Q}"
    echo "PERSIST_DEFAULT_SOURCE=${persist_default@Q}"
    if [[ -n "${MODULE_ID:-}" ]]; then
      echo "MODULE_ID=${MODULE_ID@Q}"
    fi
  } > "$STATE_FILE"
}

write_user_service() {
  local helper ctl_path
  helper="$(service_helper_path)" || { echo "error: kinectvoice-session-start.sh not found under $RUNTIME_DIR" >&2; return 10; }
  ctl_path="$(control_script_path)" || { echo "error: kinectvoicectl not found under $RUNTIME_DIR" >&2; return 10; }
  mkdir -p "$SYSTEMD_USER_DIR"
  cat > "$PERSISTENCE_SERVICE_PATH" <<EOF_SERVICE
[Unit]
Description=KinectVoice Beamformed Mic user-session startup
After=pipewire.service pipewire-pulse.service wireplumber.service default.target
Wants=pipewire.service pipewire-pulse.service wireplumber.service

[Service]
Type=oneshot
RemainAfterExit=yes
Environment=KINECTVISION_RUNTIME_DIR=$RUNTIME_DIR
ExecStart=$helper
ExecStop=$ctl_path disable-global-mic
TimeoutStartSec=45

[Install]
WantedBy=default.target
EOF_SERVICE
}

enable_persistence() {
  local persist_default="0"
  case "${1:-}" in
    --default) persist_default="1" ;;
    "") ;;
    *) echo "usage: kinectvoicectl enable-persistence [--default]" >&2; return 2 ;;
  esac
  if ! have_systemctl_user; then
    echo "error: systemctl is required for user-level persistence. No startup behavior was changed." >&2
    return 3
  fi
  write_state_with_persistence "$persist_default"
  write_user_service
  echo "created user service: $PERSISTENCE_SERVICE_PATH"
  echo "about to enable user startup service with: systemctl --user enable $PERSISTENCE_SERVICE"
  systemctl --user daemon-reload
  systemctl --user enable "$PERSISTENCE_SERVICE"
  echo "enabled KinectVoice user-session persistence"
  if [[ "$persist_default" == "1" ]]; then
    echo "persistent default-source behavior: enabled"
  else
    echo "persistent default-source behavior: disabled; mic will be selectable but not forced default"
  fi
  echo "start now with: systemctl --user start $PERSISTENCE_SERVICE"
  echo "disable with: kinectvoicectl disable-persistence"
}

disable_persistence() {
  local had_service=0
  if have_systemctl_user; then
    if systemctl --user list-unit-files "$PERSISTENCE_SERVICE" >/dev/null 2>&1 || [[ -f "$PERSISTENCE_SERVICE_PATH" ]]; then
      had_service=1
      systemctl --user disable --now "$PERSISTENCE_SERVICE" >/dev/null 2>&1 || true
      systemctl --user daemon-reload >/dev/null 2>&1 || true
    fi
  fi
  rm -f "$PERSISTENCE_SERVICE_PATH"
  if [[ -f "$STATE_FILE" ]]; then
    load_state
    write_state_with_persistence "0"
  fi
  echo "disabled KinectVoice user-session persistence"
  if [[ "$had_service" -eq 1 ]]; then
    echo "removed user service: $PERSISTENCE_SERVICE_PATH"
  fi
  echo "current global mic is not automatically removed; run disable-global-mic if you want to stop/remove it now"
}

persistence_status() {
  load_state
  echo "KinectVoice persistence status"
  echo "service_name: $PERSISTENCE_SERVICE"
  echo "service_file: $PERSISTENCE_SERVICE_PATH"
  if [[ -f "$PERSISTENCE_SERVICE_PATH" ]]; then
    echo "service_file_present: yes"
  else
    echo "service_file_present: no"
  fi
  echo "persistent_default_source: ${PERSIST_DEFAULT_SOURCE:-0}"
  if have_systemctl_user; then
    local enabled="no"
    local active="no"
    enabled="$(systemctl --user is-enabled "$PERSISTENCE_SERVICE" 2>/dev/null || true)"
    active="$(systemctl --user is-active "$PERSISTENCE_SERVICE" 2>/dev/null || true)"
    [[ -n "$enabled" ]] || enabled="no"
    [[ -n "$active" ]] || active="no"
    echo "systemd_user_enabled: $enabled"
    echo "systemd_user_active: $active"
  else
    echo "systemd_user_available: no"
  fi
  echo "logs: $STATE_DIR/kinectvoice-persistence.log"
}

first_run_test() {
  echo "KinectVoice first-run confirmation"
  echo "Kinect v2 detection:"
  if [[ -r /proc/asound/cards ]] && grep -qi "Xbox NUI Sensor\|Sensor" /proc/asound/cards; then
    echo "  Kinect mic array detected in /proc/asound/cards"
  else
    echo "  warning: Kinect mic array not obvious in /proc/asound/cards"
  fi
  status || true
  echo "Speak toward the Kinect for the next few seconds..."
  local raw="$STATE_DIR/first-run-raw.wav"
  local beam="$STATE_DIR/first-run-beamformed.wav"
  local event="$STATE_DIR/first-run-event.json"
  local log="$STATE_DIR/first-run.log"
  run_voice --duration 4 --input-gain-db "$GAIN_DB" --output "$raw" --beamformed "$beam" --event "$event" --log "$log"
  if grep -q '"detected_speech": true' "$event"; then
    echo "KinectVoice Beamformed Mic processing path detected speech through the Kinect."
    echo "Your computer can process KinectVoice beamformed audio."
  else
    echo "warning: speech was not detected in the first-run test; check gain, distance, and room noise."
  fi
  echo "test artifacts: $STATE_DIR"
  echo "test again: kinectvoicectl first-run-test"
}

case "${1:-status}" in
  status) status ;;
  start) start_live ;;
  stop) stop_live ;;
  restart) stop_live; start_live ;;
  enable-global-mic) enable_global_mic ;;
  disable-global-mic) disable_global_mic ;;
  make-default) make_default ;;
  restore-default) restore_default ;;
  first-run-test) first_run_test ;;
  enable-persistence) shift; enable_persistence "${1:-}" ;;
  disable-persistence) disable_persistence ;;
  persistence-status) persistence_status ;;
  *)
    echo "usage: kinectvoicectl {status|start|stop|restart|enable-global-mic|disable-global-mic|make-default|restore-default|first-run-test|enable-persistence [--default]|disable-persistence|persistence-status}" >&2
    exit 2
    ;;
esac
