{
  inputs = {
    # nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    systems.url = "github:nix-systems/default";
    flake-parts.url = "github:hercules-ci/flake-parts";
    # Lexical is an alternative LSP server. There are several options for Elixir
    # LSP. See https://gist.github.com/Nezteb/dc63f1d5ad9d88907dd103da2ca000b1
    lexical.url = "github:lexical-lsp/lexical";
    # Use process-compose to manage background processes during development
    process-compose-flake.url = "github:Platonic-Systems/process-compose-flake";
  };

  outputs = {
    self,
    nixpkgs,
    systems,
    flake-parts,
    ...
  } @ inputs: let
    # Set the Erlang version
    erlangVersion = "erlang";
    # Set the Elixir version
    elixirVersion = "elixir";
  in
    flake-parts.lib.mkFlake {inherit inputs;} {
      systems = import systems;

      imports = [inputs.process-compose-flake.flakeModule];

      flake = {
        nixosModules.default = {
          config,
          lib,
          pkgs,
          ...
        }:
          with lib; let
            cfg = config.sites.zoeycomputer;

            working_directory = "/var/lib/zoeycomputer";
          in {
            options.sites.zoeycomputer = {
              enable = mkEnableOption "Enables the zoey computer";

              domain = mkOption rec {
                type = types.str;
                default = "zoeys.computer";
                example = default;
                description = "The domain name for the website";
              };

              ssl = mkOption rec {
                type = types.bool;
                default = true;
                example = default;
                description = "Whether to enable SSL on the domain or not";
              };

              phx = {
                port = mkOption rec {
                  type = types.int;
                  default = 4000;
                  example = default;
                  description = "What port should phoenix run on";
                };

                package = mkOption rec {
                  type = types.package;
                  default = self.packages.${pkgs.system}.default;
                  example = default;
                  description = "The phoenix package containing the application";
                };

                secret_key_file = mkOption {
                  type = types.str;
                  example = "/var/lib/skey";
                  description = "The Secrert key for Phoenix";
                };

                database = {
                  name = mkOption rec {
                    type = types.str;
                    default = "zoeyscomputer";
                    example = default;
                    description = "Database name";
                  };
                  user = mkOption rec {
                    type = types.str;
                    default = "zoeyscomputer";
                    example = default;
                    description = "Database user";
                  };
                  passwordFile = mkOption {
                    type = types.nullOr types.path;
                    default = null;
                    example = "/run/secrets/db_password";
                    description = "File containing the database password";
                  };
                  host = mkOption rec {
                    type = types.str;
                    default = "localhost";
                    example = default;
                    description = "Database host";
                  };
                  port = mkOption rec {
                    type = types.int;
                    default = 5432;
                    example = default;
                    description = "Database port";
                  };
                };

                enableServer = mkOption rec {
                  type = types.bool;
                  default = true;
                  example = default;
                  description = "Enable Phoenix Server, why would you not enable this?";
                };
              };
            };

            config = mkIf cfg.enable {
              services.nginx.virtualHosts.${cfg.domain} = {
                forceSSL = cfg.ssl;
                enableACME = cfg.ssl;
                locations."/".proxyPass = "http://127.0.0.1:${toString cfg.phx.port}";
              };

              users.users."zoeyscomputer-phx" = {
                isSystemUser = true;
                group = "zoeyscomputer-phx";
                home = working_directory; # Add this
                createHome = true; # Add this
              };
              users.groups."zoeyscomputer-phx" = {};

              systemd.tmpfiles.rules = [
                "d '${working_directory}' 0750 zoeyscomputer-phx zoeyscomputer-phx - -"
              ];

              # Configure PostgreSQL authentication for our user
              services.postgresql.authentication = mkBefore ''
                local ${cfg.phx.database.name} ${cfg.phx.database.user} scram-sha-256
              '';

              # Create a oneshot service to set up the database user with password
              systemd.services.init-zoeyscomputer-db = {
                description = "Initialize ZoeysComputer Database User";
                wantedBy = ["multi-user.target"];
                after = ["postgresql.service"];
                before = ["zoeyscomputer.service"];
                path = with pkgs; [util-linux];
                serviceConfig = {
                  Type = "oneshot";
                  RemainAfterExit = true;
                  User = "root";
                };
                script = ''
                  ${optionalString (cfg.phx.database.passwordFile != null) ''
                    # Read password from file
                    PASSWORD=$(cat ${cfg.phx.database.passwordFile})

                    # Create user and database if they don't exist
                    if ! runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${cfg.phx.database.user}'" | grep -q 1; then
                      echo "CREATE USER ${cfg.phx.database.user} WITH PASSWORD '$PASSWORD'" | \
                        runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql
                    else
                      echo "ALTER USER ${cfg.phx.database.user} WITH PASSWORD '$PASSWORD'" | \
                        runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql
                    fi

                    # Grant privileges to the user
                    echo "GRANT ALL PRIVILEGES ON DATABASE ${cfg.phx.database.name} TO ${cfg.phx.database.user}" | \
                      runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql
                  ''}
                '';
              };

              # Ensure database exists
              services.postgresql.ensureDatabases = [
                cfg.phx.database.name
              ];

              systemd.services."zoeyscomputer-phx" = let
                release_name = "zoeyscomputer";
              in {
                wantedBy = ["multi-user.target"];
                after = ["network.target" "postgresql.service" "init-zoeyscomputer-db.service"];
                requires = ["network-online.target" "postgresql.service" "init-zoeyscomputer-db.service"];
                description = "zoey computer";
                environment = {
                  RELEASE_TMP = working_directory;
                  PORT = toString cfg.phx.port;
                  PHX_HOST = cfg.domain;
                  PHX_SERVER = toString cfg.phx.enableServer;
                };
                serviceConfig = {
                  Type = "exec";
                  User = "zoeyscomputer-phx";
                  Group = "zoeyscomputer-phx"; # Add this
                  # DynamicUser = true;
                  WorkingDirectory = working_directory;
                  PrivateTmp = true;
                  ExecStart = pkgs.writeShellScript "start-zoeycomputer" ''
                    export SECRET_KEY_BASE=$(cat ${cfg.phx.secret_key_file})
                    # If a password file is specified, construct DATABASE_URL with password
                    ${
                      if cfg.phx.database.passwordFile != null
                      then ''
                        export DB_PASSWORD=$(cat ${cfg.phx.database.passwordFile})
                        export DATABASE_URL="ecto://${cfg.phx.database.user}:$DB_PASSWORD@${cfg.phx.database.host}:${toString cfg.phx.database.port}/${cfg.phx.database.name}"
                      ''
                      else ''
                        export DATABASE_URL="ecto://${cfg.phx.database.user}@${cfg.phx.database.host}/${cfg.phx.database.name}"
                      ''
                    }

                    # Run migrations
                    ${cfg.phx.package}/bin/migrate

                    # Start the application
                    exec ${cfg.phx.package}/bin/${release_name} start
                  '';
                  ExecStop = ''
                    ${cfg.phx.package}/bin/${release_name} stop
                  '';
                  ExecReload = ''
                    ${cfg.phx.package}/bin/${release_name} restart
                  '';
                  Restart = "on-failure";
                  RestartSec = 5;
                  StartLimitBurst = 3;
                  StartLimitInterval = 10;
                };
                path = [pkgs.bash pkgs.imagemagick pkgs.librsvg];
              };
            };
          };
      };

      perSystem = {
        # self',
        config,
        system,
        pkgs,
        lib,
        ...
      }: let
        npmDeps = (pkgs.callPackage ./assets/default.nix {}).shell.nodeDependencies;

        heroicons = pkgs.fetchFromGitHub {
          owner = "tailwindlabs";
          repo = "heroicons";
          rev = "v2.1.1";
          hash = "sha256-y/kY8HPJmzB2e7ErgkUdQijU7oUhfS3fI093Rsvyvqs=";
          sparseCheckout = ["optimized"];
        };
      in {
        # Define a consistent package set for development, testing, and
        # production.
        _module.args.pkgs = import nixpkgs {
          inherit system;
          overlays = [
            (
              final: _: let
                erlang = final.beam.interpreters.${erlangVersion};
                beamPackages = final.beam.packages.${erlangVersion};
                elixir = beamPackages.${elixirVersion};
              in {
                inherit erlang elixir;
                # Hex is not used in the devShell.
                # inherit (beamPackages) hex;
              }
            )
          ];
        };

        # # Add hydraJob for x86_64-linux
        # hydraJobs = lib.optionalAttrs (system == "x86_64-linux") {
        #   default = self.packages.${system}.default;
        # };

        # You can build your Elixir application using mixRelease.
        packages.default = pkgs.beamPackages.mixRelease {
          pname = "zoeys-computer";
          version = "0.1.0";
          src = ./.;
          removeCookie = false;
          mixNixDeps = with pkgs;
            import ./deps.nix {
              inherit pkgs lib beamPackages;
            };
          buildInputs = with pkgs; [nodejs imagemagick];

          nativeBuildInputs = with pkgs; [
            imagemagick
          ];

          postBuild = ''
            echo ${heroicons}
            ln -sf ${npmDeps}/lib/node_modules assets/node_modules
            ln -sf ${heroicons} deps/heroicons
            ls deps/
            npm run deploy --prefix ./assets

            mix do deps.loadpaths --no-deps-check, phx.digest
          '';
        };

        # Add dependencies to develop your application using Mix.
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; (
            [
              erlang
              elixir
              # You are likely to need Node.js if you develop a Phoenix
              # application.
              nodejs
              mix2nix
              node2nix
              librsvg
              imagemagick
              # Add the language server of your choice.
              inputs.lexical.packages.${system}.default
              # I once added Hex via a Nix development shell, but now I install
              # and upgrade it using Mix. Hex installed using Nix can cause an
              # issue if you manage Elixir dependencies using Mix.
            ]
            # Add a dependency for a file watcher if you develop a Phoenix
            # application.
            ++ lib.optional stdenv.isLinux inotify-tools
            ++ (lib.optionals stdenv.isDarwin (
              with darwin.apple_sdk.frameworks; [
                CoreFoundation
                CoreServices
              ]
            ))
          );
        };

        # You can define background processes in Nix using
        # process-compose-flake.
        process-compose.example = {
          settings = {
            processes = {
              ponysay.command = ''
                while true; do
                  ${lib.getExe pkgs.ponysay} "Enjoy our ponysay demo!"
                  sleep 2
                done
              '';
            };
          };
        };
      };
    };
}
