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

readonly BRIDGE_USER="${JWEBOT_RUNTIME_USER:-jwebot}"
BRIDGE_HOME="${JWEBOT_RUNTIME_HOME:-$(getent passwd "${BRIDGE_USER}" | cut -d: -f6)}"
readonly BRIDGE_HOME
BRIDGE_UID=$(id -u "${BRIDGE_USER}")
readonly BRIDGE_UID
readonly DISPLAY_NAME=":1"
readonly XAUTHORITY_FILE="${BRIDGE_HOME}/.Xauthority"
readonly RUNTIME_DIR="/run/user/${BRIDGE_UID}"
readonly STATE_DIR="${BRIDGE_HOME}/.local/state/wechat-gui-bridge"
readonly PENDING_TEXT="${STATE_DIR}/pending.txt"
readonly PENDING_TOKEN="${STATE_DIR}/pending.token"
readonly PENDING_TTL=120
readonly EXPECTED_WIDTH=1440
readonly EXPECTED_HEIGHT=873
readonly DRAFT_CLICK_X=900
readonly DRAFT_CLICK_Y=780
readonly SEND_CLICK_X=1390
readonly SEND_CLICK_Y=842

require_root() {
  if [[ ${EUID} -ne 0 ]]; then
    printf 'error: run as root\n' >&2
    exit 1
  fi
}

as_bridge_user() {
  /usr/sbin/runuser -u "${BRIDGE_USER}" -- env \
    DISPLAY="${DISPLAY_NAME}" \
    XAUTHORITY="${XAUTHORITY_FILE}" \
    XDG_RUNTIME_DIR="${RUNTIME_DIR}" \
    DBUS_SESSION_BUS_ADDRESS="unix:path=${RUNTIME_DIR}/bus" \
    "$@"
}

ensure_state_dir() {
  install -d -o "${BRIDGE_USER}" -g "${BRIDGE_USER}" -m 700 "${STATE_DIR}"
}

window_id() {
  local wid name
  while IFS= read -r wid; do
    [[ -n ${wid} ]] || continue
    name=$(as_bridge_user xdotool getwindowname "${wid}" 2>/dev/null || true)
    if [[ ${name} == "Weixin" ]]; then
      printf '%s\n' "${wid}"
      return 0
    fi
  done < <(as_bridge_user xdotool search --onlyvisible --class wechat || true)
  printf 'error: visible main WeChat window not found\n' >&2
  return 1
}

window_geometry() {
  local wid=$1
  as_bridge_user xdotool getwindowgeometry --shell "${wid}"
}

ensure_maximized_window() {
  local wid=$1 geometry width height
  geometry=$(window_geometry "${wid}")
  eval "${geometry}"
  width=${WIDTH:?}
  height=${HEIGHT:?}
  if (( width == EXPECTED_WIDTH && height == EXPECTED_HEIGHT )); then
    return
  fi
  as_bridge_user wmctrl -ir "${wid}" -b add,maximized_vert,maximized_horz
  sleep 0.25
  geometry=$(window_geometry "${wid}")
  eval "${geometry}"
  width=${WIDTH:?}
  height=${HEIGHT:?}
  if (( width != EXPECTED_WIDTH || height != EXPECTED_HEIGHT )); then
    printf 'error: WeChat maximize failed; expected %sx%s, got %sx%s\n' \
      "${EXPECTED_WIDTH}" "${EXPECTED_HEIGHT}" "${width}" "${height}" >&2
    return 1
  fi
}

capture_window() {
  local destination=${1:-"${STATE_DIR}/latest.png"}
  local wid
  wid=$(window_id)
  ensure_maximized_window "${wid}"
  as_bridge_user scrot --overwrite --window "${wid}" "${destination}"
  chown "${BRIDGE_USER}:${BRIDGE_USER}" "${destination}"
  chmod 600 "${destination}"
  printf '%s\n' "${destination}"
}

status_command() {
  local wid name geometry
  wid=$(window_id)
  ensure_maximized_window "${wid}"
  name=$(as_bridge_user xdotool getwindowname "${wid}")
  geometry=$(window_geometry "${wid}" | tr '\n' ' ')
  printf '{"ok":true,"display":"%s","window_id":"%s","window_name":"%s","geometry":"%s","wechat_pid":"%s"}\n' \
    "${DISPLAY_NAME}" "${wid}" "${name//\"/}" "${geometry//\"/}" "$(pgrep -u "${BRIDGE_USER}" -x wechat | head -n 1)"
}

ocr_command() {
  ensure_state_dir
  local image="${STATE_DIR}/latest.png"
  local output="${STATE_DIR}/latest-ocr"
  capture_window "${image}" >/dev/null
  as_bridge_user tesseract "${image}" "${output}" -l chi_sim+eng --psm 11 >/dev/null 2>&1
  local lines chars
  lines=$(grep -cve '^[[:space:]]*$' "${output}.txt" || true)
  chars=$(wc -m < "${output}.txt")
  printf '{"ok":true,"image":"%s","text":"%s","lines":%s,"characters":%s}\n' \
    "${image}" "${output}.txt" "${lines}" "${chars}"
}

ocr_text_command() {
  ocr_command >/dev/null
  cat "${STATE_DIR}/latest-ocr.txt"
}

draft_command() {
  ensure_state_dir
  local text=${1:-}
  if [[ -z ${text} ]]; then
    printf 'error: draft text is empty\n' >&2
    exit 2
  fi
  if (( ${#text} > 1000 )); then
    printf 'error: draft exceeds 1000 characters\n' >&2
    exit 2
  fi

  printf '%s' "${text}" > "${PENDING_TEXT}"
  chown "${BRIDGE_USER}:${BRIDGE_USER}" "${PENDING_TEXT}"
  chmod 600 "${PENDING_TEXT}"

  local token wid
  token=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')
  printf '%s\n' "${token}" > "${PENDING_TOKEN}"
  chown "${BRIDGE_USER}:${BRIDGE_USER}" "${PENDING_TOKEN}"
  chmod 600 "${PENDING_TOKEN}"

  wid=$(window_id)
  ensure_maximized_window "${wid}"

  # xclip backgrounds itself and otherwise keeps a service caller's captured
  # stdout/stderr pipes open, making a completed draft look like a timeout.
  as_bridge_user xclip -selection clipboard -in < "${PENDING_TEXT}" >/dev/null 2>&1
  as_bridge_user xdotool windowactivate --sync "${wid}"
  as_bridge_user xdotool mousemove --window "${wid}" \
    "${DRAFT_CLICK_X}" "${DRAFT_CLICK_Y}" click 1
  as_bridge_user xdotool key --clearmodifiers ctrl+a BackSpace ctrl+v
  printf '{"ok":true,"state":"drafted","confirmation_token":"%s","expires_in_seconds":%s}\n' \
    "${token}" "${PENDING_TTL}"
}

send_command() {
  local supplied=${1:-}
  if [[ -z ${supplied} || ! -s ${PENDING_TOKEN} || ! -s ${PENDING_TEXT} ]]; then
    printf 'error: no armed draft\n' >&2
    exit 3
  fi

  local expected created now age
  expected=$(tr -d '\r\n' < "${PENDING_TOKEN}")
  created=$(stat -c %Y "${PENDING_TOKEN}")
  now=$(date +%s)
  age=$((now - created))
  if [[ ${supplied} != "${expected}" ]]; then
    printf 'error: confirmation token mismatch\n' >&2
    exit 3
  fi
  if (( age < 0 || age > PENDING_TTL )); then
    printf 'error: confirmation token expired\n' >&2
    exit 3
  fi

  local wid
  wid=$(window_id)
  ensure_maximized_window "${wid}"

  as_bridge_user xdotool windowactivate --sync "${wid}"
  as_bridge_user xdotool mousemove --window "${wid}" \
    "${SEND_CLICK_X}" "${SEND_CLICK_Y}" click 1
  rm -f -- "${PENDING_TOKEN}" "${PENDING_TEXT}"
  printf '{"ok":true,"state":"sent"}\n'
}

clear_command() {
  local wid
  wid=$(window_id)
  ensure_maximized_window "${wid}"
  as_bridge_user xdotool windowactivate --sync "${wid}"
  as_bridge_user xdotool mousemove --window "${wid}" \
    "${DRAFT_CLICK_X}" "${DRAFT_CLICK_Y}" click 1
  as_bridge_user xdotool key --clearmodifiers ctrl+a BackSpace
  rm -f -- "${PENDING_TOKEN}" "${PENDING_TEXT}"
  printf '{"ok":true,"state":"cleared"}\n'
}

usage() {
  printf 'usage: %s {status|capture|ocr|ocr-text|draft TEXT|send TOKEN|clear}\n' "$0" >&2
  exit 64
}

require_root
case ${1:-} in
  status) status_command ;;
  capture) ensure_state_dir; capture_window ;;
  ocr) ocr_command ;;
  ocr-text) ocr_text_command ;;
  draft) shift; draft_command "$*" ;;
  send) shift; send_command "${1:-}" ;;
  clear) clear_command ;;
  *) usage ;;
esac
