/**
 * @license
 * Copyright 2023 Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
// Copyright 2016 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.

// stylelint-disable selector-class-pattern --
// Selector '.forge-*' should only be used in this project.

@use 'sass:math';
@use 'sass:list';
@use 'sass:map';
@use '@material/elevation/elevation-theme' as mdc-elevation-theme;
@use '@material/feature-targeting/feature-targeting' as mdc-feature-targeting;
@use '@material/ripple/ripple-theme' as mdc-ripple-theme;
@use '@material/shape/mixins' as mdc-shape-mixins;
@use '@material/theme/css' as mdc-css;
@use '@material/theme/custom-properties' as mdc-custom-properties;
@use '@material/theme/replace' as mdc-replace;
@use '@material/theme/state' as mdc-state;
@use '@material/theme/theme-color' as mdc-theme-color;
@use '@material/theme/theme' as mdc-theme;
@use './fab-custom-properties';

$height: 56px !default;
$mini-height: 40px !default;
$shape-radius: 50% !default;
$ripple-target: '.forge-fab__ripple';

$_light-theme: (
  container-color: secondary,
  container-elevation: mdc-elevation-theme.get-elevation(6),
  container-height: 56px,
  container-shape: $shape-radius,
  container-width: 56px,
  focus-container-elevation: null,
  focus-icon-color: null,
  focus-state-layer-color: mdc-theme-color.$primary,
  focus-state-layer-opacity: null,
  hover-container-elevation: null,
  hover-icon-color: null,
  hover-state-layer-color: mdc-theme-color.$primary,
  hover-state-layer-opacity: null,
  icon-color: on-secondary,
  icon-size: 24px,
  pressed-container-elevation: null,
  pressed-icon-color: null,
  pressed-ripple-color: mdc-theme-color.$primary,
  pressed-ripple-opacity: null,
  focus-outline-color: null,
  focus-outline-width: null,
);

@mixin theme($theme) {
  @include mdc-theme.validate-theme($_light-theme, $theme);
  @include base-theme($theme);

  $shape-radius: map.get($theme, container-shape);
  @if $shape-radius {
    @include shape-radius($shape-radius);
  }
}

@mixin base-theme($theme) {
  @include container-color(map.get($theme, container-color));
  @include _container-elevation(
    (
      default: map.get($theme, container-elevation),
      hover: map.get($theme, hover-container-elevation),
      focus: map.get($theme, focus-container-elevation),
      pressed: map.get($theme, pressed-container-elevation),
    )
  );
  @include _container-height(map.get($theme, container-height));
  @include _container-width(map.get($theme, container-width));
  @include icon-size(map.get($theme, icon-size));
  @include _icon-color(
    (
      default: map.get($theme, icon-color),
      hover: map.get($theme, hover-icon-color),
      focus: map.get($theme, focus-icon-color),
      pressed: map.get($theme, pressed-icon-color),
    )
  );

  $opacity-map: (
    hover: map.get($theme, hover-state-layer-opacity),
    focus: map.get($theme, focus-state-layer-opacity),
    press: map.get($theme, pressed-ripple-opacity),
  );
  $hover-state-layer-color: map.get($theme, hover-state-layer-color);
  @if $hover-state-layer-color {
    @include ripple-color($hover-state-layer-color, $opacity-map: $opacity-map);
  }

  $focus-outline-color: map.get($theme, focus-outline-color);
  @if $focus-outline-color {
    @include focus-outline-color($focus-outline-color);
  }
  $focus-outline-width: map.get($theme, focus-outline-width);
  @if $focus-outline-width {
    @include focus-outline-width($focus-outline-width);
  }
}

@mixin ripple-color($color, $opacity-map: (), $query: mdc-feature-targeting.all()) {
  @include mdc-ripple-theme.states(
    $color,
    // $opacity-map: $opacity-map, // FORGE (modify): ripple doesn't work if this is set (introduced in MDC v11)
    $query: $query,
    $ripple-target: $ripple-target
  );
}

@mixin accessible($container-color, $query: mdc-feature-targeting.all()) {
  @include container-color($container-color, $query: $query);

  $fill-tone: mdc-theme-color.tone($container-color);

  @if ($fill-tone == 'dark') {
    @include ink-color(text-primary-on-dark, $query: $query);
    @include mdc-ripple-theme.states(
      text-primary-on-dark,
      $query: $query,
      $ripple-target: $ripple-target
    );
  } @else {
    @include ink-color(text-primary-on-light, $query: $query);
    @include mdc-ripple-theme.states(
      text-primary-on-light,
      $query: $query,
      $ripple-target: $ripple-target
    );
  }
}

@mixin container-color($color, $query: mdc-feature-targeting.all()) {
  $feat-color: mdc-feature-targeting.create-target($query, color);

  @include mdc-feature-targeting.targets($feat-color) {
    @include mdc-theme.property(background-color, $color);
  }
}

@mixin icon-size($width, $height: $width, $query: mdc-feature-targeting.all()) {
  $feat-structure: mdc-feature-targeting.create-target($query, structure);

  .forge-fab__icon {
    @include mdc-feature-targeting.targets($feat-structure) {
      width: $width;
      height: $height;
      font-size: $height;
    }
  }
}

@mixin ink-color($color, $query: mdc-feature-targeting.all()) {
  $feat-color: mdc-feature-targeting.create-target($query, color);

  @include mdc-feature-targeting.targets($feat-color) {
    &,
    &:not(:disabled) .forge-fab__icon,
    &:not(:disabled) .forge-fab__label,
    &:disabled .forge-fab__icon,
    &:disabled .forge-fab__label {
      @include mdc-theme.property(color, $color);
    }
  }
}

@mixin _container-height($height) {
  height: $height;
}

@mixin _container-width($width) {
  width: $width;
}

@mixin _icon-color($color-or-map) {
  &:not(:disabled) {
    @include _set-icon-color(mdc-state.get-default-state($color-or-map));

    &:hover {
      @include _set-icon-color(mdc-state.get-hover-state($color-or-map));
    }

    &:focus {
      @include _set-icon-color(mdc-state.get-focus-state($color-or-map));
    }

    &:active {
      @include _set-icon-color(mdc-state.get-pressed-state($color-or-map));
    }
  }

  &:disabled {
    @include _set-icon-color(mdc-state.get-disabled-state($color-or-map));
  }
}

@mixin _set-icon-color($color) {
  .forge-fab__icon {
    @include mdc-theme.property(color, $color);
  }
}

@mixin _container-elevation($elevation-or-map) {
  &:not(:disabled) {
    @include _set-container-elevation(
      mdc-state.get-default-state($elevation-or-map)
    );

    &:hover {
      @include _set-container-elevation(
        mdc-state.get-hover-state($elevation-or-map)
      );
    }

    &:focus {
      @include _set-container-elevation(
        mdc-state.get-focus-state($elevation-or-map)
      );
    }

    &:active {
      @include _set-container-elevation(
        mdc-state.get-pressed-state($elevation-or-map)
      );
    }
  }

  &:disabled {
    // FAB does not have disabled state. Use default state's elevation.
    @include _set-container-elevation(
      mdc-state.get-default-state($elevation-or-map)
    );
  }
}

@mixin _set-container-elevation($elevation) {
  @if $elevation and map.get($elevation, box-shadow) {
    @include mdc-elevation-theme.shadow(map.get($elevation, box-shadow));
  }

  @if $elevation and map.get($elevation, overlay-opacity) {
    @include mdc-elevation-theme.overlay-opacity(
      map.get($elevation, overlay-opacity)
    );
  }
}

///
/// Sets outline width only when button is in focus. Also sets padding to
/// include outline on focus (Helps prevent size jump on focus).
/// @param {Number} $width - Outline (border) width.
/// @param {Number|List} $padding [0] - Padding when button is not in focus.
///     Offsets padding based on given outline width on focus.
///
@mixin focus-outline-width(
  $width,
  $padding: 0,
  $query: mdc-feature-targeting.all()
) {
  $feat-structure: mdc-feature-targeting.create-target($query, structure);

  $padding: mdc-css.unpack-value($padding);
  $padding-fallbacks: (0 0 0 0);
  $is-padding-custom-prop: (false false false false);
  $is-width-custom-prop: mdc-custom-properties.is-custom-prop($width);
  $width-fallback: if(
    mdc-custom-properties.is-custom-prop($width),
    mdc-custom-properties.get-fallback($width),
    $width
  );
  $width: if(
    mdc-custom-properties.is-custom-prop($width),
    mdc-custom-properties.get-declaration-value($width),
    $width
  );

  // conform padding values and extract custom property metadata from them
  @for $i from 1 through list.length($padding) {
    $value: list.nth($padding, $i);
    $value-is-custom-prop: mdc-custom-properties.is-custom-prop($value);
    // css max will fail to compare a bare 0 to a px value
    $value: if($value == 0, 0, $value);
    $value-fallback: if(
      $value-is-custom-prop,
      mdc-custom-properties.get-fallback($value),
      $value
    );
    $value: if(
      $value-is-custom-prop,
      mdc-custom-properties.get-declaration-value($value),
      $value
    );

    $padding: list.set-nth($padding, $i, $value);
    $padding-fallbacks: list.set-nth($padding-fallbacks, $i, $value-fallback);
    $is-padding-custom-prop: list.set-nth(
      $is-padding-custom-prop,
      $i,
      $value-is-custom-prop
    );
  }

  // Padding should include outline width which will be set on focus.
  // sass math required for IE since IE doesn't support css max
  $padding-top-fallback: math.max(
    list.nth($padding-fallbacks, 1),
    $width-fallback
  );
  $padding-right-fallback: math.max(
    list.nth($padding-fallbacks, 2),
    $width-fallback
  );
  $padding-bottom-fallback: math.max(
    list.nth($padding-fallbacks, 3),
    $width-fallback
  );
  $padding-left-fallback: math.max(
    list.nth($padding-fallbacks, 4),
    $width-fallback
  );

  $padding-top: mdc-replace.replace-string(
    'max(paddingval, width)',
    (
      paddingval: list.nth($padding, 1),
      width: $width,
    )
  );
  $padding-right: mdc-replace.replace-string(
    'max(paddingval, width)',
    (
      paddingval: list.nth($padding, 2),
      width: $width,
    )
  );
  $padding-bottom: mdc-replace.replace-string(
    'max(paddingval, width)',
    (
      paddingval: list.nth($padding, 3),
      width: $width,
    )
  );
  $padding-left: mdc-replace.replace-string(
    'max(paddingval, width)',
    (
      paddingval: list.nth($padding, 4),
      width: $width,
    )
  );

  $top-has-custom-prop: $is-width-custom-prop or
    list.nth($is-padding-custom-prop, 1);
  @include mdc-css.declaration(
    padding-top,
    $padding-top-fallback,
    $gss: (alternate: $top-has-custom-prop)
  );
  @if $top-has-custom-prop {
    @include mdc-css.declaration(padding-top, $padding-top);
  }

  $right-has-custom-prop: $is-width-custom-prop or
    list.nth($is-padding-custom-prop, 2);
  @include mdc-css.declaration(
    padding-right,
    $padding-right-fallback,
    $gss: (alternate: $right-has-custom-prop)
  );
  @if $right-has-custom-prop {
    @include mdc-css.declaration(padding-right, $padding-right);
  }

  $bottom-has-custom-prop: $is-width-custom-prop or
    list.nth($is-padding-custom-prop, 3);
  @include mdc-css.declaration(
    padding-bottom,
    $padding-bottom-fallback,
    $gss: (alternate: $bottom-has-custom-prop)
  );
  @if $bottom-has-custom-prop {
    @include mdc-css.declaration(padding-bottom, $padding-bottom);
  }

  $left-has-custom-prop: $is-width-custom-prop or
    list.nth($is-padding-custom-prop, 4);
  @include mdc-css.declaration(
    padding-left,
    $padding-left-fallback,
    $gss: (alternate: $left-has-custom-prop)
  );
  @if $left-has-custom-prop {
    @include mdc-css.declaration(padding-left, $padding-left);
  }

  &:not(:disabled) {
    @include mdc-ripple-theme.focus() {
      @include mdc-feature-targeting.targets($feat-structure) {
        border-style: solid;
        @include mdc-theme.property(border-width, $width);

        // sass math required for IE since IE doesn't support css max
        $padding-top-fallback: math.abs(
          list.nth($padding-fallbacks, 1) - $width-fallback
        );
        $padding-right-fallback: math.abs(
          list.nth($padding-fallbacks, 2) - $width-fallback
        );
        $padding-bottom-fallback: math.abs(
          list.nth($padding-fallbacks, 3) - $width-fallback
        );
        $padding-left-fallback: math.abs(
          list.nth($padding-fallbacks, 4) - $width-fallback
        );

        // max(a, calc(a * -1)) is equivalent to math.abs
        $padding-top: mdc-replace.replace-string(
          'max(paddingcalc, calc(paddingcalc * -1))',
          (
            paddingcalc: 'calc(paddingval - width)',
            paddingval: list.nth($padding, 1),
            width: $width,
          )
        );
        $padding-right: mdc-replace.replace-string(
          'max(paddingcalc, calc(paddingcalc * -1))',
          (
            paddingcalc: 'calc(paddingval - width)',
            paddingval: list.nth($padding, 2),
            width: $width,
          )
        );
        $padding-bottom: mdc-replace.replace-string(
          'max(paddingcalc, calc(paddingcalc * -1))',
          (
            paddingcalc: 'calc(paddingval - width)',
            paddingval: list.nth($padding, 3),
            width: $width,
          )
        );
        $padding-left: mdc-replace.replace-string(
          'max(paddingcalc, calc(paddingcalc * -1))',
          (
            paddingcalc: 'calc(paddingval - width)',
            paddingval: list.nth($padding, 4),
            width: $width,
          )
        );

        @include mdc-css.declaration(
          padding-top,
          $padding-top-fallback,
          $gss: (alternate: $top-has-custom-prop)
        );
        @if $top-has-custom-prop {
          @include mdc-css.declaration(padding-top, $padding-top);
        }
        @include mdc-css.declaration(
          padding-right,
          $padding-right-fallback,
          $gss: (alternate: $right-has-custom-prop)
        );
        @if $right-has-custom-prop {
          @include mdc-css.declaration(padding-right, $padding-right);
        }
        @include mdc-css.declaration(
          padding-bottom,
          $padding-bottom-fallback,
          $gss: (alternate: $bottom-has-custom-prop)
        );
        @if $bottom-has-custom-prop {
          @include mdc-css.declaration(padding-bottom, $padding-bottom);
        }
        @include mdc-css.declaration(
          padding-left,
          $padding-left-fallback,
          $gss: (alternate: $left-has-custom-prop)
        );
        @if $left-has-custom-prop {
          @include mdc-css.declaration(padding-left, $padding-left);
        }
      }
    }
  }
}

///
/// Sets outline color only when button is in focus. Use `focus-outline-width()`
/// to set appropriate outline width.
/// @param {Color} $color - Outline (border) color.
///
@mixin focus-outline-color($color, $query: mdc-feature-targeting.all()) {
  $feat-color: mdc-feature-targeting.create-target($query, color);

  &:not(:disabled) {
    @include mdc-ripple-theme.focus() {
      @include mdc-feature-targeting.targets($feat-color) {
        @include mdc-theme.property(border-color, $color);
      }
    }
  }
}

@mixin shape-radius(
  $radius,
  $rtl-reflexive: false,
  $query: mdc-feature-targeting.all()
) {
  &:not(.forge-fab--extended) {
    // Do not specify $component-height for shape radius. FABs are circular,
    // which means they can use percentage border radius without resolving to
    // a component height.
    @include mdc-shape-mixins.radius($radius, $rtl-reflexive, $query: $query);

    #{$ripple-target} {
      @include mdc-shape-mixins.radius($radius, $rtl-reflexive, $query: $query);
    }
  }
}
