# Hardware enablement beyond nixos-hardware.
#
# The nixos-hardware "common-*" profiles the installer selects cover the
# BASICS (microcode, the Intel/AMD VA-API media stack, weekly fstrim), and
# power.nix adds thermald + power-profiles-daemon. This module fills the GAP
# above those: broadly-beneficial bits that default ON when the installer
# detects the vendor (opt-OUT), and heavier/experimental bits behind opt-IN
# toggles. The installer's hardware-db.sh probes what's present and writes the
# matching nomarchy.hardware.* into the generated system.nix.
#
# Audited against the commons so we don't double-set: we add GuC/HuC, the
# amd-pstate governor, the AMD VA-API env, GPU-compute runtimes, fprintd, and
# the NPU driver — none of which the commons turn on.
{ config, lib, pkgs, ... }:

let
  cfg = config.nomarchy.hardware;
  # Fingerprint PAM can follow theme-state.json (menu toggle → next
  # sys-rebuild), same bridge as autoTimezone (BACKLOG #55). Missing or
  # invalid JSON fails closed (theme-state-read.nix) instead of a raw stack.
  hwState =
    if config.nomarchy.system.stateFile != null
    then import ../theme-state-read.nix { inherit lib; } config.nomarchy.system.stateFile
    else { };
  pamFromState = (hwState.settings or { }).fingerprint.pam or false;
in
{
  options.nomarchy.hardware = {
    intel = {
      enable = lib.mkEnableOption ''
        Intel CPU/GPU enablement (the installer turns this on when it detects
        an Intel CPU or GPU). Complements nixos-hardware's common-gpu-intel'';

      guc = lib.mkOption {
        type = lib.types.bool;
        default = cfg.intel.enable;
        defaultText = lib.literalExpression "config.nomarchy.hardware.intel.enable";
        description = ''
          Load the GPU's GuC/HuC firmware via the i915 param
          (i915.enable_guc=3) — better power management and HuC-accelerated
          media. On by default with intel.enable. NOTE: this is the *i915*
          driver's param; the newer `xe` driver (Lunar Lake / Battlemage /
          Panther Lake and other recent Xe GPUs) enables GuC by default and
          ignores it, so the installer turns this off on xe-driver hardware.
        '';
      };

      computeRuntime = lib.mkEnableOption ''
        Intel GPU compute: the OpenCL / Level Zero (intel-compute-runtime) and
        oneVPL (vpl-gpu-rt) runtimes for GPU compute and transcode. Opt-in (a
        few hundred MB) — the Intel counterpart to AMD ROCm'';
    };

    amd = {
      enable = lib.mkEnableOption ''
        AMD CPU/GPU enablement (installer-set on an AMD CPU or GPU).
        Complements nixos-hardware's common-cpu-amd / common-gpu-amd'';

      pstate = lib.mkOption {
        type = lib.types.bool;
        default = cfg.amd.enable;
        defaultText = lib.literalExpression "config.nomarchy.hardware.amd.enable";
        description = ''
          Use the amd-pstate EPP driver (amd_pstate=active) — the modern Zen
          power/perf governor that power-profiles-daemon drives per profile.
          On by default with amd.enable (broadly beneficial on Zen 2+).
        '';
      };

      vaapi = lib.mkOption {
        type = lib.types.bool;
        default = cfg.amd.enable;
        defaultText = lib.literalExpression "config.nomarchy.hardware.amd.enable";
        description = ''
          Point VA-API at mesa's radeonsi (LIBVA_DRIVER_NAME=radeonsi) for
          hardware video decode/encode. On by default with amd.enable.
        '';
      };

      rocm = {
        enable = lib.mkEnableOption ''
          AMD ROCm: the HIP / OpenCL GPU-compute stack (multi-GB closure).
          Opt-in — unlocks GPU PyTorch / Ollama on Radeon'';

        gfxOverride = lib.mkOption {
          type = lib.types.str;
          default = "";
          example = "11.0.0";
          description = ''
            HSA_OVERRIDE_GFX_VERSION for GPUs ROCm doesn't officially list
            (e.g. an RDNA3 780M iGPU, gfx1103, needs "11.0.0"). Empty = no
            override.
          '';
        };
      };
    };

    fingerprint = {
      enable = lib.mkEnableOption ''
        a fingerprint reader via fprintd (the installer turns this on when it
        detects a known reader). Enroll with `fprintd-enroll`'';

      pam = lib.mkOption {
        type = lib.types.bool;
        default = pamFromState;
        defaultText = lib.literalExpression
          "(settings.fingerprint.pam from theme-state.json) or false";
        description = ''
          Use the fingerprint for login and sudo (PAM). Opt-in — password-only
          stays the default for the cautious; enroll a finger first. Defaults
          from theme-state.json `settings.fingerprint.pam` (System › Fingerprint
          menu) when set; otherwise false.
        '';
      };
    };

    npu.enable = lib.mkEnableOption ''
      the on-die NPU (AI accelerator) kernel driver — amdxdna on AMD (Ryzen
      AI), intel_vpu on Intel (Core Ultra and newer). Opt-in and experimental:
      this loads the in-kernel driver only; the userspace runtime (AMD XRT /
      oneAPI Level Zero NPU) is yours to add. Needs a recent kernel (see
      latestKernel)'';

    latestKernel = lib.mkEnableOption ''
      the latest mainline kernel (pkgs.linuxPackages_latest) instead of the
      distro default — for very new hardware whose drivers (a fresh NPU, the
      `xe` GPU driver, new-platform enablement) only landed recently. Off by
      default; the default kernel already carries amd-pstate and amdxdna (6.14+)'';

    camera = {
      hideIrSensor = lib.mkEnableOption ''
        hiding a dual-sensor webcam's IR (face-unlock) node from PipeWire so
        apps only ever see the colour camera. Such modules (common on recent
        ThinkPads) expose the IR sensor as a SECOND, identically-named
        "Integrated Camera"; selecting it gives a dark, 8-bit-greyscale image —
        the classic "my webcam is dark" symptom. The installer turns this on
        when it detects a paired RGB+IR webcam. Only the PipeWire node is
        disabled — the kernel /dev/video* device stays open, so face-unlock
        (Howdy) still works. Acts on the V4L2 path only; the libcamera monitor
        is left untouched, so an external camera you plug in is never affected.
        See irMatch'';

      irMatch = lib.mkOption {
        type = lib.types.str;
        default = "~.*(Integrated I|IR Camera|Infrared).*";
        description = ''
          WirePlumber regex (matched against a V4L2 node's api.v4l2.cap.card)
          selecting the IR sensor to hide. The default catches the common
          dual-sensor naming ("… Integrated I", "IR Camera", "Infrared"); set it
          to your camera's IR card name if it differs — find it with
          `v4l2-ctl --list-devices` or `wpctl inspect`. Only consulted when
          camera.hideIrSensor is on.
        '';
      };
    };

    i2c = {
      enable = lib.mkEnableOption ''
        I2C devices support. Enables access to /dev/i2c-* (useful for RGB
        controllers, sensors, and DDC/CI monitor control)'';

      ddcci = lib.mkEnableOption ''
        the ddcci-driver kernel module to expose external monitors as standard
        backlight devices via DDC/CI. This allows brightness keys and swayosd
        to natively control external displays'';
    };
  };

  config = lib.mkMerge [
    # ── Intel ──────────────────────────────────────────────────────────
    (lib.mkIf cfg.intel.guc {
      boot.kernelParams = [ "i915.enable_guc=3" ];
    })
    (lib.mkIf cfg.intel.computeRuntime {
      hardware.graphics.extraPackages = with pkgs; [ intel-compute-runtime vpl-gpu-rt ];
    })

    # ── AMD ────────────────────────────────────────────────────────────
    (lib.mkIf cfg.amd.pstate {
      boot.kernelParams = [ "amd_pstate=active" ];
    })
    (lib.mkIf cfg.amd.vaapi {
      # radeonsi itself comes from mesa (via common-gpu-amd); this just steers
      # libva at it. mkDefault so a hand-set value or another module wins.
      environment.sessionVariables.LIBVA_DRIVER_NAME = lib.mkDefault "radeonsi";
      hardware.graphics.extraPackages = [ pkgs.libva ];
    })
    (lib.mkIf cfg.amd.rocm.enable {
      hardware.graphics.extraPackages = [ pkgs.rocmPackages.clr pkgs.rocmPackages.clr.icd ];
      environment.sessionVariables = lib.optionalAttrs (cfg.amd.rocm.gfxOverride != "") {
        HSA_OVERRIDE_GFX_VERSION = cfg.amd.rocm.gfxOverride;
      };
    })

    # ── Fingerprint ────────────────────────────────────────────────────
    (lib.mkIf cfg.fingerprint.enable {
      services.fprintd.enable = true;
    })
    (lib.mkIf (cfg.fingerprint.enable && cfg.fingerprint.pam) {
      security.pam.services.login.fprintAuth = true;
      security.pam.services.sudo.fprintAuth = true;
    })

    # ── Newest kernel for very-new hardware (opt-in escape hatch) ──────
    (lib.mkIf cfg.latestKernel {
      boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
    })

    # ── NPU (in-kernel driver only; userspace runtime is BYO) ──────────
    # The driver has to actually be in the running kernel — warn (don't fail)
    # when it predates the shipped one, pointing at latestKernel.
    (lib.mkIf (cfg.npu.enable && cfg.amd.enable) {
      boot.kernelModules = [ "amdxdna" ];
      warnings = lib.optional
        (!lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.14")
        ''
          nomarchy.hardware.npu: the amdxdna driver needs kernel >= 6.14, but
          this config ships ${config.boot.kernelPackages.kernel.version}. Set
          nomarchy.hardware.latestKernel = true.'';
    })
    (lib.mkIf (cfg.npu.enable && cfg.intel.enable) {
      boot.kernelModules = [ "intel_vpu" ];
      warnings = lib.optional
        (!lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.11")
        ''
          nomarchy.hardware.npu: the intel_vpu driver (especially for newer
          NPUs) wants a recent kernel, but this config ships
          ${config.boot.kernelPackages.kernel.version}. Consider
          nomarchy.hardware.latestKernel = true.'';
    })

    # ── Webcam: hide a dual-sensor module's IR node ────────────────────
    # A built-in RGB+IR webcam exposes its IR (face-unlock) sensor as a second,
    # identically-named camera; an app that picks it gets a dark greyscale
    # image. Disable that node on the V4L2 PipeWire path so only the colour
    # camera is offered. Matched by card name (irMatch). libcamera is left
    # alone on purpose — an external camera may rely on it, and surgical
    # internal-only libcamera scoping isn't possible (the distinguishing
    # device props bind after the monitor rule runs). The kernel /dev/video*
    # stays open, so Howdy face-unlock still reads the IR sensor directly.
    (lib.mkIf (cfg.camera.hideIrSensor && config.services.pipewire.wireplumber.enable) {
      services.pipewire.wireplumber.extraConfig."90-nomarchy-hide-ir-camera" = {
        "monitor.v4l2.rules" = [
          {
            matches = [ { "api.v4l2.cap.card" = cfg.camera.irMatch; } ];
            actions."update-props"."node.disabled" = true;
          }
        ];
      };
    })

    # ── I2C / DDC/CI ───────────────────────────────────────────────────
    (lib.mkIf cfg.i2c.enable {
      hardware.i2c.enable = true;
    })
    (lib.mkIf cfg.i2c.ddcci {
      # The driver needs I2C underneath it
      hardware.i2c.enable = true;
      boot.extraModulePackages = [ config.boot.kernelPackages.ddcci-driver ];
      boot.kernelModules = [ "ddcci_backlight" ];
    })

    # ── Sanity ─────────────────────────────────────────────────────────
    {
      assertions = [
        {
          assertion = cfg.amd.rocm.enable -> cfg.amd.enable;
          message = "nomarchy.hardware.amd.rocm.enable needs nomarchy.hardware.amd.enable.";
        }
        {
          assertion = cfg.intel.computeRuntime -> cfg.intel.enable;
          message = "nomarchy.hardware.intel.computeRuntime needs nomarchy.hardware.intel.enable.";
        }
        {
          assertion = cfg.npu.enable -> (cfg.amd.enable || cfg.intel.enable);
          message = "nomarchy.hardware.npu.enable needs a detected Intel or AMD platform.";
        }
      ];
    }
  ];
}
