{
  config,
  pkgs,
  lib,
  ...
}:
let
  inherit (lib)
    mkOption
    mkForce
    getExe
    getExe'
    listToAttrs
    flatten
    mapAttrsToList
    mapAttrs
    mapAttrs'
    nameValuePair
    toLower
    replaceStrings
    concatMapStringsSep
    ;
  partOf = cfg: "${replaceStrings [ " " ] [ "-" ] (toLower cfg.name)}.target";
  # make a firefox webapp + hidden .desktop entry for the client app
  make-firefox =
    cfg:
    mapAttrs' (
      name: cfg:
      nameValuePair "${name}-client" {
        inherit (cfg) name;
        url = "http://127.0.0.1:${builtins.toString cfg.port}";
        extraSettings = config.programs.firefox.profiles.default.settings;
        hidden = true;
      }
    ) cfg;
  # make a systemd service for running the frontend
  make-systemd-service =
    cfg:
    mapAttrs' (
      name: cfg:
      if (cfg.service != null) then
        nameValuePair "${cfg.name}-frontend" {
          Unit = {
            Description = "${cfg.name} Frontend";
            WantedBy = mkForce [ ];
          };

          Service = cfg.service;
        }
      else
        nameValuePair "" { }
    ) cfg;
  # modify systemd units to be PartOf this target
  modify-systemd-services =
    cfg:
    listToAttrs (
      flatten (
        mapAttrsToList (
          name: cfg:
          (map (req: {
            name = "${req}";
            value = {
              Unit = {
                PartOf = partOf cfg;
              };
            };
          }) cfg.requires.services)
        ) cfg
      )
    );
  modify-quadlets =
    cfg:
    listToAttrs (
      flatten (
        mapAttrsToList (
          name: cfg:
          (map (req: {
            name = "${req}";
            value = {
              extraConfig.Unit.PartOf = partOf cfg;
            };
          }) cfg.requires.containers)
        ) cfg
      )
    );
  # make a systemd target to collate dependencies
  make-systemd-target =
    cfg:
    mapAttrs (name: cfg: {
      Unit = {
        Description = "${cfg.name} Target";
        WantedBy = mkForce [ ];
        Requires =
          (map (req: req + ".service") cfg.requires.services)
          ++ (map (req: "podman-" + req + ".service") cfg.requires.containers);
      };
    }) cfg;
  # make desktop shortcuts and a script which will handle starting everything
  make-xdg =
    cfg:
    mapAttrs (name: cfg: {
      inherit (cfg) name icon genericName;
      type = "Application";
      exec = "${
        let
          notify-send = "${getExe' pkgs.libnotify "notify-send"} -a \"${cfg.name}\"";
          systemctl = "${getExe' pkgs.systemd "systemctl"}";
          dex = "${getExe pkgs.dex}";
          podman = "${getExe pkgs.podman}";
          makeContainerCheck =
            container: ''[ "$(${podman} inspect -f {{.State.Health.Status}} ${container})" == "healthy" ]'';
          # makeContainerCheck = container: ''
          #   [ ${podman} inspect -f {{.State.Status}} ${container})" != "running" ]
          # '';
          containerChecks =
            if (cfg.requires.containers != [ ]) then
              ''
                container_checks() {
                  if ''
              + (concatMapStringsSep " && " makeContainerCheck cfg.requires.containers)
              + ''
                  ; then
                    return 0
                  else
                    return 1
                  fi
                }
              ''
            else
              ''
                container_checks() {
                  return 0
                }
              '';
        in
        pkgs.writeShellScript "${name}" ''
          set -euo pipefail

          exit_error() {
            ${notify-send} -w "Failure" $1
            exit 1
          }

          ${containerChecks}

          ${notify-send} "Launching ${name} backend.." "Please be patient."
          ${systemctl} --user start ${name}.target || exit_error "Failed to launch!"

          checks=0
          until container_checks; do
            sleep 2
            checks=$((checks+1))
            if [ $((checks%10)) -eq 0 ]; then
              ${notify-send} "Waiting for backend."
            fi
            if [ $checks -ge 60 ]; then
              ${systemctl} --no-block --user stop ${name}.target
              exit_error "Failed to launch!"
            fi
          done

          ${notify-send} "Launching ${name}.."
          ${dex} -w ~/.nix-profile/share/applications/${name}-client.desktop

          ${notify-send} "Goodbye" "Shutting down."
          ${systemctl} --user stop ${name}.target
          exit 0
        ''
      }";
    }) cfg;
  cfg = config.localWebApps;
in
{
  options.localWebApps = mkOption {
    default = { };
    type =
      with lib.types;
      attrsOf (submodule {
        options = {
          name = mkOption {
            type = str;
            description = "Display name of the webapp.";
          };

          genericName = mkOption {
            type = nullOr str;
            description = "Generic name of the webapp.";
            default = null;
          };

          icon = mkOption {
            type = nullOr (either str path);
            description = "Path to a file to use for application icon.";
            default = null;
          };

          requires = mkOption {
            type = nullOr (submodule {
              options = {
                containers = mkOption {
                  type = listOf str;
                  default = [ ];
                };
                services = mkOption {
                  type = listOf str;
                  default = [ ];
                };
              };
            });
            default = null;
            description = "Containers or services this app requires.";
          };

          service = mkOption {
            type = nullOr (submodule {
              options = {
                execStartPre = mkOption {
                  type = nullOr str;
                  default = null;
                };
                execStart = mkOption {
                  type = nullOr str;
                  default = null;
                };
                execStop = mkOption {
                  type = nullOr str;
                  default = null;
                };
              };
            });
            default = null;
            description = "Submodule containing exec[StartPre/Start/Stop] commands for any required systemd service";
          };

          port = mkOption {
            type = int;
            description = "Local port the webapp should host on.";
          };
        };
      });
  };

  config = {
    programs.firefox.webapps = make-firefox cfg;

    systemd.user.targets = make-systemd-target cfg;
    systemd.user.services = (make-systemd-service cfg) // (modify-systemd-services cfg);
    services.podman.containers = modify-quadlets cfg;

    xdg.desktopEntries = make-xdg cfg;
  };
}
