<commit_before>{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.home-assistant;

  # cfg.config != null can be assumed here
  configJSON = pkgs.writeText "configuration.json"
    (builtins.toJSON (if cfg.applyDefaultConfig then
    (recursiveUpdate defaultConfig cfg.config) else cfg.config));
  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
    # Hack to support secrets, that are encoded as custom yaml objects,
    # https://www.home-assistant.io/docs/configuration/secrets/
    sed -i -e "s/'\!secret \(.*\)'/\!secret \1/" $out
  '';

  lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
    (builtins.toJSON cfg.lovelaceConfig);
  lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } ''
    ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
  '';

  availableComponents = cfg.package.availableComponents;

  usedPlatforms = config:
    if isAttrs config then
      optional (config ? platform) config.platform
      ++ concatMap usedPlatforms (attrValues config)
    else if isList config then
      concatMap usedPlatforms config
    else [ ];

  # Given a component "platform", looks up whether it is used in the config
  # as `platform = "platform";`.
  #
  # For example, the component mqtt.sensor is used as follows:
  # config.sensor = [ {
  #   platform = "mqtt";
  #   ...
  # } ];
  useComponentPlatform = component: elem component (usedPlatforms cfg.config);

  # Returns whether component is used in config
  useComponent = component:
    hasAttrByPath (splitString "." component) cfg.config
    || useComponentPlatform component;

  # List of components used in config
  extraComponents = filter useComponent availableComponents;

  package = if (cfg.autoExtraComponents && cfg.config != null)
    then (cfg.package.override { inherit extraComponents; })
    else cfg.package;

  # If you are changing this, please update the description in applyDefaultConfig
  defaultConfig = {
    homeassistant.time_zone = config.time.timeZone;
    http.server_port = cfg.port;
  } // optionalAttrs (cfg.lovelaceConfig != null) {
    lovelace.mode = "yaml";
  };

in {
  meta.maintainers = with maintainers; [ dotlambda ];

  options.services.home-assistant = {
    enable = mkEnableOption "Home Assistant";

    configDir = mkOption {
      default = "/var/lib/hass";
      type = types.path;
      description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
    };

    port = mkOption {
      default = 8123;
      type = types.int;
      description = "The port on which to listen.";
    };

    applyDefaultConfig = mkOption {
      default = true;
      type = types.bool;
      description = ''
        Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have.
        </para>
        <para>
        Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled.
        </para>
        <para>
        This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten.
      '';
    };

    config = mkOption {
      default = null;
      # Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584
      type =  with lib.types; let
          valueType = nullOr (oneOf [
            bool
            int
            float
            str
            (lazyAttrsOf valueType)
            (listOf valueType)
          ]) // {
            description = "Yaml value";
            emptyValue.value = {};
          };
        in valueType;
      example = literalExample ''
        {
          homeassistant = {
            name = "Home";
            latitude = "!secret latitude";
            longitude = "!secret longitude";
            elevation = "!secret elevation";
            unit_system = "metric";
            time_zone = "UTC";
          };
          frontend = { };
          http = { };
          feedreader.urls = [ "https://nixos.org/blogs.xml" ];
        }
      '';
      description = ''
        Your <filename>configuration.yaml</filename> as a Nix attribute set.
        Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
        <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link>
        are encoded as strings as shown in the example.
      '';
    };

    configWritable = mkOption {
      default = false;
      type = types.bool;
      description = ''
        Whether to make <filename>configuration.yaml</filename> writable.
        This only has an effect if <option>config</option> is set.
        This will allow you to edit it from Home Assistant's web interface.
        However, bear in mind that it will be overwritten at every start of the service.
      '';
    };

    lovelaceConfig = mkOption {
      default = null;
      type = with types; nullOr attrs;
      # from https://www.home-assistant.io/lovelace/yaml-mode/<commit_msg>nixos/home-assistant: allow arbitrary yaml functions<commit_after>{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.home-assistant;

  # cfg.config != null can be assumed here
  configJSON = pkgs.writeText "configuration.json"
    (builtins.toJSON (if cfg.applyDefaultConfig then
    (recursiveUpdate defaultConfig cfg.config) else cfg.config));
  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
    # Hack to support custom yaml objects,
    # i.e. secrets: https://www.home-assistant.io/docs/configuration/secrets/
    sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
  '';

  lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
    (builtins.toJSON cfg.lovelaceConfig);
  lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } ''
    ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
  '';

  availableComponents = cfg.package.availableComponents;

  usedPlatforms = config:
    if isAttrs config then
      optional (config ? platform) config.platform
      ++ concatMap usedPlatforms (attrValues config)
    else if isList config then
      concatMap usedPlatforms config
    else [ ];

  # Given a component "platform", looks up whether it is used in the config
  # as `platform = "platform";`.
  #
  # For example, the component mqtt.sensor is used as follows:
  # config.sensor = [ {
  #   platform = "mqtt";
  #   ...
  # } ];
  useComponentPlatform = component: elem component (usedPlatforms cfg.config);

  # Returns whether component is used in config
  useComponent = component:
    hasAttrByPath (splitString "." component) cfg.config
    || useComponentPlatform component;

  # List of components used in config
  extraComponents = filter useComponent availableComponents;

  package = if (cfg.autoExtraComponents && cfg.config != null)
    then (cfg.package.override { inherit extraComponents; })
    else cfg.package;

  # If you are changing this, please update the description in applyDefaultConfig
  defaultConfig = {
    homeassistant.time_zone = config.time.timeZone;
    http.server_port = cfg.port;
  } // optionalAttrs (cfg.lovelaceConfig != null) {
    lovelace.mode = "yaml";
  };

in {
  meta.maintainers = with maintainers; [ dotlambda ];

  options.services.home-assistant = {
    enable = mkEnableOption "Home Assistant";

    configDir = mkOption {
      default = "/var/lib/hass";
      type = types.path;
      description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
    };

    port = mkOption {
      default = 8123;
      type = types.int;
      description = "The port on which to listen.";
    };

    applyDefaultConfig = mkOption {
      default = true;
      type = types.bool;
      description = ''
        Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have.
        </para>
        <para>
        Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled.
        </para>
        <para>
        This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten.
      '';
    };

    config = mkOption {
      default = null;
      # Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584
      type =  with lib.types; let
          valueType = nullOr (oneOf [
            bool
            int
            float
            str
            (lazyAttrsOf valueType)
            (listOf valueType)
          ]) // {
            description = "Yaml value";
            emptyValue.value = {};
          };
        in valueType;
      example = literalExample ''
        {
          homeassistant = {
            name = "Home";
            latitude = "!secret latitude";
            longitude = "!secret longitude";
            elevation = "!secret elevation";
            unit_system = "metric";
            time_zone = "UTC";
          };
          frontend = {
            themes = "!include_dir_merge_named themes";
          };
          http = { };
          feedreader.urls = [ "https://nixos.org/blogs.xml" ];
        }
      '';
      description = ''
        Your <filename>configuration.yaml</filename> as a Nix attribute set.
        Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
        <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link>
        are encoded as strings as shown in the example.
      '';
    };

    configWritable = mkOption {
      default = false;
      type = types.bool;
      description = ''
        Whether to make <filename>configuration.yaml</filename> writable.
        This only has an effect if <option>config</option> is set.
        This will allow you to edit it from Home Assistant's web interface.
        However, bear in mind that it will be overwritten at every start of the service.
      '';
    };

    lovelaceConfig = mkOption {
      default = null;
      type = with types; nullOr attrs;
      # from https://www.home-assistant.io/lovelace/yaml-mode/
{'fraction_non_alphanumeric': 0.107455933641248, 'fraction_numerical': 0.0023564897728343857, 'mean_word_length': 2.491280026324449, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 47, 'https://': 10, 'lorem ipsum': 0, 'www.': 6, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '362403', 'n_tokens_mistral': 3020, 'n_tokens_neox': 2941, 'n_words': 883}