@use '../style/sass-utils';
@use 'sass:list';
@use 'sass:map';
@use 'sass:string';

// Creates a CSS variable, including the fallback if provided.
@function _create-var($name, $fallback: null) {
  @if ($fallback) {
    @return var($name, $fallback);
  } @else {
    @return var($name);
  }
}

// Returns a list of overrides for the given M3 get-tokens mixin and prefix. Each token has its
// prefix removed since the overrides API expects its absence. The returned map includes "all" for
// all override tokens, and also the subsets with keys base, color, typography, and density.
@function get-overrides($tokens, $prefix) {
  $base: remove-token-prefixes(map.get($tokens, base), $prefix);
  $color: remove-token-prefixes(map.get($tokens, color), $prefix);
  $typography: remove-token-prefixes(map.get($tokens, typography), $prefix);
  $density: remove-token-prefixes(map.get($tokens, density), $prefix);
  $all: ();
  @each $map in ($base, $color, $typography, $density) {
    $all: map.merge($all, $map);
  }

  @return (
    all: $all,
    base: $base,
    color: $color,
    typography: $typography,
    density: $density,
  );
}

// Removes a prefix from each component token in the provided map of prefixed tokens.
@function remove-token-prefixes($prefixed-tokens: (), $prefix) {
  $tokens: ();
  @each $prefixed-token, $value in $prefixed-tokens {
    $token: string.slice($prefixed-token, string.length($prefix) + 2);
    $tokens: map.set($tokens, $token, $value);
  }
  @return $tokens;
}

// Returns the token slot value.
// Accepts an optional fallback parameter to include in the CSS variable.
// If $fallback is `true`, then use the tokens map to get the fallback.
@function slot($token, $fallbacks, $fallback: null) {
  // Fallbacks are a map of base, color, typography, and density tokens. To simplify
  // lookup, flatten these token groups into a single map.
  $fallbacks-flattened: ();
  @each $tokens in map.values($fallbacks) {
    @each $token, $value in $tokens {
      $fallbacks-flattened: map.set($fallbacks-flattened, $token, $value);
    }
  }
  @if not map.has-key($fallbacks-flattened, $token) {
    @error 'Token #{$token} does not exist. Configured tokens are:' +
        #{map.keys($fallbacks-flattened)};
  }

  $sys-fallback: map.get($fallbacks-flattened, $token);
  @if (sass-utils.is-css-var-name($sys-fallback)) {
    $sys-fallback: _create-var($sys-fallback, $fallback);
  }

  @return _create-var(--mat-#{$token}, $sys-fallback);
}

// Outputs a map of token values as CSS variable definitions.
@mixin values($tokens) {
  @include sass-utils.current-selector-or-root() {
    @each $key, $value in $tokens {
      @if $value != null {
        --mat-#{$key}: #{$value};
      }
    }
  }
}

/// Emits new token values for the given token overrides.
/// Verifies that the overrides passed in are valid tokens.
/// New token values are emitted under the current selector or root.
@mixin batch-create-token-values($overrides: (), $namespace-configs) {
  @include sass-utils.current-selector-or-root() {
    $prefixed-name-data: ();
    $all-names: ();

    @each $config in $namespace-configs {
      $namespace: map.get($config, namespace);
      $tokens: map.get(map.get($config, tokens), all);
      $prefix: '';

      @if (map.has-key($config, prefix)) {
        $prefix: map.get($config, prefix);
      }

      @each $name, $value in $tokens {
        $prefixed-name: $prefix + $name;
        $all-names: list.append($all-names, $prefixed-name, $separator: comma);
        $prefixed-name-data: map.set($prefixed-name-data, $prefixed-name, ($namespace, $name));
      }
    }

    @each $name, $value in $overrides {
      @if map.has-key($prefixed-name-data, $name) {
        $data: map.get($prefixed-name-data, $name);
        $namespace: list.nth($data, 1);
        $name: list.nth($data, 2);
        $prefixed-name: $namespace + '-' + $name;
        @include values(($prefixed-name: $value));
      } @else {
        @error #{'Invalid token name `'}#{$name}#{'`. '}#{'Valid tokens are: '}#{$all-names};
      }
    }
  }
}
