//
// Copyright 2017 Google Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

@use 'sass:color';
@use 'sass:list';
@use 'sass:map';
@use 'sass:math';
@use 'sass:meta';
@use 'sass:string';
@use './custom-properties';
@use './keys';

@function _linear-channel-value($channel-value) {
  $normalized-channel-value: math.div($channel-value, 255);
  @if $normalized-channel-value < 0.03928 {
    @return math.div($normalized-channel-value, 12.92);
  }

  @return math.pow(math.div($normalized-channel-value + 0.055, 1.055), 2.4);
}

// Calculate the luminance for a color.
// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
@function luminance($color) {
  $red: _linear-channel-value(color.red($color));
  $green: _linear-channel-value(color.green($color));
  $blue: _linear-channel-value(color.blue($color));

  @return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue;
}

// Calculate the contrast ratio between two colors.
// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
@function contrast($back, $front) {
  $backLum: luminance($back) + 0.05;
  $foreLum: luminance($front) + 0.05;

  @return math.div(math.max($backLum, $foreLum), math.min($backLum, $foreLum));
}

// Determine whether the color is 'light' or 'dark'.
@function tone($color) {
  @if $color == 'dark' or $color == 'light' {
    @return $color;
  }

  @if meta.type-of($color) != 'color' {
    @warn '#{$color} is not a color. Falling back to "dark" tone.';
    @return 'dark';
  }

  $minimumContrast: 3.1;

  $lightContrast: contrast($color, white);
  $darkContrast: contrast($color, rgba(black, 0.87));

  @if ($lightContrast < $minimumContrast) and ($darkContrast > $lightContrast) {
    @return 'light';
  } @else {
    @return 'dark';
  }
}

// Determine whether to use dark or light text on top of given color to meet accessibility standards for contrast.
// Returns 'dark' if the given color is light and 'light' if the given color is dark.
@function contrast-tone($color) {
  @return if(tone($color) == 'dark', 'light', 'dark');
}

///
/// @param $color Target color in any color format.
/// @return Returns hash in string format that uniquely represents
///     any given color format. Useful for generating unique keyframe names.
/// @example
///   `color-hash(#6200ee)` => "6200ee"
///   `color-hash(rgb(255, 112, 112))` => "ff7070"
///   `color-hash((varname: --my-fancy-color, fallback: teal))` => 'teal'
///   `color-hash((varname: --my-fancy-color, fallback: null))` => '--my-fancy-color'
///
@function color-hash($color) {
  @if custom-properties.is-custom-prop($color) {
    $color-value: custom-properties.get-fallback($color);

    @if (custom-properties.is-custom-prop-string($color-value)) {
      $varEndIndex: if(
        string.index($color-value, ', '),
        string.index($color-value, ', ') - 1,
        -2
      );
      @return string.slice($color-value, 5, $varEndIndex);
    }

    @if (meta.type-of($color-value) == 'color') {
      @return _get-hex-string($color-value);
    }

    @return custom-properties.get-varname($color);
  }

  @if meta.type-of($color) == 'string' {
    @return $color;
  }

  @return _get-hex-string($color);
}

@function _get-hex-string($color) {
  @return string.slice(color.ie-hex-str($color), 2); // Index starts at 1
}

//
// Main theme colors for your brand.
//
// If you're a user customizing your color scheme in SASS, these are probably the only variables you need to change.
//

$primary: #6200ee !default; // baseline purple, 500 tone
$on-primary: if(contrast-tone($primary) == 'dark', #000, #fff) !default;

// The $mdc-theme-accent variable is DEPRECATED - it exists purely for backward compatibility.
// The $mdc-theme-secondary* variables should be used for all new projects.
/// @deprecated - use $secondary
$accent: #018786 !default; // baseline teal, 600 tone
$secondary: $accent !default;
$on-secondary: if(contrast-tone($secondary) == 'dark', #000, #fff) !default;
$background: #fff !default; // White

$surface: #fff !default;
$on-surface: if(contrast-tone($surface) == 'dark', #000, #fff) !default;

$error: #b00020 !default;
$on-error: if(contrast-tone($error) == 'dark', #000, #fff) !default;

//
// Text colors according to light vs dark and text type.
//

$text-colors: (
  dark: (
    primary: rgba(black, 0.87),
    secondary: rgba(black, 0.54),
    hint: rgba(black, 0.38),
    disabled: rgba(black, 0.38),
    icon: rgba(black, 0.38),
  ),
  light: (
    primary: white,
    secondary: rgba(white, 0.7),
    hint: rgba(white, 0.5),
    disabled: rgba(white, 0.5),
    icon: rgba(white, 0.5),
  ),
) !default;

$text-emphasis: (
  high: 0.87,
  medium: 0.6,
  disabled: 0.38,
) !default;

@function ink-color-for-fill_($text-style, $fill-color) {
  $contrast-tone: contrast-tone($fill-color);

  @return map.get(map.get($text-colors, $contrast-tone), $text-style);
}

//
// Primary text colors for each of the theme colors.
//

/// @deprecated Use individual variables (`$primary`, `$secondary`). Do not
/// override this Map of variables.
$property-values: (
  primary: $primary,
  secondary: $secondary,
  background: $background,
  surface: $surface,
  error: $error,
  on-primary: $on-primary,
  on-secondary: $on-secondary,
  on-surface: $on-surface,
  on-error: $on-error,
  text-primary-on-background: ink-color-for-fill_(primary, $background),
  text-secondary-on-background: ink-color-for-fill_(secondary, $background),
  text-hint-on-background: ink-color-for-fill_(hint, $background),
  text-disabled-on-background: ink-color-for-fill_(disabled, $background),
  text-icon-on-background: ink-color-for-fill_(icon, $background),
  text-primary-on-light: ink-color-for-fill_(primary, light),
  text-secondary-on-light: ink-color-for-fill_(secondary, light),
  text-hint-on-light: ink-color-for-fill_(hint, light),
  text-disabled-on-light: ink-color-for-fill_(disabled, light),
  text-icon-on-light: ink-color-for-fill_(icon, light),
  text-primary-on-dark: ink-color-for-fill_(primary, dark),
  text-secondary-on-dark: ink-color-for-fill_(secondary, dark),
  text-hint-on-dark: ink-color-for-fill_(hint, dark),
  text-disabled-on-dark: ink-color-for-fill_(disabled, dark),
  text-icon-on-dark: ink-color-for-fill_(icon, dark),
) !default;

@include keys.set-values(
  $property-values,
  $options: (custom-property-prefix: theme)
);

// A copy of the property values Map that is used to detect compile-time changes
// for Angular support.
$_property-values-copy: $property-values;

/// Checks if the global $mdc-theme-property-values was dynamically changed at
/// compile time. Typically, $property-values is configured once, but a Sass
/// hack allows the variable to be changed multiple times and effectively
/// support dynamic values.
///
/// Angular uses this in their dynamic theming. This function checks if this
/// scenario has occurred and returns the current global value that should be
/// used instead of the key store value.
///
/// @deprecated The function should not be used externally. It will be removed
///     when $mdc-theme-property-values is fully deprecated and removed.
@function deprecated-get-global-theme-key-value-if-changed($key) {
  // Determine if we need to use a compile-time updated value to support
  // Angular.
  $current-global-value: map.get($property-values, $key);
  $configured-global-value: map.get($_property-values-copy, $key);
  @if $current-global-value != $configured-global-value {
    // $mdc-theme-property-values was changed at compile time. Return the new
    // compile-time value.
    @return (value: $current-global-value, changed: true);
  }

  @return (changed: false);
}

// @deprecated use theme.property(). If you need to ensure the value is not a
// custom property, use custom-properties.is-custom-prop() to check if the value
// is a custom prop, then custom-properties.get-fallback() to get its value.
// If `$style` is a color (a literal color value, `currentColor`, or a CSS custom property), it is returned verbatim.
// Otherwise, `$style` is treated as a theme property name, and the corresponding value from
// `$mdc-theme-property-values` is returned. If this also fails, an error is thrown.
//
// This is mainly useful in situations where `mdc-theme-prop` cannot be used directly (e.g., `box-shadow`).
//
// Examples:
//
// 1. mdc-theme-prop-value(primary) => "#6200ee"
// 2. mdc-theme-prop-value(blue)    => 'blue'
//
// NOTE: This function must be defined in _variables.scss instead of _functions.scss to avoid circular imports.
@function prop-value($style) {
  @if custom-properties.is-custom-prop($style) {
    @return custom-properties.get-fallback($style);
  }

  @if is-valid-theme-prop-value_($style) {
    @return $style;
  }

  @if is-theme-key($style) {
    // Determine if we need to use a compile-time updated value to support
    // Angular.
    $result: deprecated-get-global-theme-key-value-if-changed($style);
    @if map.get($result, changed) {
      @return map.get($result, value);
    }
  }

  @return keys.resolve($style);
}

// NOTE: This function must be defined in _variables.scss instead of _functions.scss to avoid circular imports.
@function accessible-ink-color($fill-color, $text-style: primary) {
  $fill-color-value: prop-value($fill-color);
  $color-map-for-tone: map.get($text-colors, contrast-tone($fill-color-value));

  @if not map.has-key($color-map-for-tone, $text-style) {
    @error "Invalid $text-style: '#{$text-style}'. Choose one of: #{map.keys($color-map-for-tone)}";
  }

  @return map.get($color-map-for-tone, $text-style);
}

// NOTE: This function is depended upon by mdc-theme-prop-value (above) and thus must be defined in this file.
@function is-valid-theme-prop-value_($style) {
  @if $style == null {
    @return false;
  }

  @return meta.type-of($style) == 'color' or $style == 'currentColor' or
    str_slice($style, 1, 4) == 'var(' or $style == 'inherit' or $style ==
    'transparent' or
    // NOTE: `GrayText` is deprecated, but is the only feasible way to convey the
    // correct high-contrast mode colors in alignment with Windows system colors.
    $style == 'GrayText';
}

@function text-emphasis($emphasis) {
  @return map.get($text-emphasis, $emphasis);
}

@function is-theme-key($style) {
  @return map.has-key($property-values, $style);
}

@function get-theme-keys() {
  @return map.keys($property-values);
}

///
/// @param {Color|String} Color property key name (i.e., `primary`, `secondary`,
///     etc).
/// @return Returns custom property map containing CSS custom property and
///     fallback value (i.e., (varname: ..., fallback: ...). Returns color if
///     valid color value is provided. Throws error otherwise.
/// @examples
///   1. get-custom-property(primary)
///   => (varname: --mdc-theme-primary, fallback: #6200ee)
///
///   2. get-custom-property(#fff)
///   => #fff
///
@function get-custom-property($color) {
  $is-tokens-custom-prop: meta.type-of($color) == 'string' and
    string.index($color, '--') != null;
  @if custom-properties.is-custom-prop($color) or
    $is-tokens-custom-prop or
    is-valid-theme-prop-value_($color)
  {
    @return $color;
  } @else if is-theme-key($color) {
    $custom-prop: keys.create-custom-property($color);

    // Determine if we need to use a compile-time updated value to support
    // Angular.
    $result: deprecated-get-global-theme-key-value-if-changed($color);
    @if map.get($result, changed) {
      $custom-prop: custom-properties.set-fallback(
        $custom-prop,
        map.get($result, value)
      );
    }

    @return $custom-prop;
  } @else {
    @error "Invalid theme property: '#{$color}'. Choose one of: #{get-theme-keys()}";
  }
}
