@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
@use 'sass:string';
@use '../style/validation';
@use './palettes';

/// Creates an error message by finding `$config` in the existing message and appending a suffix to
/// it.
/// @param {List|String} $err The error message.
/// @param {String} $suffix The suffix to add.
/// @return {List|String} The updated error message.
@function _create-dollar-config-error-message($err, $suffix) {
  @if meta.type-of($err) == 'list' {
    @for $i from 1 through list.length($err) {
      $err: list.set-nth($err, $i,
        _create-dollar-config-error-message(list.nth($err, $i), $suffix));
    }
  }
  @else if meta.type-of($err) == 'string' {
    $start: string.index($err, '$config');
    @if $start {
      $err: string.insert($err, $suffix, $start + 7);
    }
  }
  @return $err;
}

/// Validates that the given object is an M3 palette.
/// @param {*} $palette The object to test
/// @return {Boolean|null} null if it is a valid M3 palette, else true.
@function validate-palette($palette) {
  @if not meta.type-of($palette) == 'map' {
    @return true;
  }
  $keys: map.keys($palette);
  $expected-keys: map.keys(palettes.$red-palette);
  @if validation.validate-allowed-values($keys, $expected-keys...) or
      validation.validate-required-values($keys, $expected-keys...) {
    @return true;
  }
  $nv-keys: map.keys(map.get($palette, neutral-variant));
  $expected-nv-keys: map.keys(map.get(palettes.$red-palette, neutral-variant));
  @if validation.validate-allowed-values($nv-keys, $expected-nv-keys...) or
      validation.validate-required-values($nv-keys, $expected-nv-keys...) {
    @return true;
  }
  @return null;
}

/// Validates a theme config.
/// @param {Map} $config The config to test.
/// @return {List} null if no error, else the error message
@function validate-theme-config($config) {
  $err: validation.validate-type($config, 'map', 'null');
  @if $err {
    @return (#{'$config should be a configuration object. Got:'} $config);
  }
  $allowed: (color, typography, density);
  $err: validation.validate-allowed-values(map.keys($config or ()), $allowed...);
  @if $err {
    @return (
      #{'$config has unexpected properties. Valid properties are'}
      #{'#{$allowed}.'}
      #{'Found:'}
      $err
    );
  }
  $err: validate-color-config(map.get($config, color));
  @if $err {
    @return _create-dollar-config-error-message($err, '.color');
  }
  $err: validate-typography-config(map.get($config, typography));
  @if $err {
    @return _create-dollar-config-error-message($err, '.typography');
  }
  $err: validate-density-config(map.get($config, density));
  @if $err {
    @return _create-dollar-config-error-message($err, '.density');
  }
  @return null;
}

/// Validates a theme color config.
/// @param {Map} $config The config to test.
/// @return {List} null if no error, else the error message
@function validate-color-config($config) {
  $err: validation.validate-type($config, 'map', 'null');
  @if $err {
    @return (#{'$config should be a color configuration object. Got:'} $config);
  }
  $allowed: (theme-type, primary, tertiary, use-system-variables, system-variables-prefix);
  $err: validation.validate-allowed-values(map.keys($config or ()), $allowed...);
  @if $err {
    @return (
      #{'$config has unexpected properties. Valid properties are'}
      #{'#{$allowed}.'}
      #{'Found:'}
      $err
    );
  }
  @if $config and map.has-key($config, theme-type) and
      not list.index((light, dark, color-scheme), map.get($config, theme-type)) {
    @return (
      #{'Expected $config.theme-type to be one of: light, dark, color-scheme. Got:'}
      map.get($config, theme-type)
    );
  }
  @each $palette in (primary, secondary, tertiary) {
    @if $config and map.has-key($config, $palette) {
      $err: validate-palette(map.get($config, $palette));
      @if $err {
        @return (
          #{'Expected $config.#{$palette} to be a valid M3 palette. Got:'}
          map.get($config, $palette)
        );
      }
    }
  }
  @return null;
}

/// Validates a theme typography config.
/// @param {Map} $config The config to test.
/// @return {List} null if no error, else the error message
@function validate-typography-config($config) {
  $err: validation.validate-type($config, 'map', 'null');
  @if $err {
    @return (#{'$config should be a typography configuration object. Got:'} $config);
  }
  $allowed: (
    brand-family,
    plain-family,
    bold-weight,
    medium-weight,
    regular-weight,
    use-system-variables,
    system-variables-prefix
  );
  $err: validation.validate-allowed-values(map.keys($config or ()), $allowed...);
  @if $err {
    @return (
      #{'$config has unexpected properties. Valid properties are'}
      #{'#{$allowed}.'}
      #{'Found:'}
      $err
    );
  }
  @return null;
}

/// Validates a theme density config.
/// @param {Map} $config The config to test.
/// @return {List} null if no error, else the error message
@function validate-density-config($config) {
  $err: validation.validate-type($config, 'map', 'null');
  @if $err {
    @return (#{'$config should be a density configuration object. Got:'} $config);
  }
  $err: validation.validate-allowed-values(map.keys($config or ()), scale);
  @if $err {
    @return (#{'$config has unexpected properties. Valid properties are: scale. Found:'} $err);
  }
  @if $config and map.has-key($config, scale) {
    $allowed-scales: (0, -1, -2, -3, -4, -5, minimum, maximum);
    @if validation.validate-allowed-values(map.get($config, scale), $allowed-scales...) {
      @return (
        #{'Expected $config.scale to be one of: #{$allowed-scales}. Got:'}
        map.get($config, scale)
      );
    }
  }
  @return null;
}
