@use '../../color/all-color';
@use '../../density/private/all-density';
@use '../../m2/palette' as m2-palette;
@use '../../m2/theming' as m2-theming;
@use '../../m2/typography' as m2-typography;
@use '../../typography/all-typography';
@use '../../typography/typography';
@use '../all-theme';
@use '../theming';
@use 'sass:map';

// A new way to configure themes has been introduced. This Sass file ensures that the theming
// API is backwards compatible, so that the old pattern for configuring themes can be still
// used. The Sass compilation of this file will fail if one of the tests/assertions fails.

$primary: m2-theming.define-palette(m2-palette.$blue-palette);
$accent: m2-theming.define-palette(m2-palette.$grey-palette);
$warn: m2-theming.define-palette(m2-palette.$indigo-palette);
$default-warn: m2-theming.define-palette(m2-palette.$red-palette);

// Asserts that a given value matches the expected value. If not, an error is
// thrown with the given error message.
@mixin assert($value, $expected, $error-msg) {
  @if $value != $expected {
    @error 'Expected "#{$value}" to be "#{$expected}". #{$error-msg}';
  }
}

// Asserts that the specified color configuration is configured properly.
@mixin assert-color-config($config, $primary, $accent, $warn, $is-dark) {
  $color: map.get($config, color);
  @include assert(map.get($color, primary), $primary,
      'Primary palette does not match expected palette.');
  @include assert(map.get($color, accent), $accent,
      'Accent palette does not match expected palette.');
  @include assert(map.get($color, warn), $warn,
      'Warn palette does not match expected palette.');
  @include assert(map.get($color, is-dark), $is-dark,
      'Expected color config `is-dark` to be: #{$is-dark}');
}

$legacy-light-theme: m2-theming.define-light-theme($primary, $accent, $warn);
$legacy-dark-theme: m2-theming.define-dark-theme($primary, $accent, $warn);
$legacy-light-theme-default-warn: m2-theming.define-light-theme($primary, $accent);
$legacy-dark-theme-default-warn: m2-theming.define-dark-theme($primary, $accent);

$new-api-light-theme: m2-theming.define-light-theme((
  color: (primary: $primary, accent: $accent, warn: $warn)
));
$new-api-dark-theme: m2-theming.define-dark-theme((
  color: (primary: $primary, accent: $accent, warn: $warn)
));
$new-api-light-theme-default-warn: m2-theming.define-light-theme((
  color: (primary: $primary, accent: $accent)
));
$new-api-dark-theme-default-warn: m2-theming.define-dark-theme((
  color: (primary: $primary, accent: $accent)
));

$light-theme-only-density: m2-theming.define-light-theme((density: -2));
$dark-theme-only-density: m2-theming.define-dark-theme((density: -2));

$typography-config: m2-typography.define-typography-config();
$light-theme-only-typography: m2-theming.define-light-theme((typography: $typography-config));
$dark-theme-only-typography: m2-theming.define-dark-theme((typography: $typography-config));

// Test which ensures that constructed themes by default do not set configurations
// for the individual parts of the theming system (color, density, typography).
@mixin test-default-theming-system-configs() {
  $themes: ($legacy-light-theme, $legacy-dark-theme, $new-api-light-theme, $new-api-dark-theme);
  @each $theme in $themes {
    @include assert(map.has-key($theme, color), true,
      'Expected color to be explicitly configured.');
    @include assert(map.has-key($theme, typography), false,
      'Expected typography to be not explicitly configured.');
    @include assert(map.has-key($theme, density), false,
      'Expected density to be not explicitly configured.');
  }
  @include assert(map.has-key($light-theme-only-density, color), false,
      'Expected color to be not explicitly configured.');
  @include assert(map.has-key($dark-theme-only-density, color), false,
      'Expected color to be not explicitly configured.');
}

// Test which ensures that constructed themes have the proper color configurations
// as specified in the `mat-light-theme` and `mat-dark-theme` functions.
@mixin test-create-color-config() {
  @include assert-color-config($legacy-light-theme, $primary, $accent, $warn, false);
  @include assert-color-config($legacy-dark-theme, $primary, $accent, $warn, true);
  @include assert-color-config($legacy-light-theme-default-warn, $primary, $accent,
    $default-warn, false);
  @include assert-color-config($legacy-dark-theme-default-warn, $primary, $accent,
    $default-warn, true);
  @include assert-color-config($new-api-light-theme, $primary, $accent, $warn, false);
  @include assert-color-config($new-api-dark-theme, $primary, $accent, $warn, true);
  @include assert-color-config($new-api-light-theme-default-warn, $primary, $accent,
    $default-warn, false);
  @include assert-color-config($new-api-dark-theme-default-warn, $primary, $accent,
    $default-warn, true);
}

// Test which ensures that constructed themes pass through the specified
// density and typography configurations.
@mixin test-density-typography-config-pass-through() {
  @include assert(map.get($light-theme-only-density, density), -2,
    'Expected density config to be included in theme.');
  @include assert(map.get($dark-theme-only-density, density), -2,
    'Expected density config to be included in theme.');
  @include assert(map.get($light-theme-only-typography, typography), $typography-config,
    'Expected typography config to be included in theme.');
  @include assert(map.get($dark-theme-only-typography, typography), $typography-config,
    'Expected typography config to be included in theme.');
}

// Test which ensures that color configurations can be properly read from
// theme objects passed to individual component theme mixins.
@mixin test-get-color-config() {
  $color-config: map.get($legacy-light-theme, color);
  $no-color-light-theme: m2-theming.define-light-theme((color: null));
  $no-color-dark-theme: m2-theming.define-dark-theme((color: null));
  @include assert(m2-theming.get-color-config($color-config), $color-config,
      'Expected that passing a color config to a theme will work for backwards compatibility.');
  // For legacy constructed themes, the color configuration is the actual `$theme` object.
  // This is done for backwards compatibility because developers could modify colors directly
  // by updating immediate properties on the `$theme` object.
  @include assert(m2-theming.get-color-config($legacy-light-theme), $legacy-light-theme,
      'Expected that the theme object is used for the color configuration.');
  @include assert(m2-theming.get-color-config($new-api-dark-theme),
    map.get($new-api-dark-theme, color),
    'Expected that a color config can be read from a theme object.');
  @include assert(m2-theming.get-color-config($light-theme-only-density), null,
      'Expected that by default, no color configuration is used.');
  @include assert(m2-theming.get-color-config($no-color-light-theme), null,
      'Expected that no color configuration can be explicitly specified.');
  @include assert(m2-theming.get-color-config($no-color-dark-theme), null,
    'Expected that no color configuration can be explicitly specified.');
}

// Test which ensures that density configurations can be properly read from
// theme objects passed to individual component theme mixins.
@mixin test-get-density-config() {
  $density-config: map.get($light-theme-only-density, density);
  $no-density-light-theme: m2-theming.define-light-theme((density: null));
  $no-density-dark-theme: m2-theming.define-dark-theme((density: null));
  @include assert(m2-theming.get-density-config($light-theme-only-density), -2,
    'Expected that a density config can be read from a theme object.');
  @include assert(m2-theming.get-density-config($light-theme-only-typography), 0,
    'Expected that by default, the density system will be configured.');
  @include assert(m2-theming.get-density-config($no-density-light-theme), null,
    'Expected that no density configuration can be explicitly specified.');
  @include assert(m2-theming.get-density-config($no-density-dark-theme), null,
    'Expected that no density configuration can be explicitly specified.');
}

// Test which ensures that typography configurations can be properly read from
// theme objects passed to individual component theme mixins.
@mixin test-get-typography-config() {
  $no-typography-light-theme: m2-theming.define-light-theme((density: null));
  $no-typography-dark-theme: m2-theming.define-dark-theme((density: null));
  @include assert(m2-theming.get-typography-config($light-theme-only-typography),
    $typography-config, 'Expected that a typography config can be read from a theme object.');
  @include assert(m2-theming.get-typography-config($legacy-light-theme), null,
    'Expected that by default, no typography configuration is used.');
  @include assert(m2-theming.get-typography-config($no-typography-light-theme), null,
    'Expected that no typography configuration can be explicitly specified.');
  @include assert(m2-theming.get-typography-config($no-typography-dark-theme), null,
    'Expected that no typography configuration can be explicitly specified.');
}

// Test which ensures that all Angular Material theme mixins accept a theme
// object without throwing any exception.
@mixin test-theme-mixins-successful-compilation() {
  @include all-theme.all-component-themes($legacy-light-theme);
  @include all-theme.all-component-themes($legacy-light-theme-default-warn);
  @include all-theme.all-component-themes($legacy-dark-theme);
  @include all-theme.all-component-themes($legacy-dark-theme-default-warn);
  @include all-theme.all-component-themes($new-api-light-theme);
  @include all-theme.all-component-themes($new-api-light-theme-default-warn);
  @include all-theme.all-component-themes($new-api-dark-theme);
  @include all-theme.all-component-themes($new-api-dark-theme-default-warn);
  @include all-theme.all-component-themes($light-theme-only-density);
  @include all-theme.all-component-themes($dark-theme-only-density);
  @include all-theme.all-component-themes($light-theme-only-typography);
  @include all-theme.all-component-themes($dark-theme-only-typography);
}

// Custom theme mixin used by the custom theme backwards compatibility test.
// This replicates a custom theme that expects legacy theme objects.
@mixin _my-custom-theme-legacy($theme) {
  .demo-custom-comp {
    border-color: m2-theming.get-color-from-palette(map.get($theme, primary));
  }
}

@mixin _my-custom-theme-new-api-color($config-or-theme) {
  $config: m2-theming.get-color-config($config-or-theme);

  .demo-custom-comp {
    border-color: m2-theming.get-color-from-palette(map.get($config, primary));
  }
}

@mixin _my-custom-theme-new-api-density($config-or-theme) {
  $density-scale: m2-theming.get-density-config($config-or-theme);

  .demo-custom-comp {
    // Simulates logic for computing density according to the Material Design
    // specification.
    height: 48 - (4px * $density-scale);
  }
}

// Custom theme mixin used by the custom theme backwards compatibility test.
@mixin _my-custom-theme-new-api($theme-or-color-config) {
  $theme: theming.private-legacy-get-theme($theme-or-color-config);
  $color: m2-theming.get-color-config($theme);
  $density: m2-theming.get-density-config($theme);

  @if $color != null {
    @include _my-custom-theme-new-api-color($color);
  }
  @if $density != null {
    @include _my-custom-theme-new-api-density($density);
  }
}

@mixin test-custom-theme-backwards-compatibility() {
  @include _my-custom-theme-legacy($legacy-light-theme);
  @include _my-custom-theme-legacy($legacy-dark-theme);
  @include _my-custom-theme-legacy($new-api-light-theme);
  @include _my-custom-theme-legacy($new-api-dark-theme);
  @include _my-custom-theme-new-api($legacy-light-theme);
  @include _my-custom-theme-new-api($legacy-dark-theme);
  @include _my-custom-theme-new-api($new-api-light-theme);
  @include _my-custom-theme-new-api($new-api-dark-theme);
}

// Test which ensures that either theme objects, or individual system configurations
// can be passed to the all-density, all-typography or all-color mixins.
@mixin test-all-theme-mixins-arguments() {
  $test-themes: (
    $legacy-light-theme,
    $legacy-dark-theme,
    $new-api-light-theme,
    $new-api-dark-theme
  );

  @each $theme in $test-themes {
    @include all-typography.all-component-typographies($theme);
    @include all-color.all-component-colors($theme);
    @include all-density.all-component-densities($theme);
  }

  @include all-typography.all-component-typographies(
    map.get($light-theme-only-typography, typography));
  @include all-typography.all-component-typographies($light-theme-only-typography);

  @include all-density.all-component-densities(map.get($light-theme-only-density, density));
  @include all-density.all-component-densities($light-theme-only-density);
}

@mixin test-individual-system-mixins-unwrap() {
  @include _my-custom-theme-new-api-color($new-api-dark-theme);
  @include _my-custom-theme-new-api-color(map.get($new-api-dark-theme, color));
  @include _my-custom-theme-new-api($new-api-dark-theme);
  @include _my-custom-theme-new-api(map.get($new-api-dark-theme, color));

  @include all-theme.all-component-themes($new-api-dark-theme);
  @include all-theme.all-component-themes(map.get($new-api-dark-theme, color));
}

// Include all tests. Sass will throw if one of the tests fails.
@include test-create-color-config();
@include test-default-theming-system-configs();
@include test-density-typography-config-pass-through();
@include test-theme-mixins-successful-compilation();
@include test-get-color-config();
@include test-get-density-config();
@include test-get-typography-config();
@include test-custom-theme-backwards-compatibility();
@include test-all-theme-mixins-arguments();
@include test-individual-system-mixins-unwrap();
