# User-level `nomarchy.*` options — the full surface downstream users
# configure in their home.nix. Kept small on purpose.
{ config, lib, pkgs, ... }:

let
  # One output's layout — turned into a Hyprland `monitor` rule in
  # hyprland.nix. Friendlier than raw positional CSV; unset optional fields
  # are omitted from the rule.
  monitorType = lib.types.submodule {
    options = {
      name = lib.mkOption {
        type = lib.types.str;
        example = "HDMI-A-1";
        description = "Output name (see `hyprctl monitors`), or a `desc:<…>` match.";
      };
      resolution = lib.mkOption {
        type = lib.types.str;
        default = "preferred";
        description = "`preferred` | `highres` | `highrr` | `<w>x<h>@<hz>` | `disable`.";
      };
      position = lib.mkOption {
        type = lib.types.str;
        default = "auto";
        description = "`auto` | `auto-right`/`auto-left`/… | `<x>x<y>`.";
      };
      scale = lib.mkOption {
        type = lib.types.oneOf [ lib.types.str lib.types.int lib.types.float ];
        default = 1;
        description = "Scale factor (1, 1.5, …) or `auto`.";
      };
      transform = lib.mkOption {
        type = lib.types.nullOr lib.types.int;
        default = null;
        description = "Rotation 0–7 (90°/180°/270° = 1/2/3).";
      };
      mirror = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Mirror another output by name.";
      };
      bitdepth = lib.mkOption {
        type = lib.types.nullOr lib.types.int;
        default = null;
        description = "Colour bit depth (8 or 10).";
      };
      vrr = lib.mkOption {
        type = lib.types.nullOr lib.types.int;
        default = null;
        description = "Variable refresh: 0 off, 1 on, 2 fullscreen-only.";
      };
      extra = lib.mkOption {
        type = lib.types.str;
        default = "";
        description = "Extra comma-separated Hyprland monitor args, appended verbatim.";
      };
    };
  };

  # One display profile: a monitor layout plus optional workspace→output
  # pinning. A bare list of monitor entries still works (the original
  # shape) — hyprland.nix normalizes it to { monitors = […]; }.
  # (either, not coercedTo: coercedTo refuses list-of-submodule sources.)
  displayProfileType = lib.types.either
    (lib.types.listOf monitorType)
    (lib.types.submodule {
      options = {
        monitors = lib.mkOption {
          type = lib.types.listOf monitorType;
          default = [ ];
          description = "nomarchy.monitors-style entries; each replaces the base entry for the same output whole.";
        };
        workspaces = lib.mkOption {
          type = lib.types.attrsOf lib.types.str;
          default = { };
          example = { "1" = "DP-3"; "9" = "eDP-1"; };
          description = ''
            Workspace → output pinning while this profile is active
            (Hyprland `workspace = <ws>, monitor:<output>` rules).
            Applied instantly on profile switch (existing workspaces are
            moved over) and baked at the next rebuild.
          '';
        };
      };
    });
in
{
  options.nomarchy = {
    # ── Required ───────────────────────────────────────────────────
    stateFile = lib.mkOption {
      type = lib.types.path;
      example = lib.literalExpression "./theme-state.json";
      description = ''
        Path to theme-state.json, the single source of truth for all UI
        configuration. Must live inside your flake (so evaluation stays
        pure) and be git-tracked. nomarchy-theme-sync writes to the
        on-disk copy; rebuilds bake it into the generation.
      '';
    };

    # ── Preferences ────────────────────────────────────────────────
    terminal = lib.mkOption {
      type = lib.types.str;
      default = config.nomarchy.settings.terminal or "ghostty";
      description = "Terminal emulator command, used by keybinds and $TERMINAL.";
    };

    keyboard.layout = lib.mkOption {
      type = lib.types.str;
      default = config.nomarchy.settings.keyboard.layout or "us";
      example = "de";
      description = ''
        XKB layout for the Hyprland session. The console (and the LUKS
        passphrase prompt) take theirs from the system side — the
        installer writes services.xserver.xkb + console.useXkbConfig
        into system.nix with the same value.
      '';
    };

    keyboard.variant = lib.mkOption {
      type = lib.types.str;
      default = "";
      example = "nodeadkeys";
      description = "XKB variant for the Hyprland session.";
    };

    keyboard.layouts = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [ ];
      example = [ "de" "fr" ];
      description = ''
        Extra candidate layouts offered by the interactive new-keyboard
        picker. When non-empty, a small watcher runs in the session: when a
        keyboard connects that isn't covered by nomarchy.keyboard.devices and
        hasn't been chosen before, it pops a rofi picker (these layouts plus
        the primary nomarchy.keyboard.layout), applies the choice to that
        keyboard only (a per-device kb_layout, so the built-in board is left
        alone), and remembers it in the git-tracked in-flake state
        (settings.keyboard.devices, not ~/.local/state) — re-applied
        automatically on later reconnects and across reboots. Each remembered
        choice graduates into nomarchy.keyboard.devices on the next rebuild
        (a generated device block). The runtime-remember complement to the
        declarative keyboard.devices.
      '';
    };

    keyboard.devices = lib.mkOption {
      type = lib.types.attrsOf (lib.types.submodule {
        options = {
          layout = lib.mkOption {
            type = lib.types.str;
            description = "XKB layout for this specific keyboard.";
          };
          variant = lib.mkOption {
            type = lib.types.str;
            default = "";
            description = "XKB variant for this keyboard.";
          };
        };
      });
      default = { };
      example = lib.literalExpression ''{ "keychron-keychron-k2" = { layout = "de"; }; }'';
      description = ''
        Per-device keyboard layout. The key is the device name from
        `hyprctl devices` (lower-case, hyphenated). Each entry generates a
        Hyprland `device` block that overrides the session-wide
        nomarchy.keyboard.layout for that keyboard only — e.g. an external
        keyboard that's physically a different layout than the laptop's
        built-in one. Hyprland applies it automatically whenever that
        keyboard is connected. The interactive watcher
        (nomarchy.keyboard.layouts) also writes its remembered picks here on
        the next rebuild, so a runtime choice graduates into reproducible config.
      '';
    };

    package = lib.mkOption {
      type = lib.types.package;
      default = pkgs.nomarchy-theme-sync;
      defaultText = lib.literalExpression "pkgs.nomarchy-theme-sync";
      description = "The nomarchy-theme-sync package (provided by overlays.default).";
    };

    themesDir = lib.mkOption {
      type = lib.types.path;
      default = ../../themes;
      defaultText = lib.literalExpression "\"\${nomarchy}/themes\"";
      description = ''
        Theme presets/assets directory probed at eval time for per-theme
        app overrides (<slug>/waybar.css, <slug>/waybar.jsonc). Point it
        at your own directory to ship custom layouts downstream.
      '';
    };

    nightlight = {
      enable = lib.mkEnableOption ''
        a scheduled blue-light filter (hyprsunset): warm at night, no shift
        by day. Opt-in; tune the temperature + sunrise/sunset below'';

      temperature = lib.mkOption {
        type = lib.types.int;
        default = 4000;
        example = 3500;
        description = "Warm colour temperature (K) applied at night — lower is warmer.";
      };

      sunrise = lib.mkOption {
        type = lib.types.str;
        default = "07:00";
        example = "06:30";
        description = "Time (HH:MM) the filter turns OFF — daytime, no colour shift.";
      };

      sunset = lib.mkOption {
        type = lib.types.str;
        default = "20:00";
        example = "21:00";
        description = "Time (HH:MM) the filter turns ON — warm.";
      };
    };

    updates = {
      enable = lib.mkEnableOption ''
        passive update awareness: a background check (systemd user timer) that
        compares the flake's locked inputs (nixpkgs, the Nomarchy input, …)
        against upstream and — when Flatpak is enabled — counts Flatpak
        updates, surfacing a Waybar indicator + a notification when something
        is available. It never changes anything; you still run sys-update /
        home-update / flatpak update yourself'' // { default = config.nomarchy.settings.updates.enable or false; };

      interval = lib.mkOption {
        type = lib.types.str;
        default = "daily";
        example = "6h";
        description = "How often to check, as a systemd OnCalendar expression.";
      };

      flatpak = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = ''
          Also count available Flatpak updates when the `flatpak` CLI is
          present (i.e. nomarchy.services.flatpak is on). No effect otherwise.
        '';
      };
    };

    monitors = lib.mkOption {
      type = lib.types.listOf monitorType;
      default = [ ];
      example = lib.literalExpression ''
        [
          { name = "eDP-1"; position = "0x0"; }
          { name = "HDMI-A-1"; position = "auto-right"; scale = 1; }
        ]
      '';
      description = ''
        Declarative per-output monitor layout. Each entry becomes a Hyprland
        `monitor` rule; Hyprland applies them on hotplug, so a declared
        external/dock output arranges itself when connected. The built-in
        `,preferred,auto,1` wildcard stays as the fallback for any output you
        don't list. For raw control set
        `wayland.windowManager.hyprland.settings.monitor` directly instead
        (it replaces this). Run `nwg-displays` (nomarchy.displays.enable) to
        find the right values interactively.
      '';
    };

    launchOrFocus = lib.mkOption {
      type = lib.types.listOf (lib.types.submodule {
        options = {
          key = lib.mkOption {
            type = lib.types.str;
            example = "B";
            description = "The key (with `mods`) that focuses-or-launches the app.";
          };
          mods = lib.mkOption {
            type = lib.types.str;
            default = "$mod";
            description = "Modifier string, Hyprland syntax (\"$mod\", \"$mod SHIFT\").";
          };
          class = lib.mkOption {
            type = lib.types.str;
            example = "firefox";
            description = "Window class to focus (case-insensitive; see `hyprctl clients`).";
          };
          command = lib.mkOption {
            type = lib.types.str;
            default = "";
            description = "Command to launch when no window matches (defaults to the class).";
          };
          desc = lib.mkOption {
            type = lib.types.str;
            default = "";
            description = "Cheatsheet label (defaults to \"Focus or launch <command>\").";
          };
        };
      });
      default = [ ];
      example = lib.literalExpression ''
        [
          { key = "B"; class = "firefox"; }
          { key = "O"; class = "obsidian"; }
        ]
      '';
      description = ''
        Launch-or-focus binds: the key focuses the app's existing window
        (case-insensitive class match) or launches it if none is open. Each
        entry generates a Hyprland bind AND a SUPER+? cheatsheet row. The
        launcher self-gates: a bind whose command isn't installed notifies
        instead of failing silently.
      '';
    };

    displayProfiles = lib.mkOption {
      type = lib.types.attrsOf displayProfileType;
      default = { };
      example = lib.literalExpression ''
        {
          docked = {
            monitors = [
              { name = "eDP-1"; resolution = "disable"; }
              { name = "DP-3"; position = "0x0"; }
              { name = "DP-4"; position = "auto-right"; }
            ];
            workspaces = { "1" = "DP-3"; "9" = "DP-4"; };
          };
          undocked = [ { name = "eDP-1"; position = "0x0"; } ];
        }
      '';
      description = ''
        Named display layouts for the same outputs (docked, undocked, …):
        a list of nomarchy.monitors-style entries, or an attrset with
        `monitors` plus optional `workspaces` (workspace → output pinning
        while the profile is active). Switch from the menu (System ›
        Display › Profiles) or `nomarchy-display-profile apply <name>`:
        the profile's rules apply instantly via hyprctl and the choice
        persists in the in-flake state (settings.displayProfile), so the
        next rebuild bakes the active profile's entries over
        nomarchy.monitors by name. Outputs a profile doesn't name keep
        their base rules.
      '';
    };

    # ── Component toggles ──────────────────────────────────────────
    hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
    waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
    rofi.enable = lib.mkEnableOption "Nomarchy's themed rofi launcher + the nomarchy-menu dispatcher" // { default = true; };
    swaync.enable = lib.mkEnableOption "swaync notifications, themed from the state file" // { default = true; };
    batteryNotify.enable = lib.mkEnableOption "low-battery notifications at the bar's thresholds (25% low, 10% critical — that one stays up until dismissed); self-gating, a silent no-op on machines without a battery" // { default = true; };
    idle.enable = lib.mkEnableOption "hyprlock + hypridle (idle lock, display off, suspend)" // { default = true; };
    yazi.enable = lib.mkEnableOption "the yazi TUI file manager, themed with a curated plugin set" // { default = true; };
    osd.enable = lib.mkEnableOption "swayosd on-screen display for volume/brightness/mute" // { default = true; };
    shell.enable = lib.mkEnableOption "the zsh shell experience (starship prompt, bat/eza/zoxide)" // { default = true; };
    keys.enable = lib.mkEnableOption "the SSH + GPG agent (gpg-agent fronting SSH, pinentry-qt)" // { default = true; };
    fastfetch.enable = lib.mkEnableOption "fastfetch system info fronted by the themed Nomarchy logo" // { default = true; };
    ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
    btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
    stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };
    displays.enable = lib.mkEnableOption "the nwg-displays interactive monitor arranger (a helper to find nomarchy.monitors values; the declarative config stays the source of truth)" // { default = true; };
    viewers.enable = lib.mkEnableOption "the document/image viewers (zathura, Stylix-themed, + imv)" // { default = true; };
    mime.enable = lib.mkEnableOption "default file associations (xdg mimeapps.list: PDF/image/video/text/browser/directory); entries for absent apps are skipped, so it degrades with the package suite" // { default = true; };

    # ── Computed (read-only) ───────────────────────────────────────
    theme = lib.mkOption {
      type = lib.types.attrs;
      readOnly = true;
      description = "The parsed theme state (stateFile merged over defaults).";
    };

    settings = lib.mkOption {
      type = lib.types.attrs;
      readOnly = true;
      description = ''
        Parsed feature settings — the `settings` section of the state file,
        what the menu/Waybar toggles write (e.g. settings.nightlight.enable).
        Feature options mkDefault-read from here, so a menu toggle lands in the
        in-flake state (git-tracked, reproducible) rather than ~/.local/state.
      '';
    };

    lib = lib.mkOption {
      type = lib.types.attrs;
      readOnly = true;
      description = "Color-format helpers shared by the theme consumers.";
    };
  };
}
