#!/usr/bin/bash
set -uo pipefail

CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/generalshare"
CONFIG="$CONFIG_DIR/config.json"
GUI=0
INTERVAL=1

usage() {
  cat <<'EOF'
GeneralShare clipboard sync

Usage:
  generalshare-clip.sh --send [--gui]       send the current clipboard to the hub
  generalshare-clip.sh --copy [--gui]       copy the hub clipboard to this device
  generalshare-clip.sh --watch              two-way sync while running
  generalshare-clip.sh --status             show the hub clipboard
  generalshare-clip.sh --install-autostart  run --watch at login

Options:
  --hub URL       override the saved hub
  --code CODE     pairing code if this device is not paired yet
  --interval N    poll interval in seconds for --watch (default 1)
  --gui           use zenity for prompts/errors

Clipboard text is kept in memory by the hub and is never written to disk.
Wayland needs wl-clipboard; X11 needs xclip or xsel.
EOF
}

MODE=""
HUB_OVERRIDE=""
CODE_OVERRIDE=""

while [ $# -gt 0 ]; do
  case "$1" in
    --send|--copy|--watch|--status|--install-autostart)
      MODE="${1#--}"; shift ;;
    --hub) HUB_OVERRIDE="${2:-}"; shift 2 ;;
    --code) CODE_OVERRIDE="${2:-}"; shift 2 ;;
    --interval) INTERVAL="${2:-1}"; shift 2 ;;
    --gui) GUI=1; shift ;;
    --help|-h) usage; exit 0 ;;
    *) echo "Unknown option: $1" >&2; usage; exit 2 ;;
  esac
done

is_gui() { [ "$GUI" -eq 1 ] && command -v zenity >/dev/null 2>&1; }

fail() {
  local message="$*"
  echo "GeneralShare: $message" >&2
  if is_gui; then
    zenity --error --title="GeneralShare" --text="$message" >/dev/null 2>&1 || true
  fi
  exit 1
}

notify() {
  command -v notify-send >/dev/null 2>&1 &&
    notify-send "GeneralShare" "$*" >/dev/null 2>&1 || true
}

prompt_code() {
  if [ -n "$CODE_OVERRIDE" ]; then
    printf '%s' "$CODE_OVERRIDE"
    return
  fi
  if is_gui; then
    zenity --entry --title="GeneralShare" \
      --text="Enter the 6-digit code shown on the receiver:" --entry-text=""
  else
    local code
    read -r -p "Pairing code: " code
    printf '%s' "$code"
  fi
}

load_saved_hub() {
  [ -f "$CONFIG" ] || return 0
  jq -r '.hub // empty' "$CONFIG" 2>/dev/null || true
}

load_token() {
  [ -f "$CONFIG" ] || return 0
  jq -r '.token // empty' "$CONFIG" 2>/dev/null || true
}

save_config() {
  mkdir -p "$CONFIG_DIR"
  jq -n --arg hub "$1" --arg token "$2" --arg name "$3" --arg id "$4" \
    '{hub:$hub,token:$token,name:$name,id:$id}' > "$CONFIG"
  chmod 600 "$CONFIG" 2>/dev/null || true
}

pair_hub() {
  local hub="$1" code="$2" body raw http response token name id
  body=$(jq -n --arg code "$code" --arg name "$(hostname)" --arg platform "linux" \
    '{code:$code,name:$name,platform:$platform}')
  raw=$(curl -sS -w '\n%{http_code}' -X POST -H 'Content-Type: application/json' \
    --data-binary "$body" "$hub/api/pair") || fail "Cannot reach $hub"
  http=$(printf '%s\n' "$raw" | tail -n 1)
  response=$(printf '%s\n' "$raw" | sed '$d')
  [ "$http" = "200" ] || fail "Pairing failed (HTTP $http): $response"
  token=$(jq -r '.token // empty' <<<"$response")
  [ -n "$token" ] || fail "Pairing failed: $response"
  name=$(jq -r '.name // "receiver"' <<<"$response")
  id=$(jq -r '.deviceId // ""' <<<"$response")
  save_config "$hub" "$token" "$name" "$id"
  TOKEN="$token"
  notify "Paired with $name"
}

select_hub() {
  HUB="${HUB_OVERRIDE:-$(load_saved_hub)}"
  [ -n "$HUB" ] || fail "No saved hub. Pair first with generalshare-send.sh --pair, or pass --hub."
  TOKEN=$(load_token)
  if [ -z "$TOKEN" ]; then
    local code
    code=$(prompt_code)
    [[ "$code" =~ ^[0-9]{6}$ ]] || fail "A 6-digit pairing code is required."
    pair_hub "$HUB" "$code"
  fi
}

clip_tool() {
  if [ -n "${WAYLAND_DISPLAY:-}" ] && command -v wl-paste >/dev/null 2>&1; then
    echo wayland
  elif command -v xclip >/dev/null 2>&1; then
    echo xclip
  elif command -v xsel >/dev/null 2>&1; then
    echo xsel
  else
    echo none
  fi
}

read_clipboard() {
  local out="$1" tool
  tool=$(clip_tool)
  case "$tool" in
    wayland) wl-paste --no-newline > "$out" 2>/dev/null || : ;;
    xclip) xclip -selection clipboard -o > "$out" 2>/dev/null || : ;;
    xsel) xsel --clipboard --output > "$out" 2>/dev/null || : ;;
    *) : > "$out" ;;
  esac
}

write_clipboard() {
  local in="$1" tool
  tool=$(clip_tool)
  case "$tool" in
    wayland) wl-copy --type text/plain < "$in" ;;
    xclip) xclip -selection clipboard -i < "$in" ;;
    xsel) xsel --clipboard --input < "$in" ;;
    *) return 1 ;;
  esac
}

hash_file() {
  sha256sum "$1" | awk '{print $1}'
}

post_text_file() {
  local file="$1"
  jq -Rs --arg source "$(hostname)" '{text:.,source:$source}' < "$file" |
    curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
      -H 'Content-Type: application/json' --data-binary @- \
      "$HUB/api/clipboard" >/dev/null
}

remote_updated_at() {
  curl -sS -H "Authorization: Bearer $TOKEN" "$HUB/api/clipboard" |
    jq -r '.updatedAt // 0' 2>/dev/null || echo 0
}

send_once() {
  select_hub
  local tmp
  tmp=$(mktemp) || fail "Cannot create a temporary file."
  read_clipboard "$tmp"
  if [ ! -s "$tmp" ]; then
    rm -f "$tmp"
    fail "The local clipboard is empty."
  fi
  post_text_file "$tmp" || { rm -f "$tmp"; fail "Cannot reach $HUB"; }
  rm -f "$tmp"
  notify "Clipboard sent to the hub"
}

copy_once() {
  select_hub
  local tmp response
  tmp=$(mktemp) || fail "Cannot create a temporary file."
  response=$(curl -sS -H "Authorization: Bearer $TOKEN" "$HUB/api/clipboard") ||
    { rm -f "$tmp"; fail "Cannot reach $HUB"; }
  jq -jr '.text // ""' <<<"$response" > "$tmp"
  if [ ! -s "$tmp" ]; then
    rm -f "$tmp"
    fail "The hub clipboard is empty."
  fi
  write_clipboard "$tmp" || { rm -f "$tmp"; fail "No supported clipboard tool (install wl-clipboard or xclip)."; }
  rm -f "$tmp"
  notify "Copied the hub clipboard"
}

status_once() {
  select_hub
  curl -sS -H "Authorization: Bearer $TOKEN" "$HUB/api/clipboard" |
    jq '{source,updatedAt,text}'
}

watch_loop() {
  select_hub
  [ "$(clip_tool)" != "none" ] ||
    fail "No supported clipboard tool. Install wl-clipboard (Wayland) or xclip (X11)."

  local local_file remote_file local_hash last_local_hash last_remote_hash
  local remote_at last_remote_at remote_text_hash
  local_file=$(mktemp) || fail "Cannot create a temporary file."
  remote_file=$(mktemp) || fail "Cannot create a temporary file."
  trap 'rm -f "$local_file" "$remote_file"' EXIT

  last_local_hash=""
  last_remote_hash=""
  last_remote_at=""

  # Prime the remote timestamp without overwriting the local clipboard at
  # startup. Use --copy for an explicit one-time pull.
  last_remote_at=$(remote_updated_at)

  notify "Clipboard sync started"
  echo "GeneralShare clipboard sync: $HUB (Ctrl+C to stop)"

  while :; do
    read_clipboard "$local_file"
    local_hash=$(hash_file "$local_file")

    if [ -s "$local_file" ] && [ "$local_hash" != "$last_local_hash" ] && [ "$local_hash" != "$last_remote_hash" ]; then
      if post_text_file "$local_file"; then
        echo "$(date +%H:%M:%S) sent $(wc -c < "$local_file") bytes"
      fi
    fi
    last_local_hash="$local_hash"

    if curl -sS -H "Authorization: Bearer $TOKEN" "$HUB/api/clipboard" > "$remote_file" 2>/dev/null; then
      remote_at=$(jq -r '.updatedAt // 0' "$remote_file" 2>/dev/null || echo 0)
      if [ "$remote_at" != "$last_remote_at" ]; then
        last_remote_at="$remote_at"
        jq -jr '.text // ""' "$remote_file" > "$remote_file.text"
        remote_text_hash=$(hash_file "$remote_file.text")
        if [ "$remote_text_hash" != "$local_hash" ] && [ -s "$remote_file.text" ]; then
          if write_clipboard "$remote_file.text"; then
            last_remote_hash="$remote_text_hash"
            last_local_hash="$remote_text_hash"
            echo "$(date +%H:%M:%S) received $(wc -c < "$remote_file.text") bytes"
          fi
        fi
        rm -f "$remote_file.text"
      fi
    fi

    sleep "$INTERVAL"
  done
}

install_autostart() {
  local bin="$0" dir
  [[ "$bin" != /* ]] && bin="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
  dir="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
  mkdir -p "$dir"
  cat > "$dir/generalshare-clipboard.desktop" <<EOF
[Desktop Entry]
Type=Application
Name=GeneralShare clipboard sync
Comment=Two-way clipboard sync on the local network
Exec="$bin" --watch
Terminal=false
X-GNOME-Autostart-enabled=true
EOF
  echo "Installed $dir/generalshare-clipboard.desktop"
  echo "Log out/in, or start it now with: $bin --watch"
}

case "$MODE" in
  send) send_once ;;
  copy) copy_once ;;
  status) status_once ;;
  watch) watch_loop ;;
  install-autostart) install_autostart ;;
  *) usage; exit 2 ;;
esac
