{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";

  outputs = { nixpkgs, ... }:
    let
      lib = nixpkgs.lib;

      systems = [ "x86_64-linux" "aarch64-linux" ];
      pkgsFor = system: nixpkgs.legacyPackages.${system};

      appFor = pkgs:
        let
          src = lib.fileset.toSource {
            root = ./.;
            fileset = lib.fileset.unions [
              ./rest
              ./mod
              ./providers
              ./package.json
              ./package-lock.json
            ];
          };

          npmDeps = pkgs.fetchNpmDeps {
            name = "streamreverse-npm-deps";
            inherit src;
            hash = "sha256-pMxVG+OHiqjz1GHWDhhNOsrhoVPzVJ5kXhiRsP0Rflg=";
          };
        in
        pkgs.stdenvNoCC.mkDerivation {
          pname = "streamreverse";
          version = "0.1.0";

          inherit src npmDeps;

          nativeBuildInputs = [ pkgs.nodejs pkgs.npmHooks.npmConfigHook ];

          dontConfigure = true;
          dontBuild = true;

          installPhase = ''
            mkdir -p $out/lib/bug-tv $out/bin
            cp -r rest mod providers package.json node_modules $out/lib/bug-tv/
            printf '%s\n' \
              '#!${pkgs.bash}/bin/bash' \
              "exec ${pkgs.nodejs}/bin/node \"$out/lib/bug-tv/rest/server.js\" \"\$@\"" \
              > $out/bin/bug-tv
            chmod 755 $out/bin/bug-tv
          '';
        };
    in
    {
      packages = lib.genAttrs systems (system: {
        default = appFor (pkgsFor system);
      });

      devShells = lib.genAttrs systems (system: {
        default =
          let pkgs = pkgsFor system;
          in pkgs.mkShell {
            packages = [ pkgs.nodejs pkgs.nodejs-npm ];
          };
      });

      nixosModules.default =
        { config, lib, pkgs, ... }:
        let
          cfg = config.services."bug-tv";

          baseFilters = {
            minQuality = cfg.filters.minQuality;
            audioCodec = cfg.filters.audioCodec;
            subtitles = cfg.filters.subtitles;
            subtitleLang = cfg.filters.subtitleLang;
          };

          baseConfig = {
            tmdbKey = cfg.tmdbApiKey;
            enabledProviders = cfg.providers;
            timeoutMs = cfg.timeoutMs;
            apiHost = cfg.host;
            apiPort = cfg.port;
            filters = baseFilters;
          };

          mergedConfig =
            (baseConfig // (lib.removeAttrs cfg.extraConfig [ "filters" ]))
            // lib.optionalAttrs (cfg.extraConfig ? "filters") { filters = baseFilters // cfg.extraConfig.filters; };

          configJson = pkgs.writeText "bug-tv-config.json" (builtins.toJSON mergedConfig);

          app = pkgs.runCommand "bug-tv" { preferLocalBuild = true; } ''
            mkdir -p $out
            for path in rest mod providers package.json node_modules; do
              cp -a ${cfg.package}/lib/bug-tv/''${path} $out/
            done
            cp ${configJson} $out/config.json
            mkdir -p $out/bin
            printf '%s\n' \
              '#!${pkgs.bash}/bin/bash' \
              "exec ${pkgs.nodejs}/bin/node \"$out/rest/server.js\" \"\$@\"" \
              > $out/bin/bug-tv
            chmod 755 $out/bin/bug-tv
          '';
        in
        {
          options.services."bug-tv" = lib.mkOption {
            default = { };
            type = lib.types.submodule {
              options = {
                enable = lib.mkEnableOption "the bug.tv multi-provider stream finder API";

                package = lib.mkOption {
                  description = "The streamreverse package to run.";
                  type = lib.types.package;
                };

                port = lib.mkOption {
                  description = "TCP port to listen on.";
                  type = lib.types.port;
                  default = 8789;
                };

                host = lib.mkOption {
                  description = "Interface to bind.";
                  type = lib.types.str;
                  default = "0.0.0.0";
                };

                tmdbApiKey = lib.mkOption {
                  description = "TMDB API key used for search and details lookups.";
                  type = lib.types.str;
                  default = "";
                };

                providers = lib.mkOption {
                  description = "Providers enabled by default when none are requested.";
                  type = lib.types.listOf lib.types.str;
                  default = [ "cineby" ];
                };

                timeoutMs = lib.mkOption {
                  description = "Per-provider scrape timeout in milliseconds.";
                  type = lib.types.int;
                  default = 30000;
                };

                filters = lib.mkOption {
                  description = "Default stream filters.";
                  default = { };
                  type = lib.types.submodule {
                    options = {
                      minQuality = lib.mkOption {
                        description = "Minimum quality (e.g. 1080p, or any).";
                        type = lib.types.str;
                        default = "any";
                      };
                      audioCodec = lib.mkOption {
                        description = "Required audio codec, or any.";
                        type = lib.types.str;
                        default = "any";
                      };
                      subtitles = lib.mkOption {
                        description = "Subtitle requirement (any/required/none).";
                        type = lib.types.str;
                        default = "any";
                      };
                      subtitleLang = lib.mkOption {
                        description = "Required subtitle language, empty for any.";
                        type = lib.types.str;
                        default = "";
                      };
                    };
                  };
                };

                extraConfig = lib.mkOption {
                  description = "Extra keys merged into the generated config.json.";
                  type = lib.types.attrsOf lib.types.json;
                  default = { };
                };
              };
            };
          };

          config = lib.mkIf cfg.enable {
            assertions = [
              {
                assertion = cfg.tmdbApiKey != "";
                message = "services.bug-tv.tmdbApiKey must be set";
              }
              {
                assertion = cfg.providers != [ ];
                message = "services.bug-tv.providers must not be empty";
              }
            ];

            users.users."bug-tv" = {
              isSystemUser = true;
              group = "bug-tv";
            };

            users.groups."bug-tv" = { };

            systemd.services."bug-tv" = {
              description = "bug.tv stream finder API";
              wantedBy = [ "multi-user.target" ];
              after = [ "network-online.target" ];
              wants = [ "network-online.target" ];

              serviceConfig = {
                User = "bug-tv";
                Group = "bug-tv";
                ExecStart = "${app}/bin/bug-tv";
                Restart = "always";
                RestartSec = 5;

                NoNewPrivileges = true;
                PrivateTmp = true;
                ProtectSystem = "strict";
                ProtectHome = true;
                LockPersonality = true;
              };
            };
          };
        };
    };
}
