// RIVER Design-System: System - Mixins / Utilities //

// Nixins for outputting our single-purpose utility classes here.
// Utilities can be defined with their own values, or refer to values from a design-map.
// Utilities need to be normalised before they can be output via these mixins.

// Utility classes have the format: {utility|property}--{value}
// The utility (sometimes referred to as the property) is optional,
// and may be skipped when you want the value to be the uility class, such as '.block' instead of '.display--block'.
// Note that if the value refers to a design system range,
// it will include the range name with "." separators accordingly (such as 'primary.normal').
// Examples: .block, .display--block, .color--base, .color--primary.normal, etc...

// Mixin to output utility classes, based on the given utility settings
@mixin design-output-utility($utilitySettings, $variation: '', $utilityType: null) {

    // Grab the utility settings
    // See system/settings => $design-settings.utilities for the full list of options
    $important: map-get($utilitySettings, 'important');
    $importantVariation: map-get($utilitySettings, 'importantVariation');
    $calculation: map-get($utilitySettings, 'calculation');
    $valuePrefix: map-get($utilitySettings, 'valuePrefix');
    $valueSuffix: map-get($utilitySettings, 'valueSuffix');
    $defaultValue: map-get($utilitySettings, 'defaultValue');
    $includeRange: map-get($utilitySettings, 'includeRange');
    $includeXero: map-get($utilitySettings, 'includeXero');

    // Properties: utility/property map
    // (or a single property such as 'display' to output the property only, such as '.block' instead of '.display--block')
    $utilityProperties: map-get($utilitySettings, 'properties');
    @if(type-of($utilityProperties) == 'string') {
        $utilityProperties: (null: $utilityProperties);
    }

    // Values: label/value map
    // (or a '@group.range' design system reference, followed by an optional whitelist)
    $utilityValues: map-get($utilitySettings, 'values');
    $utilityValuesDesignRange: null;
    $utilityValuesDesignGroup: null;
    $utilityValuesFromDesignMap: false;
    $utilityValuesWhitelist: null;
    @if(not(type-of($utilityValues) == 'map') and length($utilityValues) >= 1 and str-char-first(nth($utilityValues, 1)) == '@') {
        $utilityValuesDesignRange: str-slice(nth($utilityValues, 1), 2); // eg, '@sizes.size' => 'sizes.size'
        $utilityValuesDesignGroup: str-before($utilityValuesDesignRange, '.', true); // 'sizes'
        $utilityValuesWhitelist: if(length($utilityValues) > 1, list-remove-nth($utilityValues, 1), null);
        $utilityValues: if($utilityValuesDesignRange == $utilityValuesDesignGroup, design-group($utilityValuesDesignRange), design-range($utilityValuesDesignRange));
        $utilityValuesFromDesignMap: true;
    }

    // Loop each utility/property pair
    // eg, 'm': 'margin', 'mx': 'margin-left' 'margin-right' ($utilityName may be null, in which case the $property should be used as the utility name)
    // eg, 'color': 'color', 'bg': 'background-color', 'border': 'border-color' ...
    @each $utilityName, $properties in $utilityProperties {
        // A reminder, in SASS we have no break or continue within loops >< 

        // Loop each label/value pair
        // eg, 'small' => 1rem, 'normal' => 2rem, 'large' => 3rem
        // eg, 'between' => 'space-between', 'around' => 'space-around',
        // eg, design-map @colors.primary ...
        @each $label, $value in $utilityValues {

            // If the value is a map, recursively call design-output-utility(...)
            // Most likely the case if this has been called on an entire design group, such as '@colors'
            @if(type-of($value) == 'map') {
                // Note the utility setting changes to pass forward to the recursive call
                $utilityValuesForward: map-get($utilitySettings, 'values');
                $utilityValuesForward: set-nth($utilityValuesForward, 1, nth($utilityValuesForward, 1) + '.' + $label); // eg, '@colors' => '@colors.brand-primary' (accounts for whitelist)

                $utilitySettingsForward: map-merge($utilitySettings, (
                    'properties': ($utilityName: $properties), // Scope down to this property only
                    'valuePrefix': if($valuePrefix, $valuePrefix, '') + $label + design-setting('lang.utilities.range-seperator'),
                    'values': $utilityValuesForward,
                ));
                @include design-output-utility($utilitySettingsForward, $variation, $utilityType);
            }

            // Otherwise, validate and output the utility value accordingly
            @if(type-of($value) != 'map' and length($value) > 0) {
                // Check if we have a valid label/value pair
                @if(
                    // Include explicit values (not from a design-map)
                    not($utilityValuesFromDesignMap)
                    // Only include design-map values that meet a range of criteria
                    or (
                        // Ignore map keys that start with '_', as these refer to settings for the map
                        (
                            type-of($label) != 'string' or str-char-first($label) != '_'
                        )
                        // Allow the range if it's enabled; or if it's a reference (we also allow 0 for convenience)
                        and (
                            $includeRange
                            or design-key-is-reference($utilityValues, $label)
                            or ($label == 0 and $includeXero)
                            or ($label == $defaultValue)
                        )
                        // Only allow this value if it passes our whitelist
                        and (
                            not($utilityValuesWhitelist)
                            or index($utilityValuesWhitelist, $label)
                        )
                    )
                    
                ) {
                    // Manage the value (account for design-maps, and '@' design system references)
                    @if($utilityValuesFromDesignMap) {
                        $value: design-variable(($utilityValuesDesignRange, $label));
                    }
                    @if(type-of($label) == 'string' and str-char-last($label) == '@') {
                        $label: str-slice($label, 1, str-length($label) - 1); // Remove '@' suffix
                        $value: design-variable-resolve($value);
                    }
                    @if(type-of($label) == 'string' and str-char-last($label) == '#') {
                        $label: str-slice($label, 1, str-length($label) - 1); // Remove '#' suffix
                        $value: design-value-resolve($value);
                    }
                    $value: if($calculation, calc(#{$value} * #{$calculation}), $value); // Final value

                    // Manage the classname (account for top-level class prefixes, such as a design-map with top-level values)
                    $variationPrefix: if($variation and $variation != '', $variation + design-setting('lang.utilities.variation-seperator'), '');
                    $className: ''
                        + $variationPrefix // variation; eg, '', 'md', 'hover', etc
                        + if($utilityName and $utilityName != '', $utilityName + design-setting('lang.utilities.value-seperator'), '') // utility name; eg, '', 'color--', etc
                        + if($valuePrefix and $valuePrefix != '', $valuePrefix, '') // value prefix; eg, 'primary.' for @colors.primary.normal, etc
                        + $label
                        + if($valueSuffix and $valueSuffix != '', $valueSuffix, '');

                    // Utility - defaultValue (skip the label)
                    @if($utilityValuesFromDesignMap and $label == $defaultValue) {
                        // Class name for the default value without the label
                        // eg, '.color--primary.normal' => '.color--primary', '.color--normal' => '.color', etc
                        $classNameDefault: if(
                            str-contains($className, design-setting('lang.utilities.range-seperator') + $label),
                            str-replace($className, design-setting('lang.utilities.range-seperator') + $label, ''),
                            str-replace($className, design-setting('lang.utilities.value-seperator') + $label, '')
                        );
                        // Standard utility class
                        @include design-output-utility-class(
                            $classNameDefault,
                            $properties,
                            if($important, $value + ' !important', $value) // value
                        );
                    }

                    // Utility types - states
                    @if($utilityType == 'states' or $utilityType == 'active') {
                        // Focus utility class
                        @include design-output-utility-class-states-active(
                            $className, // class
                            $properties, // properties
                            if($important, $value, $value) // value
                        );
                    }
                    @if($utilityType == 'states' or $utilityType == 'hover') {
                        // Hover utility class
                        @include design-output-utility-class-states-hover(
                            $className, // class
                            $properties, // properties
                            if($important, $value, $value) // value
                        );
                    }
                    @if($utilityType == 'states' or $utilityType == 'focus') {
                        // Focus utility class
                        @include design-output-utility-class-states-focus(
                            $className, // class
                            $properties, // properties
                            if($important, $value, $value) // value
                        );
                    }
                    @if($utilityType == 'states' or $utilityType == 'disabled') {
                        // Focus utility class
                        @include design-output-utility-class-states-disabled(
                            $className, // class
                            $properties, // properties
                            if($important, $value, $value) // value
                        );
                    }

                    // Utility type - media
                    @if($utilityType == 'media') {
                        // Important utility class (we already expect to be within the media query)
                        @include design-output-utility-class(
                            $className, // class
                            $properties, // properties
                            $value + ' !important' // value
                        );
                    }

                    // Utility types - standard
                    @if(not($utilityType)) {
                        // Standard utility class
                        @include design-output-utility-class(
                            $className, // class
                            $properties, // properties
                            if($important, $value + ' !important', $value) // value
                        );

                        // Important utility class
                        @if($importantVariation) {
                            @include design-output-utility-class(
                                $className + '!', // class
                                $properties, // properties
                                $value + ' !important' // value
                            );
                        }
                    }

                }// valid label/value pair
            }// type-of($value) != 'map'

        }

    }
}

// Assorted mixins to output utility classes directly
@mixin design-output-utility-class($class, $properties, $value) {
    // Output class
    .#{design-transform-class-name($class)} {
        // Loop each property, in case a list of multiple properties is provided
        // eg, 'mx' => 'margin-left', 'margin-right'... (this will generally be a single property)
        @each $property in $properties {
            #{$property}: #{$value};
        } 
    }
}
@mixin design-output-utility-class-states-active($class, $properties, $value) {
    // Enforce value
    $value: $value + ' !important';

    // Output classes
    .#{design-transform-class-name('active:' + $class)}:active {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
}
@mixin design-output-utility-class-states-hover($class, $properties, $value) {
    // Enforce value
    $value: $value + ' !important';

    // Output classes
    .#{design-transform-class-name('hover:' + $class)}:hover {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
    .group:hover .#{design-transform-class-name('group-hover:' + $class)} {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
}
@mixin design-output-utility-class-states-focus($class, $properties, $value) {
    // Enforce value
    $value: $value + ' !important';

    // Output classes
    .#{design-transform-class-name('focus:' + $class)}:focus {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
    .#{design-transform-class-name('focus-within:' + $class)}:hover {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
    .group:focus .#{design-transform-class-name('group-focus:' + $class)} {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
}
@mixin design-output-utility-class-states-disabled($class, $properties, $value) {
    // Enforce value
    $value: $value + ' !important';

    // Output classes
    .#{design-transform-class-name('disabled:' + $class)}:disabled {
        @each $property in $properties {
            #{$property}: #{$value};
        }
    }
}
