@use 'sass:list';
@use 'sass:map';
@use 'sass:string';
////
/// @group utils.misc
////

@use 'collections.scss' as *;
@use 'flatmap.scss' as *;
@use 'strings.scss' as *;
@use 'units.scss' as *;

/// Simple helper that places the content within a root selector (and outside of any nesting)

@mixin root() {
    @at-root :root {
        @content;
    }
}

/// Helper for a hover state selector that only applies hover styles when not disabled.

@mixin hover() {
    &:hover:not(:disabled) {
        @content;
    }
}

/// Helper for an active state selector that only applies hover styles when not disabled.

@mixin active() {
    &:active:not(:disabled) {
        @content;
    }
}

/// Adds a focus-visible state selector to the element
///
/// @example
/// .item { @include focus() {...} }  => .item:focus-visible {...}

@mixin focus() {
    &:focus-visible {
        @content;
    }
}

/// Shortcut for checked state (and a reminder to use aria to manage this)
///
/// @example
/// .item { @include aria-checked() {...} }  => .item:[aria-checked] {...}

@mixin aria-checked() {
    &[aria-checked] {
        @content;
    }
}

/// Shortcut for checked state (and a reminder to use aria to manage this)
///
/// @example
/// .item { @include disabled() {...} }  =>
/// .item:disabled, .item:disabled:hover, .item:disabled:active, .item:disabled[aria-checked] {...}

@mixin disabled() {
    &:disabled,
    &:disabled:hover,
    &:disabled:active,
    &:disabled[aria-checked] {
        @content;
    }
}

/// Style the placeholder text for a text input element
/// @example
/// input {
///     %%include placeholder {
///         /* styles for placeholder here */
///     }
/// }

@mixin placeholder {
    &::placeholder {
        @content;
    }
    &::-webkit-input-placeholder {
        @content;
    }
    &:-moz-placeholder {
        @content;
    }
    &::-moz-placeholder {
        @content;
    }
    &:-ms-input-placeholder {
        @content;
    }
}

/// Disable text selection on an element

@mixin no-select {
    -webkit-touch-callout: none;
    user-select: none;
}

/// Use the (now broadly supported) line-clamp property to restrict the
/// number of lines that are shown in a text element.
///
/// @param {number} $lines The maximum number of lines to be shown
///
/// @example  line-clamp(3) // =>
/// display: -webkit-box;
/// -webkit-line-clamp: 3;
/// -webkit-box-orient: vertical;
/// overflow: hidden;

@mixin line-clamp($lines) {
    display: -webkit-box;
    -webkit-line-clamp: $lines;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/// Shortcut for the ::before pseudo element with common defaults for content
/// and display.
///
/// @param {*} $content [''] The value for the content property.
/// @param {*} $display [block] The value for the display property.
///
/// @example  before('hello, world') // =>
/// content: "";
/// display: block;

@mixin before($content: '', $display: block) {
    &::before {
        content: $content;
        display: $display;
        @content;
    }
}

/// Shortcut for the ::after pseudo element with common defaults for content
/// and display.
///
/// @param {*} $content [''] The value for the content property.
/// @param {*} $display [block] The value for the display property.
///
/// @example  after('goodbye, world') // =>
/// content: "";
/// display: block;

@mixin after($content: '', $display: block) {
    &::after {
        content: $content;
        display: $display;
        @content;
    }
}

/// Create a triangle element using the border properties
///
/// @param {string} $direction The direction the arrow is pointing (up, down, left, right, etc.)
/// @param {color} $color The fill color for the arrow
/// @param {length} $height The height of the arrow
/// @param {length} $width  The width of the arrow
///
/// @example triangle('up', 'red', 10px, 2px)

@mixin triangle($direction, $color, $height, $width) {
    border-style: solid;
    height: 0;
    width: 0;

    @if ($direction == 'up') {
        border-color: transparent transparent $color transparent;
        border-width: 0 ($width * 0.5) $height ($width * 0.5);
    }
    @if ($direction == 'down') {
        border-color: $color transparent transparent transparent;
        border-width: $height ($width * 0.5) 0 ($width * 0.5);
    }
    @if ($direction == 'right') {
        border-color: transparent transparent transparent $color;
        border-width: ($height * 0.5) 0 ($height * 0.5) $width;
    }
    @if ($direction == 'left') {
        border-color: transparent $color transparent transparent;
        border-width: ($height * 0.5) $width ($height * 0.5) 0;
    }
    @if ($direction == 'up-left') {
        border-color: $color transparent transparent transparent;
        border-width: $height $width 0 0;
    }
    @if ($direction == 'up-right') {
        border-color: transparent $color transparent transparent;
        border-width: 0 $width $height 0;
    }
    @if ($direction == 'down-left') {
        border-color: transparent transparent transparent $color;
        border-width: $height 0 0 $width;
    }
    @if ($direction == 'down-right') {
        border-color: transparent transparent $color transparent;
        border-width: 0 0 $height $width;
    }
}

/// Sets a generated checked pattern background svg image on an element
///
/// @param {*} $width [12px] The width of an individual square in the checked pattern
/// @param {*} $color [#c8c8c8] The first of two colors in the check pattern
/// @param {*} $color-alt [transparent] The second of two colors in the check pattern
///
/// @example @include checkered-background(20px, green) // => creates a green and transparent checkered background where each square is 20px x 20px

@mixin checkered-background($width: 12px, $color: rgba(black, 0.1), $color-alt: transparent) {
    background-size: #{$width * 2} #{$width * 2};
    background-repeat: repeat;
    background-image: inline-svg(
        '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#{$color}" d="M0 0h10v10H0z"/><path fill="#{$color-alt}" d="M10 0h10v10H10zM0 10h10v10H0z"/><path fill="#{$color}" d="M10 10h10v10H10z"/></g></svg>'
    );
}

/// Finds and replaces keys in a map
///
/// @example
/// $map: map-key-replace(( oldkey: 'value'), (oldkey: newkey)); // => (newkey: 'value')

@function map-key-replace($map, $key-replacements) {
    $remap: ();
    @each $key, $val in $map {
        $prop: map-get-default($key-replacements, $key, $key);
        @if (not is-list($prop)) {
            $prop: ($prop);
        }
        @each $propname in $prop {
            $remap: map.merge(
                $remap,
                (
                    $propname: $val,
                )
            );
        }
    }

    @return $remap;
}

/// Generates css from a map of properties and values. Properties can be remapped with aliases or
/// ignored with a start pattern.
///
/// @param {map} $map The property map used to generate the css
/// @param {map} $aliases Property aliases map (e.g (-px-accent: background-color))
/// @param {list} $map-states If provided, the specified map-type properties will be interpreted as
/// sub states (e.g. (hover: (...)) becomes &:hover {...})
/// @param {string} $ignore-start-pattern Properties that start with this will be ignored
///
/// @todo need a better description of all of these weird parameters and why they exist (or else better api)
///
/// @example @include css-map-dpr(( color: blue, background-color: green, font-family: sans-serif));

@mixin css-map($map, $aliases: (), $ignore: '__', $auto-alias-common-states: true) {
    @if ($auto-alias-common-states) {
        $common-states: (
            hover: '&:hover',
            hover-enabled: '&:hover:enabled',
            active: '&:active',
            active-enabled: '&:active:enabled',
            focus: '&:focus-visible',
            disabled: '&:disabled, &:disabled:hover, &:disabled:active, &:disabled[aria-checked]',
            checked: '&:checked, &[aria-checked="true"]',
            readonly: '&:read-only, &:read-only:hover, &:read-only:active',
            read-only: '&:read-only, &:read-only:hover, &:read-only:active',
            before: '&::before',
            after: '&::after',
            first-child: '&:first-child',
            last-child: '&:last-child',
            nth-even: '&:nth-child(even)',
            nth-odd: '&:nth-child(odd)',
            visited: '&:visited',
            required: '&:required',
            valid: '&:valid',
            invalid: '&:invalid',
            optional: '&:optional',
            placeholder: '&::placeholder',
        );
        $aliases: map.merge($common-states, $aliases);
    }
    // walk through $map and replace any aliases with their counterparts
    $remap: map-key-replace($map, $aliases);

    // remove keys that start with $ignore
    @each $key, $val in $remap {
        @if starts-with($key, $ignore) {
            $remap: map.remove($remap, $key);
        }
    }

    // output non-states first
    @each $key, $val in $remap {
        @if (is-map($val) or (is-list($val) and list.length($val) == 0)) {
            // do nothing (this is a map or an empty list)
        } @else {
            #{$key}: $val;
        }
    }

    // now output mapped states
    @each $key, $val in $remap {
        @if (is-map($val) or (is-list($val) and list.length($val) == 0)) {
            $state-map: map.get($remap, $key);
            $state-key: string.unquote($key);
            #{$state-key} {
                @include css-map($state-map, $aliases, $ignore);
            }
        }
    }
}

/// Generates a loud striped background gradient that can be used when debugging (especially helpful for scrolling)
///
/// @param {*} $color1 [yellowgreen] The first color in the gradient
/// @param {*} $color2 [gold] The second color in the gradient
/// @example debug-zebra() // =>

@mixin debug-zebra($color1: yellowgreen, $color2: gold) {
    background-image: repeating-linear-gradient(-45deg, $color1 0 20px, $color2 20px 40px);
}

/// Rule that only includes the content when the browser is Safari

@mixin only-safari() {
    @media not all and (min-resolution: 0.001dpcm) {
        @supports (-webkit-appearance: none) {
            @content;
        }
    }
}

/// Produces a css calc expression that adds two values (or subtracts if the second value is
/// negative)

@function calc-add($a, $b) {
    @if ($b > 0) {
        @return calc(#{$a} + #{$b});
    }
    @return calc(#{$a} - #{$b * -1});
}
