# 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: {
        # flake-parts imports
        imports = [
          ./flake
          ./lib/nixfiles/module.nix
          ./pkgs/module.nix
        ];

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

          debug = lib.mkDefault true;

          flake = 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 {
            # 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; [
                  alejandra
                  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: {
                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