@use 'sass:map';
@use '@material/tokens/v0_161' as mdc-tokens;
@use '../style/sass-utils';
@use '../m2' as m2-theming;
@use './m3';
@use '../tokens/m2' as m2-tokens;
@use './density';
@use './format-tokens';

/// Generates tokens for the given palette with the given prefix.
/// @param {Map} $palette The palette to generate tokens for
/// @param {String} $prefix The key prefix used to name the tokens
/// @return {Map} A set of tokens for the given palette
@function _generate-palette-tokens($palette, $prefix) {
  $palette: map.remove($palette, neutral, neutral-variant);
  $result: ();
  @each $hue, $value in $palette {
    $result: map.set($result, '#{$prefix}#{$hue}', $value);
  }
  @return $result;
}

/// Creates a set of `md-ref-palette` tokens from the given palettes. (See
/// https://github.com/material-components/material-components-web/blob/master/packages/mdc-tokens/v0_161/_md-ref-palette.scss)
/// @param {Map} $primary The primary palette
/// @param {Map} $secondary The secondary palette
/// @param {Map} $tertiary The tertiary palette
/// @param {Map} $error The error palette
/// @return {Map} A set of `md-ref-palette` tokens
@function _generate-ref-palette-tokens($primary, $tertiary, $error) {
  @return sass-utils.merge-all(
      (black: #000, white: #fff),
      _generate-palette-tokens($primary, primary),
      _generate-palette-tokens(map.get($primary, secondary), secondary),
      _generate-palette-tokens($tertiary, tertiary),
      _generate-palette-tokens(map.get($primary, neutral), neutral),
      _generate-palette-tokens(map.get($primary, neutral-variant), neutral-variant),
      _generate-palette-tokens($error, error),
  );
}

/// Creates a set of `md-ref-typeface` tokens from the given palettes. (See
/// https://github.com/material-components/material-components-web/blob/master/packages/mdc-tokens/v0_161/_md-ref-typeface.scss)
/// @param {List|String} $brand The font-family to use for brand text
/// @param {List|String} $plain The font-family to use for plain text
/// @param {String} $bold The font-weight to use for bold text
/// @param {String} $medium The font-weight to use for medium text
/// @param {String} $regular The font-weight to use for regular text
/// @return {Map} A set of `md-ref-typeface` tokens
@function _generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular) {
  @return (
    brand: $brand,
    plain: $plain,
    weight-bold: $bold,
    weight-medium: $medium,
    weight-regular: $regular,
  );
}

$_cached-token-slots: null;

/// Determines the token slots for all components.
@function _get-token-slots() {
  // Cache the slots since they're constant and calculating
  // them appears to be expensive (see #29009).
  @if ($_cached-token-slots) {
    @return $_cached-token-slots;
  }

  // TODO(mmalerba): Refactor this to not depend on the legacy theme when moving out of
  //  material-experimental. This is a hack for now because there is no good way to get the token
  //  slots in material-experimental without exposing them all from material.
  $fake-theme: m2-theming.define-light-theme((
    color: (
      primary: m2-theming.define-palette(m2-theming.$red-palette),
      accent: m2-theming.define-palette(m2-theming.$red-palette),
      warn: m2-theming.define-palette(m2-theming.$red-palette),
    ),
    typography: m2-theming.define-typography-config(),
    density: 0
  ));
  $_cached-token-slots: m2-tokens.m2-tokens-from-theme($fake-theme) !global;
  @return $_cached-token-slots;
}

/// Generates a set of namespaced tokens for all components.
/// @param {Map} $systems The MDC system tokens
/// @param {Boolean} $include-non-systemized Whether to include non-systemized tokens
/// @return {Map} A map of namespaced tokens
@function _generate-tokens($systems, $include-non-systemized: false) {
  $systems: map.merge((
    md-sys-color: (),
    md-sys-elevation: (),
    md-sys-motion: (),
    md-sys-shape: (),
    md-sys-state: (),
    md-sys-typescale: ()
  ), $systems);
  $exclude-hardcoded: not $include-non-systemized;

  // DO NOT REMOVE
  // This function is used internally.
  $systems: format-tokens.private-format-tokens($systems);
  $token-slots: _get-token-slots();

  // TODO(mmalerba): Fill in remaining tokens.
  $result: sass-utils.deep-merge-all(
    // Add the system color & typography tokens (so we can give users access via an API).
    (
      (mdc, theme): map.get($systems, md-sys-color),
      (mdc, typography): map.get($systems, md-sys-typescale),
    ),
    m3.get-m3-tokens($systems, $exclude-hardcoded, $token-slots),
  );

  // Strip out tokens that are systemized by our made up density system.
  @each $namespace, $tokens in $result {
    @each $token, $value in $tokens {
      @if density.is-systemized($namespace, $token) {
        $tokens: map.remove($tokens, $token);
      }
    }
    $result: map.set($result, $namespace, $tokens);
  }
  @return $result;
}

@mixin system-level-colors($theme, $overrides: ()) {
  $palettes: map.get($theme, _mat-theming-internals-do-not-access, palettes);
  $base-palettes: (
    neutral: map.get($palettes, neutral),
    neutral-variant: map.get($palettes, neutral-variant),
    secondary: map.get($palettes, secondary),
    error: map.get($palettes, error),
  );

  $type: map.get($theme, _mat-theming-internals-do-not-access, theme-type);
  $primary: map.merge(map.get($palettes, primary), $base-palettes);
  $tertiary: map.merge(map.get($palettes, tertiary), $base-palettes);
  $error: map.get($palettes, error);

  $ref: (
    md-ref-palette: _generate-ref-palette-tokens($primary, $tertiary, $error)
  );

  $sys-colors: if($type == dark,
    mdc-tokens.md-sys-color-values-dark($ref),
    mdc-tokens.md-sys-color-values-light($ref));

  @each $name, $value in $sys-colors {
    --sys-#{$name}: #{map.get($overrides, $name) or $value};
  }
}

@mixin system-level-typography($theme, $overrides: ()) {
  $font-definition: map.get($theme, _mat-theming-internals-do-not-access, font-definition);
  $brand: map.get($font-definition, brand);
  $plain: map.get($font-definition, plain);
  $bold: map.get($font-definition, bold);
  $medium: map.get($font-definition, medium);
  $regular: map.get($font-definition, regular);

  $ref: (
    md-ref-typeface: _generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular)
  );

  $sys-typescale: mdc-tokens.md-sys-typescale-values($ref);

  @each $name, $value in $sys-typescale {
    --sys-#{$name}: #{map.get($overrides, $name) or $value};
  }
}

@function _get-sys-color($type, $ref) {
  $mdc-sys-color: if($type == dark,
    mdc-tokens.md-sys-color-values-dark($ref),
    mdc-tokens.md-sys-color-values-light($ref));

  @if (sass-utils.$use-system-color-variables) {
    @return (
      'background': var(--sys-background),
      'error': var(--sys-error),
      'error-container': var(--sys-error-container),
      'inverse-on-surface': var(--sys-inverse-on-surface),
      'inverse-primary': var(--sys-inverse-primary),
      'inverse-surface': var(--sys-inverse-surface),
      'on-background': var(--sys-on-background),
      'on-error': var(--sys-on-error),
      'on-error-container': var(--sys-on-error-container),
      'on-primary': var(--sys-on-primary),
      'on-primary-container': var(--sys-on-primary-container),
      'on-primary-fixed': var(--sys-on-primary-fixed),
      'on-primary-fixed-variant': var(--sys-on-primary-fixed-variant),
      'on-secondary': var(--sys-on-secondary),
      'on-secondary-container': var(--sys-on-secondary-container),
      'on-secondary-fixed': var(--sys-on-secondary-fixed),
      'on-secondary-fixed-variant': var(--sys-on-secondary-fixed-variant),
      'on-surface': var(--sys-on-surface),
      'on-surface-variant': var(--sys-on-surface-variant),
      'on-tertiary': var(--sys-on-tertiary),
      'on-tertiary-container': var(--sys-on-tertiary-container),
      'on-tertiary-fixed': var(--sys-on-tertiary-fixed),
      'on-tertiary-fixed-variant': var(--sys-on-tertiary-fixed-variant),
      'outline': var(--sys-outline),
      'outline-variant': var(--sys-outline-variant),
      'primary': var(--sys-primary),
      'primary-container': var(--sys-primary-container),
      'primary-fixed': var(--sys-primary-fixed),
      'primary-fixed-dim': var(--sys-primary-fixed-dim),
      'scrim': var(--sys-scrim),
      'secondary': var(--sys-secondary),
      'secondary-container': var(--sys-secondary-container),
      'secondary-fixed': var(--sys-secondary-fixed),
      'secondary-fixed-dim': var(--sys-secondary-fixed-dim),
      'shadow': map.get($mdc-sys-color, shadow),
      'surface': var(--sys-surface),
      'surface-bright': var(--sys-surface-bright),
      'surface-container': var(--sys-surface-container),
      'surface-container-high': var(--sys-surface-container-high),
      'surface-container-highest': var(--sys-surface-container-highest),
      'surface-container-low': var(--sys-surface-container-low),
      'surface-container-lowest': var(--sys-surface-container-lowest),
      'surface-dim': var(--sys-surface-dim),
      'surface-tint': var(--sys-surface-tint),
      'surface-variant': var(--sys-surface-variant),
      'tertiary': var(--sys-tertiary),
      'tertiary-container': var(--sys-tertiary-container),
      'tertiary-fixed': var(--sys-tertiary-fixed),
      'tertiary-fixed-dim': var(--sys-tertiary-fixed-dim),
    );
  }

  @return $mdc-sys-color;
}

@function _get-sys-typeface($ref) {
  @if (sass-utils.$use-system-typography-variables) {
    @return (
      'body-large': var(--sys-body-large),
      'body-large-font': var(--sys-body-large-font),
      'body-large-line-height': var(--sys-body-large-line-height),
      'body-large-size': var(--sys-body-large-size),
      'body-large-tracking': var(--sys-body-large-tracking),
      'body-large-weight': var(--sys-body-large-weight),
      'body-medium': var(--sys-body-medium),
      'body-medium-font': var(--sys-body-medium-font),
      'body-medium-line-height': var(--sys-body-medium-line-height),
      'body-medium-size': var(--sys-body-medium-size),
      'body-medium-tracking': var(--sys-body-medium-tracking),
      'body-medium-weight': var(--sys-body-medium-weight),
      'body-small': var(--sys-body-small),
      'body-small-font': var(--sys-body-small-font),
      'body-small-line-height': var(--sys-body-small-line-height),
      'body-small-size': var(--sys-body-small-size),
      'body-small-tracking': var(--sys-body-small-tracking),
      'body-small-weight': var(--sys-body-small-weight),
      'display-large': var(--sys-display-large),
      'display-large-font': var(--sys-display-large-font),
      'display-large-line-height': var(--sys-display-large-line-height),
      'display-large-size': var(--sys-display-large-size),
      'display-large-tracking': var(--sys-display-large-tracking),
      'display-large-weight': var(--sys-display-large-weight),
      'display-medium': var(--sys-display-medium),
      'display-medium-font': var(--sys-display-medium-font),
      'display-medium-line-height': var(--sys-display-medium-line-height),
      'display-medium-size': var(--sys-display-medium-size),
      'display-medium-tracking': var(--sys-display-medium-tracking),
      'display-medium-weight': var(--sys-display-medium-weight),
      'display-small': var(--sys-display-small),
      'display-small-font': var(--sys-display-small-font),
      'display-small-line-height': var(--sys-display-small-line-height),
      'display-small-size': var(--sys-display-small-size),
      'display-small-tracking': var(--sys-display-small-tracking),
      'display-small-weight': var(--sys-display-small-weight),
      'headline-large': var(--sys-headline-large),
      'headline-large-font': var(--sys-headline-large-font),
      'headline-large-line-height': var(--sys-headline-large-line-height),
      'headline-large-size': var(--sys-headline-large-size),
      'headline-large-tracking': var(--sys-headline-large-tracking),
      'headline-large-weight': var(--sys-headline-large-weight),
      'headline-medium': var(--sys-headline-medium),
      'headline-medium-font': var(--sys-headline-medium-font),
      'headline-medium-line-height': var(--sys-headline-medium-line-height),
      'headline-medium-size': var(--sys-headline-medium-size),
      'headline-medium-tracking': var(--sys-headline-medium-tracking),
      'headline-medium-weight': var(--sys-headline-medium-weight),
      'headline-small': var(--sys-headline-small),
      'headline-small-font': var(--sys-headline-small-font),
      'headline-small-line-height': var(--sys-headline-small-line-height),
      'headline-small-size': var(--sys-headline-small-size),
      'headline-small-tracking': var(--sys-headline-small-tracking),
      'headline-small-weight': var(--sys-headline-small-weight),
      'label-large': var(--sys-label-large),
      'label-large-font': var(--sys-label-large-font),
      'label-large-line-height': var(--sys-label-large-line-height),
      'label-large-size': var(--sys-label-large-size),
      'label-large-tracking': var(--sys-label-large-tracking),
      'label-large-weight': var(--sys-label-large-weight),
      'label-large-weight-prominent': var(--sys-label-large-weight-prominent),
      'label-medium': var(--sys-label-medium),
      'label-medium-font': var(--sys-label-medium-font),
      'label-medium-line-height': var(--sys-label-medium-line-height),
      'label-medium-size': var(--sys-label-medium-size),
      'label-medium-tracking': var(--sys-label-medium-tracking),
      'label-medium-weight': var(--sys-label-medium-weight),
      'label-medium-weight-prominent': var(--sys-label-medium-weight-prominent),
      'label-small': var(--sys-label-small),
      'label-small-font': var(--sys-label-small-font),
      'label-small-line-height': var(--sys-label-small-line-height),
      'label-small-size': var(--sys-label-small-size),
      'label-small-tracking': var(--sys-label-small-tracking),
      'label-small-weight': var(--sys-label-small-weight),
      'title-large': var(--sys-title-large),
      'title-large-font': var(--sys-title-large-font),
      'title-large-line-height': var(--sys-title-large-line-height),
      'title-large-size': var(--sys-title-large-size),
      'title-large-tracking': var(--sys-title-large-tracking),
      'title-large-weight': var(--sys-title-large-weight),
      'title-medium': var(--sys-title-medium),
      'title-medium-font': var(--sys-title-medium-font),
      'title-medium-line-height': var(--sys-title-medium-line-height),
      'title-medium-size': var(--sys-title-medium-size),
      'title-medium-tracking': var(--sys-title-medium-tracking),
      'title-medium-weight': var(--sys-title-medium-weight),
      'title-small': var(--sys-title-small),
      'title-small-font': var(--sys-title-small-font),
      'title-small-line-height': var(--sys-title-small-line-height),
      'title-small-size': var(--sys-title-small-size),
      'title-small-tracking': var(--sys-title-small-tracking),
      'title-small-weight': var(--sys-title-small-weight),
    );
  }
  @return mdc-tokens.md-sys-typescale-values($ref);
}

/// Generates a set of namespaced color tokens for all components.
/// @param {String} $type The type of theme system (light or dark)
/// @param {Map} $primary The primary palette
/// @param {Map} $tertiary The tertiary palette
/// @param {Map} $error The error palette
/// @return {Map} A map of namespaced color tokens
@function generate-color-tokens($type, $primary, $tertiary, $error) {
  $ref: (
    md-ref-palette: _generate-ref-palette-tokens($primary, $tertiary, $error)
  );

  $sys-color: _get-sys-color($type, $ref);

  @return _generate-tokens(map.merge($ref, (
    md-sys-color: $sys-color,
    // Because the elevation values are always combined with color values to create the box shadow,
    // elevation needs to be part of the color dimension.
    md-sys-elevation: mdc-tokens.md-sys-elevation-values(),
    // Because the state values are sometimes combined with color values to create rgba colors,
    // state needs to be part of color dimension.
    // TODO(mmalerba): If at some point we remove the need for these combined values, we can move
    //  state to the base dimension.
    md-sys-state: mdc-tokens.md-sys-state-values(),
  )));
}

/// Generates a set of namespaced color tokens for all components.
/// @param {String|List} $brand The brand font-family
/// @param {String|List} $plain The plain fort-family
/// @param {String|Number} $bold The bold font-weight
/// @param {String|Number} $medium The medium font-weight
/// @param {String|Number} $regular The regular font-weight
/// @return {Map} A map of namespaced typography tokens
@function generate-typography-tokens($brand, $plain, $bold, $medium, $regular) {
  $ref: (
    md-ref-typeface: _generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular)
  );
  $sys-typeface: _get-sys-typeface($ref);
  @return _generate-tokens((
    md-sys-typescale: $sys-typeface
  ));
}

/// Generates a set of namespaced density tokens for all components.
/// @param {String|Number} $scale The regular font-weight
/// @return {Map} A map of namespaced density tokens
@function generate-density-tokens($scale) {
  @return density.get-tokens-for-scale($scale);
}

/// Generates a set of namespaced tokens not related to color, typography, or density for all
/// components.
/// @return {Map} A map of namespaced tokens not related to color, typography, or density
@function generate-base-tokens() {
  // TODO(mmalerba): Exclude density tokens once implemented.
  @return _generate-tokens((
    md-sys-motion: mdc-tokens.md-sys-motion-values(),
    md-sys-shape: mdc-tokens.md-sys-shape-values(),
  ), $include-non-systemized: true);
}
