@use 'sass:math';
@use 'sass:map';
@use '../../token-definition';
@use '../../../style/sass-utils';
@use '../../../theming/theming';
@use '../../../theming/inspection';
@use '../../../m2/palette' as m2-palette;

// The prefix used to generate the fully qualified name for tokens in this file.
$prefix: (mat, form-field);

// Tokens that can't be configured through Angular Material's current theming API,
// but may be in a future version of the theming API.
@function get-unthemable-tokens() {
  @return ();
}

// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
  $is-dark: inspection.get-theme-type($theme) == dark;
  $color-tokens: private-get-color-palette-color-tokens($theme, primary);

  @return map.merge($color-tokens, (
    // MDC has a token for the enabled placeholder, but not for the disabled one.
    disabled-input-text-placeholder-color:
      inspection.get-theme-color($theme, foreground, icon, 0.38),
    state-layer-color: inspection.get-theme-color($theme, foreground, base, 0.87),
    error-text-color: inspection.get-theme-color($theme, warn),

    // On dark themes we set the native `select` color to some shade of white,
    // however the color propagates to all of the `option` elements, which are
    // always on a white background inside the dropdown, causing them to blend in.
    // Since we can't change background of the dropdown, we need to explicitly
    // reset the color of the options to something dark.
    select-option-text-color: if($is-dark, m2-palette.$dark-primary-text, inherit),
    // Note the spelling of the `GrayText` here which is a system color. See:
    // https://developer.mozilla.org/en-US/docs/Web/CSS/system-color
    select-disabled-option-text-color: if($is-dark, m2-palette.$dark-disabled-text, GrayText),

    // These tokens are necessary for M3. MDC has them built in already, but:
    // 1. They are too specific, breaking a lot of internal clients.
    // 2. The larger selectors result in a larger bundle.
    // Note: MDC has tokens for all the various states of the icons. Some of them are ommitted,
    // because they resolve to the same value (e.g. focus and base states for the leading icon
    // are the same).
    leading-icon-color: unset,
    disabled-leading-icon-color: unset,

    trailing-icon-color: unset,
    disabled-trailing-icon-color: unset,
    error-focus-trailing-icon-color: unset,
    error-hover-trailing-icon-color: unset,
    error-trailing-icon-color: unset,

    enabled-select-arrow-color: inspection.get-theme-color($theme, foreground, icon, 0.54),
    disabled-select-arrow-color: inspection.get-theme-color($theme, foreground, icon, 0.38),

    hover-state-layer-opacity: if($is-dark, 0.08, 0.04),
    focus-state-layer-opacity: if($is-dark, 0.24, 0.08),
  ));
}

// Generates the mapping for the properties that change based on the form field color.
@function private-get-color-palette-color-tokens($theme, $palette-name) {
  $palette-color: inspection.get-theme-color($theme, $palette-name);

  @return (
    focus-select-arrow-color: sass-utils.safe-color-change($palette-color, $alpha: 0.87),
  );
}

// Tokens that can be configured through Angular Material's typography theming API.
@function get-typography-tokens($theme) {
  @return (
    // MDC uses `subtitle1` for the input value, placeholder and floating label. The spec
    // shows `body1` for text fields though, so we manually override the typography.
    // Note: Form controls inherit the typography from the parent form field.
    container-text-font: inspection.get-theme-typography($theme, body-1, font-family),
    container-text-line-height: inspection.get-theme-typography($theme, body-1, line-height),
    container-text-size: inspection.get-theme-typography($theme, body-1, font-size),
    container-text-tracking: inspection.get-theme-typography($theme, body-1, letter-spacing),
    container-text-weight: inspection.get-theme-typography($theme, body-1, font-weight),

    // In the container styles, we updated the floating label to use the `body-1` typography level.
    // The MDC notched outline overrides this accidentally (only when the label floats) to a
    // `rem`-based value. This results in different label widths when floated/docked and ultimately
    // breaks the notch width as it has been measured in the docked state (where `body-1` is
    // applied). We try to unset these styles set by the `mdc-notched-outline`:
    // https://github.com/material-components/material-components-web/blob/master/packages/mdc-notched-outline/_mixins.scss#L272-L292.
    // This is why we can't use their `label-text-populated-size` token and we have to declare
    // our own version of it.
    outlined-label-text-populated-size: inspection.get-theme-typography($theme, body-1, font-size),

    subscript-text-font: inspection.get-theme-typography($theme, caption, font-family),
    subscript-text-line-height: inspection.get-theme-typography($theme, caption, line-height),
    subscript-text-size: inspection.get-theme-typography($theme, caption, font-size),
    subscript-text-tracking: inspection.get-theme-typography($theme, caption, letter-spacing),
    subscript-text-weight: inspection.get-theme-typography($theme, caption, font-weight),
  );
}

// Tokens that can be configured through Angular Material's density theming API.
@function get-density-tokens($theme) {
  $density-scale: theming.clamp-density(inspection.get-theme-density($theme), -5);
  $size-scale: (
    0: 56px,
    -1: 52px,
    -2: 48px,
    -3: 44px,
    -4: 40px,
    -5: 36px,
  );
  $height: map.get($size-scale, $density-scale);
  $hide-label: $height < 52px;

  // We computed the desired height of the form-field using the density configuration. The
  // spec only describes vertical spacing/alignment in non-dense mode. This means that we
  // cannot update the spacing to explicit numbers based on the density scale. Instead, we
  // determine the height reduction and equally subtract it from the default `top` and `bottom`
  // padding that is provided by the Material Design specification.
  $vertical-deduction: math.div(56px - $height, 2);

  // Note: these calculations are trivial enough that we could do them at runtime with `calc`
  // and the value of the `height` token. The problem is that because we need to hide the label
  // if the container becomes too short, we have to change the padding calculation. This is
  // complicated further by the fact that filled form fields without labels have the same
  // vertical padding as outlined ones. Alternatives:
  // 1. Using container queries to hide the label and change the padding - this doesn't work
  // because size container queries require setting the `container-type` property which breaks
  // the form field layout. We could use style queries, but they're only supported in Chrome.
  // 2. Monitoring the size of the label - we already have a `ResizeObserver` on the label so we
  // could reuse it to also check when it becomes `display: none`. This would allows us to remove
  // the three padding tokens. We don't do it, because it would require us to always set up
  // the resize observer, as opposed to currently where it's only set up for outlined form fields.
  // This may lead to performance regressions.
  // 3. Conditionally adding `::before` and `::after` to the infix with positive and negative
  // margin respectively - this works, but is likely to break a lot of overrides that are targeting
  // a specific padding. It also runs the risk of overflowing the container.
  // TODO: switch the padding tokens to style-based container queries
  // when they become available in all the browsers we support.
  $filled-with-label-padding-top: 24px - $vertical-deduction;
  $filled-with-label-padding-bottom: 8px - $vertical-deduction;
  $vertical-padding: 16px - $vertical-deduction;

  @return (
    container-height: $height,
    filled-label-display: if($hide-label, none, block),
    container-vertical-padding: $vertical-padding,
    filled-with-label-container-padding-top:
      if($hide-label, $vertical-padding, $filled-with-label-padding-top),
    filled-with-label-container-padding-bottom:
      if($hide-label, $vertical-padding, $filled-with-label-padding-bottom),
  );
}

// Combines the tokens generated by the above functions into a single map with placeholder values.
// This is used to create token slots.
@function get-token-slots() {
  @return sass-utils.deep-merge-all(
      get-unthemable-tokens(),
      get-color-tokens(token-definition.$placeholder-color-config),
      get-typography-tokens(token-definition.$placeholder-typography-config),
      get-density-tokens(token-definition.$placeholder-density-config)
  );
}
