//
// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// [Modified by Sandlada & Kai Orion]
//
// Copyright 2025 Sandlada & Kai Orion
// SPDX-License-Identifier: MIT
//

@use 'sass:map';
@use 'sass:math';
@use '../../../themes';
@use '../../../tokens';

// Motion token values.
$_motion: tokens.md-sys-motion-values();
// The stroke width of the icon marks.
$_mark-stroke: 2px;
// The coordinates in an 18px viewBox of the bottom left corner of the
// indeterminate icon. Y is negative to fix an issue in Safari (see below).
$_indeterminate-bottom-left: 4px, -10px;
// The coordinates in an 18px viewBox of the bottom left corner of the
// checkmark icon. Y is negative to fix an issue in Safari (see below).
$_checkmark-bottom-left: 7px, -14px;

@mixin styles() {
    $tokens: tokens.md-comp-checkbox-values();

    & {
        border-start-start-radius: map.get($tokens, 'container-shape-start-start');
        border-start-end-radius: map.get($tokens, 'container-shape-start-end');
        border-end-end-radius: map.get($tokens, 'container-shape-end-end');
        border-end-start-radius: map.get($tokens, 'container-shape-end-start');
        display: inline-flex;
        height: map.get($tokens, 'container-size');
        position: relative;
        // Fix extra space when placed inside display: block
        vertical-align: top;
        width: map.get($tokens, 'container-size');
        -webkit-tap-highlight-color: transparent;
        cursor: pointer;
        flex-grow: 0;
        flex-shrink: 0;
        place-content: center;
        place-items: center;
    }

    &.disabled,
    &:disabled {
        cursor: default;
    }

    &[touch-target='wrapper'] {
        margin: max(0px, ((48px - map.get($tokens, 'container-size')) / 2));
    }

    & [data-component='focus-ring'] {
        height: 44px;
        inset: unset;
        width: 44px;
    }

    // <input> is also the touch target
    & input {
        appearance: none;
        height: 48px;
        margin: 0;
        opacity: 0;
        outline: none;
        position: absolute;
        width: 48px;
        z-index: 1;
        cursor: inherit;
    }

    &[touch-target='none'] input {
        height: 100%;
        width: 100%;
    }

    & .outline,
    & .background,
    & .icon {
        inset: 0;
        position: absolute;
    }

    & .outline,
    & .background {
        border-radius: inherit;
    }

    & .outline {
        border-color: map.get($tokens, 'outline-color');
        border-style: solid;
        border-width: map.get($tokens, 'outline-width');
        box-sizing: border-box;
    }

    & .background {
        background-color: map.get($tokens, 'selected-container-color');
    }

    // Background and icon transitions.
    & .background,
    & .icon {
        opacity: 0; // Background and icon fade in
        transition-duration: 150ms, 50ms; // Exit duration for scale and opacity.
        transition-property: transform, opacity;
        // Exit easing function for scale, linear for opacity.
        transition-timing-function: map.get($_motion, easing-emphasized-accelerate), linear;
        transform: scale(0.6); // Background and icon scale from 60% to 100%.
    }

    &:where(.selected) :is(.background, .icon) {
        opacity: 1;
        // Enter duration for scale and opacity.
        transition-duration: 350ms, 50ms;
        // Enter easing function for scale, linear for opacity.
        transition-timing-function: map.get($_motion, easing-emphasized-decelerate), linear;
        transform: scale(1);
    }

    & {
        @include themes.md-comp-ripple-theme(
            (
                hover-color: map.get($tokens, 'hover-state-layer-color'),
                hover-opacity: map.get($tokens, 'hover-state-layer-opacity'),
                pressed-color: map.get($tokens, 'pressed-state-layer-color'),
                pressed-opacity: map.get($tokens, 'pressed-state-layer-opacity'),
            )
        );
    }

    &.selected {
        @include themes.md-comp-ripple-theme(
            (
                hover-color: map.get($tokens, 'selected-hover-state-layer-color'),
                hover-opacity: map.get($tokens, 'selected-hover-state-layer-opacity'),
                pressed-color: map.get($tokens, 'selected-pressed-state-layer-color'),
                pressed-opacity: map.get($tokens, 'selected-pressed-state-layer-opacity'),
            )
        );
    }

    & [data-component='ripple'] {
        border-radius: map.get($tokens, 'state-layer-shape');
        height: map.get($tokens, 'state-layer-size');
        inset: unset;
        width: map.get($tokens, 'state-layer-size');
    }

    & .icon {
        // The icon is created with two <rect> marks for animation:
        // 1. Short end
        //   - the smaller leading part of the checkmark
        //   - hidden behind long end for indeterminate mark
        // 2. Long end
        //   - the larger trailing part of the checkmark
        //   - the entirety of the indeterminate mark
        fill: map.get($tokens, 'selected-icon-color');
        height: map.get($tokens, 'icon-size');
        width: map.get($tokens, 'icon-size');
    }

    // The short end of the checkmark. Initially hidden underneath the
    // indeterminate mark.
    & .mark.short {
        height: $_mark-stroke;
        transition-property: transform, height;
        width: $_mark-stroke;
    }

    // The long end of the checkmark. Initially the indeterminate mark.
    & .mark.long {
        height: $_mark-stroke;
        transition-property: transform, width;
        width: 10px;
    }

    // Exit duration and easing.
    & .mark {
        animation-duration: 150ms;
        animation-timing-function: map.get($_motion, easing-emphasized-accelerate);
        transition-duration: 150ms;
        transition-timing-function: map.get($_motion, easing-emphasized-accelerate);
    }

    // Enter duration and easing.
    &.selected .mark {
        animation-duration: 350ms;
        animation-timing-function: map.get($_motion, easing-emphasized-decelerate);
        transition-duration: 350ms;
        transition-timing-function: map.get($_motion, easing-emphasized-decelerate);
    }

    // Creates the checkmark icon.
    &.checked,
    // Keep the checkmark shape when unselecting a checked checkbox.
    &.prev-checked.unselected {
        & .mark {
            // Transform from the bottom left of the rectangles, whch turn into the
            // bottom-most point of the checkmark.
            // Fix Safari's transform-origin bug from "top left" to "bottom left"
            $scale: scaleY(-1);
            // Move the "bottom left" corner to the checkmark location.
            $translate: translate($_checkmark-bottom-left);
            // Rotate the checkmark.
            $rotate: rotate(45deg);
            transform: $scale $translate $rotate;
        }

        & .mark.short {
            // The right triangle that forms the short end has an X, Y length of
            // 4dp, 4dp. The hypoteneuse is √(4*4 + 4*4), which is the length of the
            // short end when checked.
            height: 1px * math.sqrt(32);
        }

        & .mark.long {
            // The right triangle that forms the long end has an X, Y length of
            // 8dp, 8dp. The hypoteneuse is √(8*8 + 8*8), which is the length of the
            // long end when checked.
            width: 1px * math.sqrt(128);
        }
    }

    // Creates the indeterminate icon.
    &.indeterminate,
    // Keep the indeterminate shape when unselecting an indeterminate checkbox.
    &.prev-indeterminate.unselected {
        .mark {
            transform: scaleY(-1) translate($_indeterminate-bottom-left) rotate(0deg);
        }
    }

    // When selecting an unselected checkbox, don't transition between the
    // checked and indeterminate states. The checkmark icon or indeterminate icon
    // should fade in from its final position.
    &.prev-unselected .mark {
        transition-property: none;
    }

    // When checking a checkbox, the long mark of the checkmark grows from the
    // bottom-most point of the checkmark. An animation provides the starting
    // width to animate from.
    &.prev-unselected.checked .mark.long {
        animation-name: prev-unselected-to-checked;
    }

    @keyframes prev-unselected-to-checked {
        from {
            width: 0;
        }
    }

    // States
    &:where(:hover) .outline {
        border-color: map.get($tokens, 'hover-outline-color');
        border-width: map.get($tokens, 'hover-outline-width');
    }

    &:where(:hover) .background {
        background: map.get($tokens, 'selected-hover-container-color');
    }

    &:where(:hover) .icon {
        fill: map.get($tokens, 'selected-hover-icon-color');
    }

    &:where(:focus-within) .outline {
        border-color: map.get($tokens, 'focus-outline-color');
        border-width: map.get($tokens, 'focus-outline-width');
    }

    &:where(:focus-within) .background {
        background: map.get($tokens, 'selected-focus-container-color');
    }

    &:where(:focus-within) .icon {
        fill: map.get($tokens, 'selected-focus-icon-color');
    }

    &:where(:active) .outline {
        border-color: map.get($tokens, 'pressed-outline-color');
        border-width: map.get($tokens, 'pressed-outline-width');
    }

    &:where(:active) .background {
        background: map.get($tokens, 'selected-pressed-container-color');
    }

    &:where(:active) .icon {
        fill: map.get($tokens, 'selected-pressed-icon-color');
    }

    // Don't animate to/from disabled states because the outline is hidden when
    // selected. Without this, there'd be a FOUC if the checkbox state is
    // programmatically  changed while disabled.
    &:where(.disabled, .prev-disabled) :is(.background, .icon, .mark) {
        animation-duration: 0s;
        transition-duration: 0s;
    }

    &:where(.disabled) .outline {
        border-color: map.get($tokens, 'disabled-outline-color');
        border-width: map.get($tokens, 'disabled-outline-width');
        opacity: map.get($tokens, 'disabled-container-opacity');
    }

    &:where(.selected.disabled) .outline {
        // Hide the outline behind the transparent selected container color.
        // This can be removed once disabled colors are flattened.
        visibility: hidden;
    }

    &:where(.selected.disabled) .background {
        // Set disabled opacity only when selected since opacity is used to show
        // or hide the container background.
        background: map.get($tokens, 'selected-disabled-container-color');
        opacity: map.get($tokens, 'selected-disabled-container-opacity');
    }

    &:where(.disabled) .icon {
        fill: map.get($tokens, 'selected-disabled-icon-color');
    }

    @media (forced-colors: active) {
        & .background {
            background-color: CanvasText;
        }

        &.selected.disabled .background {
            background-color: GrayText;
            opacity: 1;
        }

        & .outline {
            border-color: CanvasText;
        }

        &.disabled .outline {
            border-color: GrayText;
            opacity: 1;
        }

        & .icon {
            fill: Canvas;
        }
    }
}
