{
  config,
  lib,
  pkgs,
  ...
}:
let
  inherit (lib)
    generators
    literalExpression
    mkIf
    mkOption
    types
    ;
  inherit (builtins) typeOf stringLength;

  cfg = config.programs.mpv;

  mpvOption = with types; either str (either int (either bool float));
  mpvOptionDup = with types; either mpvOption (listOf mpvOption);
  mpvOptions = with types; attrsOf mpvOptionDup;
  mpvProfiles = with types; attrsOf mpvOptions;
  mpvBindings = with types; attrsOf str;
  mpvDefaultProfiles = with types; listOf str;

  renderOption =
    option:
    rec {
      int = toString option;
      float = int;
      bool = lib.hm.booleans.yesNo option;
      string = option;
    }
    .${typeOf option};

  renderOptionValue =
    value:
    let
      rendered = renderOption value;
      length = toString (stringLength rendered);
    in
    "%${length}%${rendered}";

  renderOptions = generators.toKeyValue {
    mkKeyValue = generators.mkKeyValueDefault { mkValueString = renderOptionValue; } "=";
    listsAsDuplicateKeys = true;
  };

  renderScriptOptions = generators.toKeyValue {
    mkKeyValue = generators.mkKeyValueDefault { mkValueString = renderOption; } "=";
    listsAsDuplicateKeys = true;
  };

  renderProfiles = generators.toINI {
    mkKeyValue = generators.mkKeyValueDefault { mkValueString = renderOptionValue; } "=";
    listsAsDuplicateKeys = true;
  };

  renderBindings =
    bindings: lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${value}") bindings);

  renderDefaultProfiles = profiles: renderOptions { profile = lib.concatStringsSep "," profiles; };

  mpvPackage =
    if cfg.scripts == [ ] then cfg.package else pkgs.mpv.override { inherit (cfg) scripts; };

in
{
  options = {
    programs.mpv = {
      enable = lib.mkEnableOption "mpv";

      package = lib.mkPackageOption pkgs "mpv" {
        example = "pkgs.mpv-unwrapped.wrapper { mpv = pkgs.mpv-unwrapped.override { vapoursynthSupport = true; }; youtubeSupport = true; }";
      };

      finalPackage = mkOption {
        type = types.package;
        readOnly = true;
        visible = false;
        description = ''
          Resulting mpv package.
        '';
      };

      scripts = mkOption {
        type = with types; listOf package;
        default = [ ];
        example = literalExpression "[ pkgs.mpvScripts.mpris ]";
        description = ''
          List of scripts to use with mpv.
        '';
      };

      scriptOpts = mkOption {
        description = ''
          Script options added to
          {file}`$XDG_CONFIG_HOME/mpv/script-opts/`. See
          {manpage}`mpv(1)`
          for the full list of options of builtin scripts.
        '';
        type = types.attrsOf mpvOptions;
        default = { };
        example = {
          osc = {
            scalewindowed = 2.0;
            vidscale = false;
            visibility = "always";
          };
        };
      };

      config = mkOption {
        description = ''
          Configuration written to
          {file}`$XDG_CONFIG_HOME/mpv/mpv.conf`. See
          {manpage}`mpv(1)`
          for the full list of options.
        '';
        type = mpvOptions;
        default = { };
        example = literalExpression ''
          {
            profile = "gpu-hq";
            force-window = true;
            ytdl-format = "bestvideo+bestaudio";
            cache-default = 4000000;
          }
        '';
      };

      includes = mkOption {
        type = types.listOf types.str;
        default = [ ];
        example = literalExpression ''
          [
            "~/path/to/config.inc";
            "~/path/to/conditional.inc";
          ]
        '';
        description = "List of configuration files to include at the end of mpv.conf.";
      };

      profiles = mkOption {
        description = ''
          Sub-configuration options for specific profiles written to
          {file}`$XDG_CONFIG_HOME/mpv/mpv.conf`. See
          {option}`programs.mpv.config` for more information.
        '';
        type = mpvProfiles;
        default = { };
        example = literalExpression ''
          {
            fast = {
              vo = "vdpau";
            };
            "protocol.dvd" = {
              profile-desc = "profile for dvd:// streams";
              alang = "en";
            };
          }
        '';
      };

      defaultProfiles = mkOption {
        description = ''
          Profiles to be applied by default. Options set by them are overridden
          by options set in [](#opt-programs.mpv.config).
        '';
        type = mpvDefaultProfiles;
        default = [ ];
        example = [ "gpu-hq" ];
      };

      bindings = mkOption {
        description = ''
          Input configuration written to
          {file}`$XDG_CONFIG_HOME/mpv/input.conf`. See
          {manpage}`mpv(1)`
          for the full list of options.
        '';
        type = mpvBindings;
        default = { };
        example = literalExpression ''
          {
            WHEEL_UP = "seek 10";
            WHEEL_DOWN = "seek -10";
            "Alt+0" = "set window-scale 0.5";
          }
        '';
      };

      extraInput = mkOption {
        description = ''
          Additional lines that are appended to {file}`$XDG_CONFIG_HOME/mpv/input.conf`.
           See {manpage}`mpv(1)` for the full list of options.
        '';
        type = with types; lines;
        default = "";
        example = ''
          esc         quit                        #! Quit
          #           script-binding uosc/video   #! Video tracks
          # additional comments
        '';
      };
    };
  };

  config = mkIf cfg.enable (
    lib.mkMerge [
      {
        assertions = [
          {
            assertion = (cfg.scripts == [ ]) || (cfg.package == pkgs.mpv);
            message = ''The programs.mpv "package" option is mutually exclusive with "scripts" option.'';
          }
        ];
      }
      {
        home.packages = [ mpvPackage ];
        programs.mpv.finalPackage = mpvPackage;
      }

      (mkIf (cfg.includes != [ ]) {
        xdg.configFile."mpv/mpv.conf" = {
          text = lib.mkAfter (lib.concatMapStringsSep "\n" (x: "include=${x}") cfg.includes);
        };
      })

      (mkIf (cfg.config != { } || cfg.profiles != { }) {
        xdg.configFile."mpv/mpv.conf".text = ''
          ${lib.optionalString (cfg.defaultProfiles != [ ]) (renderDefaultProfiles cfg.defaultProfiles)}
          ${lib.optionalString (cfg.config != { }) (renderOptions cfg.config)}
          ${lib.optionalString (cfg.profiles != { }) (renderProfiles cfg.profiles)}
        '';
      })
      (mkIf (cfg.bindings != { } || cfg.extraInput != "") {
        xdg.configFile."mpv/input.conf".text = lib.mkMerge [
          (mkIf (cfg.bindings != { }) (renderBindings cfg.bindings))
          (mkIf (cfg.extraInput != "") cfg.extraInput)
        ];
      })
      {
        xdg.configFile = lib.mapAttrs' (
          name: value:
          lib.nameValuePair "mpv/script-opts/${name}.conf" {
            text = renderScriptOptions value;
          }
        ) cfg.scriptOpts;
      }
    ]
  );

  meta.maintainers = with lib.maintainers; [
    thiagokokada
    chuangzhu
  ];
}
