# Colemak-DH EC Keyboard Remap for Framework Laptop
#
# TROUBLESHOOTING NOTES (for future agents):
# ==========================================
# This module applies keyboard remaps via the Framework EC (Embedded Controller).
# EC remaps are VOLATILE - lost after ~10 seconds unplugged (cold boot).
#
# If remap doesn't work at LUKS prompt after cold boot:
#
# 1. Check if service ran:
#    journalctl -b -u colemak-ec-remap
#
# 2. Enable verbose boot for debugging:
#    boot.initrd.verbose = lib.mkForce true;
#    boot.kernelParams = lib.mkForce [ "boot.shell_on_fail" ];
#
# 3. Verify EC modules loaded in initrd:
#    lsinitrd /boot/initrd | grep cros_ec
#
# 4. Check service ordering - must run BEFORE cryptsetup.target
#
# 5. If systemd initrd approach fails entirely, fallback options:
#    - FrameworkHacksPkg EFI driver: https://github.com/DHowett/FrameworkHacksPkg
#    - This runs before OS starts, survives cold boots
#
# References:
# - https://www.howett.net/posts/2021-12-framework-ec/
# - https://deck.sh/using-the-ec-to-change-the-keyboard-layout-on-the-framework-laptop/
# - https://wiki.archlinux.org/title/Framework_Laptop_13

{ config, pkgs, lib, ... }:

let
  # All ectool remap commands
  remapCommands = ectoolPath: ''
    # Colemak-DH layout via EC for Framework 12
    # Matrix positions verified via FRAME12_KEY_MATRIX.md scan

    # Top row (QWERTY → Colemak-DH)
    ${ectoolPath} raw 0x3E0C d1,d1,b7,b8,w2b   # e(7,8) → f
    ${ectoolPath} raw 0x3E0C d1,d1,b7,b9,w4d   # r(7,9) → p
    ${ectoolPath} raw 0x3E0C d1,d1,b2,b3,w32   # t(2,3) → b
    ${ectoolPath} raw 0x3E0C d1,d1,b2,b6,w3b   # y(2,6) → j
    ${ectoolPath} raw 0x3E0C d1,d1,b7,b1,w4b   # u(7,1) → l
    ${ectoolPath} raw 0x3E0C d1,d1,b7,b2,w3c   # i(7,2) → u
    ${ectoolPath} raw 0x3E0C d1,d1,b7,b3,w35   # o(7,3) → y
    ${ectoolPath} raw 0x3E0C d1,d1,b7,b4,w4c   # p(7,4) → ;

    # Home row (QWERTY → Colemak-DH)
    ${ectoolPath} raw 0x3E0C d1,d1,b3,b4,w2d   # s(3,4) → r
    ${ectoolPath} raw 0x3E0C d1,d1,b4,b2,w1b   # d(4,2) → s
    ${ectoolPath} raw 0x3E0C d1,d1,b4,b3,w2c   # f(4,3) → t
    ${ectoolPath} raw 0x3E0C d1,d1,b1,b6,w3a   # h(1,6) → m
    ${ectoolPath} raw 0x3E0C d1,d1,b4,b6,w31   # j(4,6) → n
    ${ectoolPath} raw 0x3E0C d1,d1,b4,b5,w24   # k(4,5) → e
    ${ectoolPath} raw 0x3E0C d1,d1,b4,b9,w43   # l(4,9) → i
    ${ectoolPath} raw 0x3E0C d1,d1,b4,b8,w44   # ;(4,8) → o

    # Bottom row with angle mod (QWERTY → Colemak-DH)
    ${ectoolPath} raw 0x3E0C d1,d1,b6,b1,w22   # z(6,1) → x
    ${ectoolPath} raw 0x3E0C d1,d1,b5,b8,w21   # x(5,8) → c
    ${ectoolPath} raw 0x3E0C d1,d1,b5,b5,w23   # c(5,5) → d
    ${ectoolPath} raw 0x3E0C d1,d1,b0,b3,w1a   # b(0,3) → z
    ${ectoolPath} raw 0x3E0C d1,d1,b0,b5,w42   # n(0,5) → k
    ${ectoolPath} raw 0x3E0C d1,d1,b5,bb,w33   # m(5,b) → h
  '';

  # Smart remap: check first, only apply if needed (for systemd service)
  # Reads position (7,8) - if scancode is 0x2b (f), remap is already applied
  smartRemapScript = ectoolPath: ''
    # Read current scancode at e-key position (7,8)
    # Response has scancode at bytes 10-11; we check byte 10 for 0x2b (f)
    CURRENT=$(${ectoolPath} raw 0x3E0C d1,d0,b7,b8,w0 2>/dev/null | grep '|' | head -1 | awk '{print $11}')
    if [ "$CURRENT" = "2b" ]; then
      echo "Colemak-DH remap already active, skipping."
      exit 0
    fi

    echo "Applying Colemak-DH EC remap..."
    ${remapCommands ectoolPath}
    echo "Colemak-DH remap applied."
  '';

  # Script with retry logic for initrd
  initrdRemapScript = pkgs.writeShellScript "colemak-ec-initrd-remap" ''
    set -e
    ECTOOL="${pkgs.fw-ectool}/bin/ectool"

    # Wait for EC driver to be ready (up to 3 seconds)
    echo "Waiting for EC driver..."
    for i in $(seq 1 30); do
      if $ECTOOL version >/dev/null 2>&1; then
        echo "EC driver ready after $((i * 100))ms"
        break
      fi
      sleep 0.1
    done

    # Verify EC communication works
    if ! $ECTOOL version >/dev/null 2>&1; then
      echo "ERROR: Cannot communicate with EC after 3 seconds"
      exit 1
    fi

    echo "Applying Colemak-DH EC remap..."
    ${remapCommands "$ECTOOL"}
    echo "Colemak-DH remap applied successfully"
  '';
in
{
  options.colemakEc.enable = lib.mkEnableOption "Colemak-DH EC keyboard remap";

  config = lib.mkIf config.colemakEc.enable {
    # Require systemd initrd (set in default.nix for Plymouth compatibility)
    assertions = [{
      assertion = config.boot.initrd.systemd.enable;
      message = "colemakEc requires boot.initrd.systemd.enable = true";
    }];

    # ===========================================
    # INITRD: Apply remap BEFORE LUKS decryption
    # ===========================================

    # Add kernel modules for EC communication
    boot.initrd.availableKernelModules = [ "cros_ec" "cros_ec_lpcs" ];

    # Add fw-ectool and the remap script to initrd
    boot.initrd.systemd.storePaths = [ pkgs.fw-ectool initrdRemapScript ];

    # Systemd service that runs before cryptsetup
    # Key: wantedBy sysinit.target + requiredBy on cryptsetup units ensures
    # this service runs before LUKS password prompt
    boot.initrd.systemd.services.colemak-ec-remap = {
      description = "Apply Colemak-DH keyboard remap to EC";
      wantedBy = [ "sysinit.target" ];
      before = [ "cryptsetup.target" "systemd-ask-password-console.service" ];
      after = [ "systemd-modules-load.service" ];
      wants = [ "systemd-modules-load.service" ];

      unitConfig = {
        DefaultDependencies = false;
      };

      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = initrdRemapScript;
        StandardOutput = "journal+console";
        StandardError = "journal+console";
      };
    };

    # Make cryptsetup units wait for our remap service
    # This adds After= and Wants= dependencies to each LUKS device's cryptsetup unit
    boot.initrd.systemd.services."systemd-cryptsetup@" = {
      after = [ "colemak-ec-remap.service" ];
      wants = [ "colemak-ec-remap.service" ];
    };

    # ===========================================
    # SYSTEMD: Apply remap after boot (backup)
    # ===========================================
    # This ensures the remap is applied even if the EC state was reset,
    # and provides a service that can be restarted manually if needed.

    systemd.services.colemak-ec-remap = {
      description = "Apply Colemak-DH remap to EC keyboard";
      after = [ "multi-user.target" ];
      wantedBy = [ "multi-user.target" ];

      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = "${pkgs.writeShellScript "colemak-dh-remap" ''
          ${smartRemapScript "${pkgs.fw-ectool}/bin/ectool"}
        ''}";
      };
    };

    # Also run after resume from suspend/hibernate
    powerManagement.resumeCommands = ''
      systemctl start colemak-ec-remap
    '';
  };
}
