# vim: set foldmethod=marker:
{
  description = "NixOS Configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
                 # ^^^^^^^^^^^^^ this part is optional
    nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    nixpkgs-nix-du.url = "github:NixOS/nixpkgs/c933cf4698e5189b35dd83bf4d7a81aef16d464a";

    # this seems to be a popular way to declare systems
    systems.url = "github:nix-systems/default";

    flake-parts.url = "github:hercules-ci/flake-parts";

    home-manager = {
      url = "github:nix-community/home-manager/release-23.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    home-manager-unstable = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs-unstable";
    };

    nix-minecraft = {
      url = "github:Silveere/nix-minecraft/quilt-revert";
      inputs.nixpkgs.follows = "nixpkgs-unstable";
    };

    nix-minecraft-upstream = {
      url = "github:infinidoge/nix-minecraft";
      inputs.nixpkgs.follows = "nixpkgs-unstable";
    };

    # provides an up-to-date database for comma
    nix-index-database = {
      url = "github:nix-community/nix-index-database";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nix-wsl = {
      url = "github:nix-community/NixOS-WSL";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    # https://github.com/nix-community/lanzaboote/releases
    lanzaboote = {
      url = "github:nix-community/lanzaboote/v0.4.2";

      inputs.nixpkgs.follows = "nixpkgs";
    };

    # no inputs.nixpkgs.follows so i can use cachix
    # https://github.com/hyprwm/Hyprland/releases
    # hyprland.url = "git+https://github.com/hyprwm/Hyprland?rev=v0.4.1&submodules=1";
    hyprland = {
      type = "git";
      url = "https://github.com/hyprwm/Hyprland";
      submodules = true;
      # ref = "refs/tags/v0.44.1";
    };

    hyprwm-contrib = {
      url = "github:hyprwm/contrib";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    hypridle = {
      url = "github:hyprwm/hypridle";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    impermanence.url = "github:nix-community/impermanence";

    agenix = {
      url = "github:ryantm/agenix";
      inputs.nixpkgs.follows = "nixpkgs-unstable";
    };

    stylix = {
      url = "github:danth/stylix";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.home-manager.follows = "home-manager";
      inputs.base16.follows = "base16";
    };

    base16 = {
      url = "github:SenchoPens/base16.nix";
    };

    nixfiles-assets = {
      # using self-hosted gitea mirror because of GitHub LFS bandwidth limit (even though i'd probably never hit it)
      type = "github";
      owner = "Silveere";
      repo = "nixfiles-assets";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.systems.follows = "systems";
    };
  };

  outputs = {
    self,
    flake-parts,
    ...
  } @ inputs:
    flake-parts.lib.mkFlake {inherit inputs;} (
      {
        inputs,
        self,
        config,
        lib,
        ...
      } @ flakeArgs: let
        # {{{
        inherit (inputs) nixpkgs nixpkgs-unstable;
        inherit (self) outputs;
        # inputs is already defined
        lib = nixpkgs.lib;
        systems = ["x86_64-linux" "aarch64-linux"];

        overlays = let
          nix-minecraft-patched-overlay = let
            normal = inputs.nix-minecraft-upstream.overlays.default;
            quilt = inputs.nix-minecraft.overlays.default;
          in
            lib.composeExtensions
            normal
            (final: prev: let
              x = quilt final prev;
            in {
              inherit (x) quiltServers quilt-server;
              minecraftServers = prev.minecraftServers // x.quiltServers;
            });
        in [
          (final: prev: let
            packages = import ./pkgs {inherit (prev) pkgs;};
          in {
            inherit (packages) mopidy-autoplay google-fonts;
            atool-wrapped = packages.atool;
          })

          # various temporary fixes that automatically revert
          self.overlays.mitigations

          # auto backports from nixpkgs unstable
          self.overlays.backports

          # modpacks (keeps modpack version in sync between hosts so i can reverse
          # proxy create track map because it's broken)
          self.overlays.modpacks

          inputs.hyprwm-contrib.overlays.default
          inputs.rust-overlay.overlays.default
          inputs.nixfiles-assets.overlays.default
          nix-minecraft-patched-overlay
        ];

        ### Configuration
        # My username
        username = "nullbite";
        # My current timezone for any mobile devices (i.e., my laptop)
        mobileTimeZone = "Europe/Amsterdam";

        # Variables to be passed to NixOS modules in the vars attrset
        vars = {
          inherit username mobileTimeZone self;
        };

        # funciton to generate packages for each system
        eachSystem = lib.genAttrs (import inputs.systems);

        # This function produces a module that adds the home-manager module to the
        # system and configures the given module to the user's Home Manager
        # configuration
        homeManagerInit = let
          _username = username;
          _nixpkgs = nixpkgs;
        in
          {
            system,
            nixpkgs ? _nixpkgs, # this is so modules can know which flake the system is using
            home-manager ? inputs.home-manager,
            username ? _username,
            module ? _: {},
            rootModule ? (import ./home/root.nix),
            userModules ? {
              ${username} = [module];
              root = [rootModule];
            },
            stateVersion,
          }: {
            config,
            lib,
            pkgs,
            ...
          }: let
            mapUserModules = lib.attrsets.mapAttrs (user: modules: {...}: {
              imports =
                [
                  ./home
                ]
                ++ modules;
              config = {
                home = {inherit stateVersion;};
              };
            });
            users = mapUserModules userModules;
          in {
            imports = [
              home-manager.nixosModules.home-manager
            ];

            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              backupFileExtension = "hm.bak";
              inherit users;
              extraSpecialArgs = {
                inherit inputs outputs vars nixpkgs home-manager;
              };
            };
          };

        # TODO rewrite this so it follows the same wrapper pattern as mkHome
        # This function produces a nixosSystem which imports configuration.nix and
        # a Home Manager home.nix for the given user from ./hosts/${hostname}/
        mkSystemN = let
          _username = username;
          _overlays = overlays;
        in
          {
            nixpkgs ? inputs.nixpkgs,
            home-manager ? inputs.home-manager,
            username ? _username,
            entrypoint ? ./system,
            modules ? [],
            stateVersion ? null,
            config ? {},
            overlays ? _overlays,
            system,
            ...
          } @ args: let
            _modules =
              [entrypoint config]
              ++ modules
              ++ [
                {
                  nixpkgs.config = {
                    inherit overlays;
                    allowUnfree = true;
                  };
                }
              ]
              ++ lib.optional (args ? stateVersion) {config.system.stateVersion = stateVersion;};
          in
            nixpkgs.lib.nixosSystem {
            };
        mkSystem = let
          _username = username;
          _overlays = overlays;
          _nixpkgs = nixpkgs;
        in
          {
            system,
            nixpkgs ? _nixpkgs,
            home-manager ? inputs.home-manager,
            overlays ? _overlays,
            hostname,
            username ? _username,
            stateVersion,
            extraModules ? [],
          }:
            nixpkgs.lib.nixosSystem {
              inherit system;
              modules =
                [
                  ./system
                  ({
                      pkgs,
                      config,
                      lib,
                      ...
                    } @ args: {
                      # Values for every single system that would not conceivably need
                      # to be made modular
                      system.stateVersion = stateVersion;
                      nixpkgs = {
                        inherit overlays;
                        config = {
                          # not having the freedom to install unfree programs is unfree
                          allowUnfree = true;
                        };
                      };
                      nix.settings.experimental-features = ["nix-command" "flakes"];
                    })
                  ./hosts/${hostname}/configuration.nix
                  (homeManagerInit {
                    inherit nixpkgs home-manager;
                    module = import ./hosts/${hostname}/home.nix;
                    inherit username system stateVersion;
                  })
                ]
                ++ extraModules;
              specialArgs = {
                inherit inputs outputs vars nixpkgs home-manager;
              };
            };

        mkWSLSystem = let
          _username = username;
        in
          {
            username ? _username,
            extraModules ? [],
            ...
          } @ args: let
            WSLModule = {...}: {
              imports = [
                inputs.nix-wsl.nixosModules.wsl
              ];
              wsl.enable = true;
              wsl.defaultUser = username;
            };
            override = {extraModules = extraModules ++ [WSLModule];};
          in
            mkSystem (args // override);

        mkISOSystem = system:
          inputs.nixpkgs-unstable.lib.nixosSystem {
            inherit system;
            modules = [
              "${inputs.nixpkgs-unstable}/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix"
              ({
                config,
                pkgs,
                lib,
                ...
              }: {
                environment.systemPackages = with pkgs; [
                  neovim
                  gparted
                ];
              })
            ];
          };

        # values to be passed to nixosModules and homeManagerModules wrappers
        moduleInputs = {
        };

        # Make a home-manager standalone configuration. This implementation is
        # better than mkSystem because it extends homeManagerConfiguration.
        mkHome = let
          _home-manager = inputs.home-manager;
          _nixpkgs = inputs.nixpkgs;
          _username = username;
        in
          {
            home-manager ? _home-manager,
            nixpkgs ? _nixpkgs,
            username ? _username,
            homeDirectory ? "/home/${username}",
            entrypoint ? ./home/standalone.nix,
            modules ? [],
            stateVersion ? null,
            config ? {},
            system,
            ...
          } @ args: let
            _modules =
              [entrypoint]
              ++ modules
              ++ [config]
              ++ [
                {
                  config = {
                    home = {
                      inherit username homeDirectory;
                    };
                    nixpkgs.config = {
                      allowUnfree = true;
                    };
                  };
                }
              ]
              ++ lib.optional (args ? stateVersion) {config.home.stateVersion = stateVersion;};
          in
            home-manager.lib.homeManagerConfiguration ({
                modules = _modules;
                pkgs = import nixpkgs {inherit system overlays;};

                extraSpecialArgs = {
                  inherit inputs outputs vars nixpkgs home-manager;

                  # this is needed because modules don't use the default arg for some reason???
                  osConfig = {};
                };
              }
              // builtins.removeAttrs args
              ["system" "nixpkgs" "home-manager" "modules" "username" "homeDirectory" "stateVersion" "entrypoint" "config"]);
        # }}}
      in {
        # flake-parts imports
        imports = [
          ./flake
        ];

        config = {
          # flake-parts systems (still uses nix-systems)
          systems = import inputs.systems;

          debug = lib.mkDefault true;

          flake = {
            # for repl debugging via :lf .
            inherit inputs vars;

            devShells = eachSystem (system: let
              pkgs = import nixpkgs-unstable {inherit system;};
            in {
              ci = pkgs.mkShell {
                buildInputs = with pkgs; [
                  nix-update
                  nix-fast-build
                ];
              };
              default = pkgs.mkShell {
                buildInputs = with pkgs; [
                  nix-update
                  inputs.agenix.packages.${system}.default
                ];
              };
            });

            # nix flake modules are meant to be portable so we cannot rely on
            # (extraS|s)pecialArgs to pass variables
            nixosModules = (import ./modules/nixos) moduleInputs;
            homeManagerModules = (import ./modules/home-manager) moduleInputs;
            packages = eachSystem (
              system: let
                pkgs = import nixpkgs-unstable {inherit system;};
              in
                (
                  import ./pkgs {inherit pkgs;}
                )
                // {
                  iso = let
                    isoSystem = mkISOSystem system;
                  in
                    isoSystem.config.system.build.isoImage;
                }
            );
            apps = eachSystem (system:
              import ./pkgs/apps.nix
              {
                inherit (self.outputs) packages;
                inherit system;
              });

            overlays = import ./overlays self;

            nixosConfigurations = {
              iso = mkISOSystem "x86_64-linux";
              slab = mkSystem {
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
                system = "x86_64-linux";
                hostname = "slab";
                stateVersion = "23.11";
              };

              nullbox = mkSystem {
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
                system = "x86_64-linux";
                hostname = "nullbox";
                stateVersion = "23.11";
              };

              nixos-wsl = mkWSLSystem {
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
                system = "x86_64-linux";
                stateVersion = "23.11";
                hostname = "nixos-wsl";
              };

              # for eval testing
              rpi4-x86_64 = mkSystem {
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
                system = "x86_64-linux";
                stateVersion = "24.11";
                hostname = "rpi4";
                extraModules = [
                  {
                    nixpkgs.hostPlatform = "x86_64-linux";
                  }
                ];
              };

              rpi4 = mkSystem {
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
                system = "aarch64-linux";
                stateVersion = "24.11";
                hostname = "rpi4";
              };
            }; # end nixosConfigurations

            homeConfigurations = {
              # minimal root config
              "root@rpi4" = mkHome {
                system = "aarch64-linux";
                stateVersion = "23.11";
                username = "root";
                homeDirectory = "/root";
                config = {pkgs, ...}: {
                  programs.bash.enable = true;

                  # update nix system-wide since it's installed via root profile
                  home.packages = with pkgs; [btdu nix];
                };
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
              };

              "nullbite@rpi4" = mkHome {
                system = "aarch64-linux";
                stateVersion = "23.11";
                config = {pkgs, ...}: {
                  programs = {
                    zsh.enable = false;
                    keychain.enable = false;
                  };
                  home.packages = with pkgs; [btdu];
                };
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
              };
              "deck" = mkHome {
                system = "x86_64-linux";
                stateVersion = "23.11";
                username = "deck";
                modules = [./users/deck/home.nix];
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
              };
              "testuser" = mkHome {
                username = "testuser";
                system = "x86_64-linux";
                modules = [./users/testuser/home.nix];
                stateVersion = "23.11";
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
              };
              "nix-on-droid" = mkHome {
                username = "nix-on-droid";
                homeDirectory = "/data/data/com.termux.nix/files/home";
                modules = [./users/nix-on-droid/home.nix];
                system = "aarch64-linux";
                stateVersion = "23.11";
                nixpkgs = inputs.nixpkgs-unstable;
                home-manager = inputs.home-manager-unstable;
              };
            };
          };
        };
      }
    ); # end outputs
}
# end flake