@use 'sass:list';
@use 'sass:map';
@use '@material/elevation/elevation-theme' as mdc-elevation-theme;
@use '@material/theme/custom-properties' as mdc-custom-properties;
@use '@material/theme/theme' as mdc-theme;
@use '@material/theme/keys' as mdc-keys;
@use '../style/sass-utils';
@use '../theming/palette';
@use '../theming/theming';
@use '../typography/typography';

// Indicates whether we're building internally. Used for backwards compatibility.
$private-is-internal-build: false;

$_placeholder-color-palette: theming.define-palette(palette.$red-palette);

// Placeholder color config that can be passed to token getter functions when generating token
// slots.
$placeholder-color-config: (
  primary: $_placeholder-color-palette,
  accent: $_placeholder-color-palette,
  warn: $_placeholder-color-palette,
  is-dark: false,
  foreground: palette.$light-theme-foreground-palette,
  background: palette.$light-theme-background-palette,
);

$_placeholder-typography-level-config: typography.typography-config-level-from-mdc(body1);

// Placeholder typography config that can be passed to token getter functions when generating token
// slots.
$placeholder-typography-config: (
  font-family: 'Roboto, sans-serif',
  headline-1: $_placeholder-typography-level-config,
  headline-2: $_placeholder-typography-level-config,
  headline-3: $_placeholder-typography-level-config,
  headline-4: $_placeholder-typography-level-config,
  headline-5: $_placeholder-typography-level-config,
  headline-6: $_placeholder-typography-level-config,
  subtitle-1: $_placeholder-typography-level-config,
  subtitle-2: $_placeholder-typography-level-config,
  body-1: $_placeholder-typography-level-config,
  body-2: $_placeholder-typography-level-config,
  caption: $_placeholder-typography-level-config,
  button: $_placeholder-typography-level-config,
  overline: $_placeholder-typography-level-config,
  subheading-1: $_placeholder-typography-level-config,
  title: $_placeholder-typography-level-config,
);

// Placeholder density config that can be passed to token getter functions when generating token
// slots.
$placeholder-density-config: 0;

$_tokens: null;
$_component-prefix: null;

@mixin _configure-token-prefix($first, $rest...) {
  $_component-prefix: '' !global;
  @each $item in $rest {
    $_component-prefix:
      if($_component-prefix == '', $item, '#{$_component-prefix}-#{$item}') !global;
  }
  @include mdc-custom-properties.configure($varname-prefix: $first) {
    @content;
  }
  $_component-prefix: null !global;
}

// Sets the token prefix and map to use when creating token slots.
@mixin use-tokens($prefix, $tokens) {
  $_tokens: $tokens !global;
  @include _configure-token-prefix($prefix...) {
    @content;
  }
  $_tokens: null !global;
}

// Emits a slot for the given token, provided that it has a non-null value in the token map passed
// to `use-tokens`.
@mixin create-token-slot($property, $token, $emit-fallback: false) {
  @if $_component-prefix == null or $_tokens == null {
    @error '`create-token-slot` must be used within `use-tokens`';
  }
  @if not map.has-key($_tokens, $token) {
    @error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
  }
  @if map.get($_tokens, $token) != null {
    $fallback: null;

    @if ($emit-fallback == true) {
      $fallback: map.get($_tokens, $token);
    }
    @else if ($emit-fallback) {
      $fallback: $emit-fallback;
    }

    $value: mdc-custom-properties.create('#{$_component-prefix}-#{$token}', $fallback: $fallback);
    @include mdc-theme.property($property, $value);
  }
}

// Returns the name of a token including the current prefix. Intended to be used in calculations
// involving tokens. `create-token-slot` should be used when outputting tokens.
@function get-token-variable($token) {
  @if $_component-prefix == null or $_tokens == null {
    @error '`get-token-variable` must be used within `use-tokens`';
  }
  @if not map.has-key($_tokens, $token) {
    @error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
  }

  @return mdc-custom-properties.create-varname('#{$_component-prefix}-#{$token}');
}

// TODO(crisbeto): should be able to replace the usages of `get-token-variable` with this.
// Returns a `var()` reference to a specific token. Intended for declarations
// where the token has to be referenced as a part of a larger expression.
@function get-token-variable-reference($token, $emit-fallback: false) {
  @if $_component-prefix == null or $_tokens == null {
    @error '`get-token-variable-reference` must be used within `use-tokens`';
  }
  @if not map.has-key($_tokens, $token) {
    @error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
  }

  $var: get-token-variable($token);
  $fallback: if($emit-fallback, map.get($_tokens, $token), null);

  @if ($fallback != null) {
    @return var($var, $fallback);
  }
  @else {
    @return var($var);
  }
}

@mixin create-token-values($prefix, $tokens) {
  @include _configure-token-prefix($prefix...) {
    @include mdc-keys.declare-custom-properties($tokens, $_component-prefix);
  }
}

// MDC doesn't currently handle elevation tokens properly. As a temporary workaround we can combine
// the elevation and shadow-color tokens into a full box-shadow and use it as the value for the
// elevation token.
@function resolve-elevation($tokens, $elevation-token, $shadow-color-token) {
  $elevation: map.get($tokens, $elevation-token);
  $shadow-color: map.get($tokens, $shadow-color-token);
  @return map.merge($tokens, (
    $elevation-token: mdc-elevation-theme.elevation-box-shadow($elevation, $shadow-color),
    $shadow-color-token: null,
  ));
}

/// Checks whether a list starts wih a given prefix
/// @param {List} $list The list value to check the prefix of.
/// @param {List} $prefix The prefix to check.
/// @return {Boolean} Whether the list starts with the prefix.
@function _is-prefix($list, $prefix) {
  @for $i from 1 through list.length($prefix) {
    @if list.nth($list, $i) != list.nth($prefix, $i) {
      @return false;
    }
  }
  @return true;
}

/// Gets the supported color variants in the given token set for the given prefix.
/// @param {Map} $tokens The full token map.
/// @param {List} $prefix The component prefix to get color variants for.
/// @return {List} The supported color variants.
@function _supported-color-variants($tokens, $prefix) {
  $result: ();
  @each $namespace in map.keys($tokens) {
    @if list.length($prefix) == list.length($namespace) - 1 and _is-prefix($namespace, $prefix) {
      $result: list.append($result, list.nth($namespace, list.length($namespace)), comma);
    }
  }
  @return $result;
}

/// Gets the token values for the given components prefix with the given options.
/// @param {Map} $tokens The full token map.
/// @param {List} $prefix The component prefix to get the token values for.
/// @param {ArgList} Any additional options
///   Currently the additional supported options are:
//     - $color-variant (The color variant to use for the component)
/// @throws If given options are invalid
/// @return {Map} The token values for the requested component.
@function get-tokens-for($tokens, $prefix, $options...) {
  $options: sass-utils.validate-keyword-args($options, (color-variant));
  @if $tokens == () {
    @return ();
  }
  $values: map.get($tokens, $prefix);
  $color-variant: map.get($options, color-variant);
  @if $color-variant == null {
    @return $values;
  }
  $overrides: map.get($tokens, list.append($prefix, $color-variant));
  @if $overrides == null {
    $variants: _supported-color-variants($tokens, $prefix);
    $secondary-message: if($variants == (),
      'Mixin does not support color variants',
      'Supported color variants are: #{$variants}'
    );

    @error 'Invalid color variant: #{$color-variant}. #{$secondary-message}.';
  }
  @return map.merge($values, $overrides);
}
