@use 'sass:list';
@use 'sass:map';
@use 'sass:math';
@use 'sass:meta';
@use 'sass:color';

// Deprecated flag that is no longer used to control duplication warnings.
// Remove in v22
$theme-ignore-duplication-warnings: false;

// Whether to enable compatibility with legacy methods for accessing theme information.
$theme-legacy-inspection-api-compatibility: true !default;

// Whether density should be generated by default.
$_generate-default-density: true !default;

// Warning that will be printed if the legacy theming API is used.
$private-legacy-theme-warning: 'Angular Material themes should be created from a map containing ' +
  'the keys "color", "typography", and "density". The color value should be a map containing the ' +
  'palette values for "primary", "accent", and "warn". ' +
  'See https://material.angular.dev/guide/theming for more information.';

// Flag whether to disable theme definitions copying color values to the top-level theme config.
// This copy is to preserve backwards compatibility.
$_disable-color-backwards-compatibility: false;

// These variable are not intended to be overridden externally. They use `!default` to
// avoid being reset every time this file is imported.
$_emitted-color: () !default;
$_emitted-typography: () !default;
$_emitted-density: () !default;
$_emitted-base: () !default;

//
// Private APIs
//

$private-internal-name: _mat-theming-internals-do-not-access;

/// Strip out any settings map entries that have empty values (null or ()).
@function _strip-empty-settings($settings) {
  $result: ();
  @each $key, $value in $settings {
    @if $value != null and $value != () {
      $result: map.set($result, $key, $value);
    }
  }
  @if ($result == ()) {
    @return null;
  }
  @return $result;
}

// Checks whether the given value resolves to a theme object. Theme objects are always
// of type `map` and can optionally only specify `color`, `density` or `typography`.
@function private-is-theme-object($value) {
  @return meta.type-of($value) == 'map' and (
    map.has-key($value, color) or
    map.has-key($value, density) or
    map.has-key($value, typography) or
    list.length($value) == 0
  );
}

// Checks whether a given value corresponds to a legacy constructed theme.
@function private-is-legacy-constructed-theme($value) {
  @return meta.type-of($value) == 'map' and map.get($value, '_is-legacy-theme');
}

// This is the implementation of the `m2-get-color-config` function.
// It's declared here to avoid a circular reference between this file and `m2/_theming.scss`.
@function private-get-color-config($theme, $default: null) {
  // If a configuration has been passed, return the config directly.
  @if not private-is-theme-object($theme) {
    @return $theme;
  }
  // If the theme has been constructed through the legacy theming API, we use the theme object
  // as color configuration instead of the dedicated `color` property. We do this because for
  // backwards compatibility, we copied the color configuration from `$theme.color` to `$theme`.
  // Hence developers could customize the colors at top-level and want to respect these changes
  // TODO: Remove when legacy theming API is removed.
  @if private-is-legacy-constructed-theme($theme) {
    @return $theme;
  }
  @if map.has-key($theme, color) {
    @return map.get($theme, color);
  }
  @return $default;
}

// This is the implementation of the `m2-get-density-config` function.
// It's declared here to avoid a circular reference between this file and `m2/_theming.scss`.
@function private-get-density-config($theme-or-config, $default: 0) {
  // If a configuration has been passed, return the config directly.
  @if not private-is-theme-object($theme-or-config) {
    @return $theme-or-config;
  }
  // In case a theme has been passed, extract the configuration if present,
  // or fall back to the default density config.
  @if map.has-key($theme-or-config, density) {
    @return map.get($theme-or-config, density);
  }
  @return $default;
}

// This is the implementation of the `m2-get-typography-config` function.
// It's declared here to avoid a circular reference between this file and `m2/_theming.scss`.
@function private-get-typography-config($theme-or-config, $default: null) {
  // If a configuration has been passed, return the config directly.
  @if not private-is-theme-object($theme-or-config) {
    @return $theme-or-config;
  }
  // In case a theme has been passed, extract the configuration if present,
  // or fall back to the default typography config.
  @if (map.has-key($theme-or-config, typography)) {
    @return map.get($theme-or-config, typography);
  }
  @return $default;
}

// Creates a backwards compatible theme. Previously in Angular Material, theme objects
// contained the color configuration directly. With the recent refactoring of the theming
// system to allow for density and typography configurations, this is no longer the case.
// To ensure that constructed themes which will be passed to custom theme mixins do not break,
// we copy the color configuration and put its properties at the top-level of the theme object.
// Here is an example of a pattern that should still work until it's officially marked as a
// breaking change:
//
//    @mixin my-custom-component-theme($theme) {
//      .my-comp {
//        background-color: mat.m2-get-color-from-palette(map.get($theme, primary));
//      }
//    }
//
// Note that the `$theme.primary` key does usually not exist since the color configuration
// is stored in `$theme.color` which contains a property for `primary`. This method copies
// the map from `$theme.color` to `$theme` for backwards compatibility.
@function private-create-backwards-compatibility-theme($theme) {
  @if ($_disable-color-backwards-compatibility or not map.get($theme, color)) {
    @return $theme;
  }
  $color: map.get($theme, color);
  @return map.merge($theme, $color);
}

// Gets the theme from the given value that is either already a theme, or a color configuration.
// This handles the legacy case where developers pass a color configuration directly to the
// theme mixin. Before we introduced the new pattern for constructing a theme, developers passed
// the color configuration directly to the theme mixins. This can be still the case if developers
// construct a theme manually and pass it to a theme. We support this for backwards compatibility.
// TODO(devversion): remove this in the future. Constructing themes manually is rare,
// and the code can be easily updated to the new API.
@function private-legacy-get-theme($theme-or-color-config) {
  @if private-is-theme-object($theme-or-color-config) or
      map.get($theme-or-color-config, $private-internal-name, theme-version) == 1 {
    @return $theme-or-color-config;
  }

  @warn $private-legacy-theme-warning;
  @return private-create-backwards-compatibility-theme((
    _is-legacy-theme: true,
    color: $theme-or-color-config
  ));
}

// Approximates an rgba color into a solid hex color, given a background color.
@function private-rgba-to-hex($color, $background-color) {
  // We convert the rgba color into a solid one by taking the opacity from the rgba
  // value and using it to determine the percentage of the background to put
  // into foreground when mixing the colors together.
  @return color.mix($background-color, rgba($color, 1), (1 - color.opacity($color)) * 100%);
}

// Clamps the density scale to a number between the given min and max.
// 'minimum' and 'maximum' are converted to the given min or max number respectively.
@function clamp-density($density-scale, $min, $max: 0) {
  @if $density-scale == minimum {
    @return $min;
  }
  @if $density-scale == maximum {
    @return $max;
  }
  @if meta.type-of($density-scale) != 'number' or not math.is-unitless($density-scale) {
    @return 0;
  }
  @if $density-scale < $min {
    @return $min;
  }
  @if $density-scale > $max {
    @return $max;
  }
  @return $density-scale;
}
