# Host-only: ideapad tablet ergonomics — touchscreen calibration, IIO sensors, virtual keyboard,
# touch-controller resume fix, and per-session helper daemons (tablet-mode toggle + auto-rotation
# via iio-sensor-proxy) for both Niri and Hyprland. Lives at the NixOS layer because the hardware
# bits are system-wide; the per-compositor autostart hooks are gated on `chiasson.desktop.<wm>.enable`
# so they stay dormant if you pick the other session at the greeter.
#
# Hyprland uses CW transforms via `hyprctl`; Niri uses CCW transforms via `niri msg output`.
{
  config,
  lib,
  pkgs,
  ...
}:
let
  # ─────────────────────── Hyprland helpers ───────────────────────
  ideapadTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-tablet-mode-daemon" ''
    #!/usr/bin/env bash
    set -euo pipefail

    HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
    JQ="${pkgs.jq}/bin/jq"
    PKILL="${pkgs.procps}/bin/pkill"
    PGREP="${pkgs.procps}/bin/pgrep"
    WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"

    monitor_name="DSI-1"
    keyboard_scale="1.25"
    tablet_scale="1.6"
    state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"

    current_transform() {
      "$HYPRCTL" -j monitors 2>/dev/null | "$JQ" -r --arg mon "$monitor_name" '
        ([.[] | select(.name == $mon)][0].transform // 1 | tostring)
      ' 2>/dev/null || echo "1"
    }

    has_attached_pogo_dock() {
      "$HYPRCTL" -j devices 2>/dev/null | "$JQ" -e '
        any((([.keyboards[]?.name] + [.mice[]?.name])[]?); (ascii_downcase | test("google-inc\\.-hammer")))
      ' >/dev/null
    }

    # wvkbd is no longer auto-spawned at session start — this daemon owns its lifecycle so we
    # only have a virtual keyboard surface in memory when the pogo cover is detached.
    start_wvkbd() {
      "$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
      "$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
    }
    stop_wvkbd() {
      "$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
    }

    apply_mode() {
      mode="$1"
      transform="$(current_transform)"
      if [ "$mode" = "tablet" ]; then
        "$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$tablet_scale, transform, $transform" >/dev/null 2>&1 || true
        start_wvkbd
      else
        "$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$keyboard_scale, transform, $transform" >/dev/null 2>&1 || true
        stop_wvkbd
      fi
      "$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
      "$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
      printf "%s\n" "$mode" > "$state_file"
    }

    # Always reapply on startup — the cached state file may lie (e.g., session restart while
    # pogo state changed) and wvkbd needs to be (re)spawned since nothing else launches it now.
    previous_mode=""

    while true; do
      if has_attached_pogo_dock; then
        mode="keyboard"
      else
        mode="tablet"
      fi

      if [ "$mode" != "$previous_mode" ]; then
        apply_mode "$mode"
        previous_mode="$mode"
      fi
      sleep 2
    done
  '';

  ideapadAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-autorotate-daemon" ''
    #!/usr/bin/env bash
    set -euo pipefail

    HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
    MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"

    monitor_name="DSI-1"
    keyboard_scale="1.25"
    tablet_scale="1.6"
    state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"

    scale_for_mode() {
      mode="$(cat "$state_file" 2>/dev/null || echo keyboard)"
      if [ "$mode" = "tablet" ]; then
        printf "%s\n" "$tablet_scale"
      else
        printf "%s\n" "$keyboard_scale"
      fi
    }

    transform_for_orientation() {
      orientation="$1"
      case "$orientation" in
        normal) printf "0\n" ;;
        bottom-up) printf "2\n" ;;
        left-up) printf "1\n" ;;
        right-up) printf "3\n" ;;
        *) return 1 ;;
      esac
    }

    apply_orientation() {
      orientation="$1"
      transform="$(transform_for_orientation "$orientation")" || return 0
      scale="$(scale_for_mode)"
      "$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$scale, transform, $transform" >/dev/null 2>&1 || true
      "$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
      "$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
    }

    while true; do
      "$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
        case "$line" in
          *normal*) apply_orientation "normal" ;;
          *bottom-up*) apply_orientation "bottom-up" ;;
          *left-up*) apply_orientation "left-up" ;;
          *right-up*) apply_orientation "right-up" ;;
        esac
      done
      sleep 2
    done
  '';

  # ─────────────────────── Niri helpers ───────────────────────
  # `niri msg output DSI-1 transform <T>` accepts: normal | 90 | 180 | 270.
  #
  # Empirical mapping for this exact device + ACCEL_MOUNT_MATRIX (lenovo-wormdingler family):
  #   physical position    iio orientation    correct niri transform
  #   ──────────────────   ────────────────   ──────────────────────
  #   keyboard down (LS)   left-up            normal
  #   keyboard up   (LS)   right-up           180
  #   keyboard right (P)   bottom-up          90
  #   keyboard left  (P)   normal             270
  # iio's "normal" is *not* the natural landscape pose here — the panel-mount matrix in the
  # mobile-nixos device file biases it toward portrait-with-keyboard-left. Don't trust the
  # textbook iio→Wayland convention; trust the table above.
  ideapadNiriTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-niri-tablet-mode-daemon" ''
    #!/usr/bin/env bash
    set -euo pipefail

    NIRI="${pkgs.niri}/bin/niri"
    JQ="${pkgs.jq}/bin/jq"
    PKILL="${pkgs.procps}/bin/pkill"
    PGREP="${pkgs.procps}/bin/pgrep"
    WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"

    output_name="DSI-1"
    keyboard_scale="1.25"
    tablet_scale="1.6"
    state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"

    has_attached_pogo_dock() {
      # Niri doesn't expose input devices via IPC, so we look in `/dev/input/by-id` for the
      # USB-class enumeration of the Google Hammer / Whiskers pogo keyboard. The directory
      # contains symlinks; presence of either string means the cover is attached.
      # NOTE: `find` ships in findutils, not coreutils — using the wrong package here makes
      # the pipeline fail silently (stderr is dropped) and pogo always reads as detached.
      ${pkgs.findutils}/bin/find /dev/input/by-id -maxdepth 1 -type l 2>/dev/null \
        | ${pkgs.gnugrep}/bin/grep -i -E 'google.*hammer|google.*whiskers' >/dev/null
    }

    # wvkbd is owned by this daemon: spawn on pogo detach, kill on pogo attach. The DMS bar
    # `pkill -SIGRTMIN -x wvkbd-mobintl` toggle still works while wvkbd is running (i.e., in
    # tablet mode). When the pogo cover is on, the pill is a no-op — that's intentional, the
    # physical keyboard is the input.
    start_wvkbd() {
      "$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
      "$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
    }
    stop_wvkbd() {
      "$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
    }

    apply_mode() {
      mode="$1"
      if [ "$mode" = "tablet" ]; then
        "$NIRI" msg output "$output_name" scale "$tablet_scale" >/dev/null 2>&1 || true
        start_wvkbd
      else
        "$NIRI" msg output "$output_name" scale "$keyboard_scale" >/dev/null 2>&1 || true
        stop_wvkbd
      fi
      printf "%s\n" "$mode" > "$state_file"
    }

    # Always reapply on startup — see Hyprland daemon's identical comment.
    previous_mode=""

    while true; do
      if has_attached_pogo_dock; then
        mode="keyboard"
      else
        mode="tablet"
      fi

      if [ "$mode" != "$previous_mode" ]; then
        apply_mode "$mode"
        previous_mode="$mode"
      fi
      sleep 2
    done
  '';

  ideapadNiriAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-niri-autorotate-daemon" ''
    #!/usr/bin/env bash
    set -euo pipefail

    NIRI="${pkgs.niri}/bin/niri"
    MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"

    output_name="DSI-1"

    transform_for_orientation() {
      orientation="$1"
      case "$orientation" in
        normal) printf "270\n" ;;
        bottom-up) printf "90\n" ;;
        left-up) printf "normal\n" ;;
        right-up) printf "180\n" ;;
        *) return 1 ;;
      esac
    }

    apply_orientation() {
      orientation="$1"
      transform="$(transform_for_orientation "$orientation")" || return 0
      "$NIRI" msg output "$output_name" transform "$transform" >/dev/null 2>&1 || true
    }

    while true; do
      "$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
        case "$line" in
          *normal*) apply_orientation "normal" ;;
          *bottom-up*) apply_orientation "bottom-up" ;;
          *left-up*) apply_orientation "left-up" ;;
          *right-up*) apply_orientation "right-up" ;;
        esac
      done
      sleep 2
    done
  '';
in
{
  # ─────────────────────── Hardware ───────────────────────
  hardware.sensor.iio.enable = true;

  # Touchscreen calibration — identity matrix is correct: hardware coordinates are already aligned
  # with the panel-native frame, and per-orientation rotation is handled by `niri msg output`,
  # not by re-tuning this matrix. Rotate the *output*, never this matrix.
  services.udev.extraRules = ''
    SUBSYSTEM=="input", ENV{ID_INPUT_TOUCHSCREEN}=="1", ENV{LIBINPUT_CALIBRATION_MATRIX}="1 0 0 0 1 0"
  '';

  # ─────────────────────── Touch controller resume fix ───────────────────────
  # The hid-over-i2c touch controller at i2c bus 4-0001 wedges across S3 suspend: after resume it
  # re-enumerates with the correct capabilities but reports zero events on touch. Cycling the
  # `i2c_hid_of` driver (unbind + bind) un-wedges it. systemd-sleep runs every executable in
  # `/etc/systemd/system-sleep/` with `$1 = pre|post`; we only act on `post`. Driver name is
  # discovered at runtime so a future kernel rename to `i2c_hid` doesn't break this.
  environment.etc."systemd/system-sleep/ideapad-touch-rebind".source =
    pkgs.writeShellScript "ideapad-touch-rebind" ''
      set -eu
      [ "$1" = post ] || exit 0
      dev=4-0001
      dev_dir=/sys/bus/i2c/devices/$dev
      [ -L "$dev_dir/driver" ] || exit 0
      drv=$(${pkgs.coreutils}/bin/basename "$(${pkgs.coreutils}/bin/readlink "$dev_dir/driver")")
      echo "ideapad-touch-rebind: cycling $drv for $dev" >&2
      echo "$dev" > "/sys/bus/i2c/drivers/$drv/unbind" || true
      ${pkgs.coreutils}/bin/sleep 0.3
      echo "$dev" > "/sys/bus/i2c/drivers/$drv/bind"
    '';

  # ─────────────────────── User-facing tools ───────────────────────
  # System-wide so any user session (Niri or Hyprland) can launch wvkbd / hyprctl / niri-msg helpers.
  environment.systemPackages = [
    pkgs.wvkbd
    pkgs.iio-sensor-proxy
    ideapadTabletModeDaemon
    ideapadAutoRotateDaemon
    ideapadNiriTabletModeDaemon
    ideapadNiriAutoRotateDaemon
  ];

  # ─────────────────────── Niri session autostart ───────────────────────
  # Set on the NixOS layer; the `desktopNiri` HM module merges this into per-user `niri/config.kdl`.
  # Touch input is glued to DSI-1 so it follows whatever transform the autorotate daemon sets.
  # wvkbd is intentionally NOT spawned here — `ideapad-niri-tablet-mode-daemon` starts/kills it
  # in response to pogo-cover attach/detach so we don't keep a virtual-keyboard surface alive
  # while a physical keyboard is plugged in.
  chiasson.desktop.niri.extraSettings = lib.mkIf config.chiasson.desktop.niri.enable {
    input.touch.map-to-output = "DSI-1";

    # Required for logind's `HandlePowerKey` in `_private/platform.nix` to take effect: otherwise
    # niri grabs a `block` inhibitor on `handle-power-key` and suspends via D-Bus, including on
    # the EC's wake-from-suspend KEY_POWER event → instant re-suspend loop.
    # https://github.com/niri-wm/niri/issues/2233
    input."disable-power-key-handling" = _: { };

    # wrapper-modules schema: each entry is a `command argv` list of strings (or a single string).
    spawn-at-startup = [
      [ "ideapad-niri-autorotate-daemon" ]
      [ "ideapad-niri-tablet-mode-daemon" ]
    ];
  };

  # ─────────────────────── Hyprland session autostart ───────────────────────
  # Same lifecycle policy as Niri: the tablet-mode daemon owns wvkbd, no exec-once for it.
  chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable {
    exec-once = lib.mkAfter [
      "${pkgs.procps}/bin/pkill -x ideapad-tablet-mode-daemon >/dev/null 2>&1 || true; ideapad-tablet-mode-daemon &"
      "${pkgs.procps}/bin/pkill -x ideapad-autorotate-daemon >/dev/null 2>&1 || true; ideapad-autorotate-daemon &"
    ];
    bind = lib.mkAfter [
      "Super, K, exec, pkill -SIGRTMIN -x wvkbd-mobintl"
    ];
    monitor = lib.mkAfter [
      "DSI-1,1200x2000@60.0,0x0,1.25, transform, 1"
    ];
  };
}
