{
  config,
  lib,
  pkgs,
  ...
}: let
  inherit (lib) converge getExe mkOption types;
  inherit (lib.modules) mkForce mkIf;
  inherit (lib.lists) elem;
  inherit (lib.strings) concatMapStringsSep optionalString;
  inherit (lib.attrsets) mapAttrsToList filterAttrsRecursive optionalAttrs;

  cfg = config.services.esphome;

  stateDir = "/var/lib/esphome";
  format = pkgs.formats.yaml {};

  # Adapted from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/home-automation/home-assistant.nix
  mkESPConf = n: cfg: let
    filteredConfig = converge (filterAttrsRecursive (_: v: ! elem v [null])) cfg;
  in rec {
    name = "${n}-nix.yaml";
    file = pkgs.runCommandLocal name {} ''
      cp ${format.generate name filteredConfig} $out
      sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
      sed -i 's/ {}//g' $out
      sed -i "s/'\"/\"/g" $out
      sed -i "s/\"'/\"/g" $out
    '';
  };
in {
  options.services.esphome = {
    firmwareConfigs = mkOption {
      default = {};
      type = with types; attrsOf anything;
    };

    secretsFile = mkOption {
      default = null;
      type = types.nullOr types.path;
    };
  };

  config = mkIf cfg.enable {
    # Fixes https://github.com/NixOS/nixpkgs/issues/339557
    users = {
      users.esphome = {
        isNormalUser = true;
        group = "esphome";
        home = stateDir;
      };
      groups.esphome = {};
    };

    # Fixes https://github.com/NixOS/nixpkgs/issues/370611
    nixpkgs.overlays = [
      (final: prev: {
        esphome = prev.esphome.overrideAttrs (o: {
          patches = [
            (builtins.toFile "post-build.patch" ''
              --- a/esphome/components/esp32/post_build.py.script
              +++ b/esphome/components/esp32/post_build.py.script
              @@ -2,10 +2,13 @@

               # pylint: disable=E0602
               Import("env")  # noqa
              +#print(env.Dump())

               import os
               import shutil

              +os.environ["PATH"] = os.path.dirname(env.get("PYTHONEXE")) + os.pathsep + os.environ["PATH"]
              +
               if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
                   try:
                       import esptool
              @@ -63,6 +66,7 @@ def esp32_create_combined_bin(source, target, env):
                       esptool.main(cmd)
                   else:
                       subprocess.run(["esptool.py", *cmd])
              +        #subprocess.run([env.get("PYTHONEXE"), "/var/lib/esphome/.platformio/packages/tool-esptoolpy/esptool.py", *cmd])


               def esp32_copy_ota_bin(source, target, env):
            '')
          ];
        });
      })
    ];

    systemd.services.esphome = {
      serviceConfig =
        (optionalAttrs (cfg.firmwareConfigs != {}) {
          ExecStartPre = getExe (pkgs.writeShellApplication {
            name = "esphome-exec-start-pre";

            runtimeInputs = [
              pkgs.findutils
            ];

            text = ''
              shopt -s nullglob

              for file in ${stateDir}/*-nix.yaml; do
                  rm "$file"
              done

              ${optionalString
                (cfg.secretsFile != null)
                # bash
                ''
                  cp -f "$(realpath "${cfg.secretsFile}")" ${stateDir}/secrets.yaml
                ''}

              ${concatMapStringsSep
                "\n"
                (dev:
                  # bash
                  ''
                    cp -f "$(realpath "${dev.file}")" ${stateDir}/"${dev.name}"
                  '')
                (mapAttrsToList mkESPConf cfg.firmwareConfigs)}
            '';
          });
        })
        // {
          # Fixes https://github.com/NixOS/nixpkgs/issues/339557
          DynamicUser = mkForce "off";
        };
    };
  };
}