// 1. The placement logic works as follows:
//
//    1. The parent element is positioned relatively.
//    2. The child element is positioned absolutely into the corresponding position using the inset map.
//    3. The child element is then translated:
//       a) using the `--placement-offset` custom property to make space for an arrow or just to add an offset,
//       b) using the `--placement-offset-orthogonal` custom property to center the child element.
//
// 2. When using a positioning library, e.g. Floating UI, the placement offset is controlled by the library.
//
//    In this case, `inset` is set to `unset` because the library will look after it. Also, the
//    `--placement-offset-orthogonal` custom property is reset to 0 because the library will center the child element,
//    using the absolute position.
//
//    See https://floating-ui.com

@use 'sass:list';
@use 'sass:map';
@use 'sass:string';
@use 'list' as spirit-list;

$_hyphen: '-';

$_arrow-rotate-map: (
    top: 0deg,
    top-start: 0deg,
    top-end: 0deg,
    bottom: 180deg,
    bottom-start: 180deg,
    bottom-end: 180deg,
    left: -90deg,
    left-start: -90deg,
    left-end: -90deg,
    right: 90deg,
    right-start: 90deg,
    right-end: 90deg,
);

// 1.2
$_inset-map: (
    top: auto auto 100% 50%,
    top-start: auto auto 100% 0,
    top-end: auto 0 100% auto,
    bottom: 100% auto auto 50%,
    bottom-start: 100% auto auto 0,
    bottom-end: 100% 0 auto auto,
    left: 50% 100% auto auto,
    left-start: 0 100% auto auto,
    left-end: auto 100% 0 auto,
    right: 50% auto auto 100%,
    right-start: 0 auto auto 100%,
    right-end: auto auto 0 100%,
);

$_physical-placement-inverse-map: (
    top: bottom,
    bottom: top,
    left: right,
    right: left,
);

$_logical-placement-inverse-map: (
    start: end,
    end: start,
);

$_logical-to-physical-placement-map: (
    horizontal: (
        start: left,
        end: right,
    ),
    vertical: (
        start: top,
        end: bottom,
    ),
);

@function -get-arrow-translate-map($prefix) {
    @return (
        top: -50% 0%,
        top-start: var(--#{$prefix}-arrow-corner-offset) 0%,
        top-end: calc(-1 * var(--#{$prefix}-arrow-corner-offset)) 0%,
        bottom: -50% -100%,
        bottom-start: var(--#{$prefix}-arrow-corner-offset) -100%,
        bottom-end: calc(-1 * var(--#{$prefix}-arrow-corner-offset)) -100%,
        left: 50% -100%,
        left-start: calc(var(--#{$prefix}-arrow-width) / 2)
            calc(
                var(--#{$prefix}-arrow-width) / 2 - var(--#{$prefix}-arrow-height) +
                    var(--#{$prefix}-arrow-corner-offset)
            ),
        left-end: calc(var(--#{$prefix}-arrow-width) / 2)
            calc(var(--#{$prefix}-arrow-width) / -2 - var(--#{$prefix}-arrow-corner-offset)),
        right: -50% -100%,
        right-start: calc(var(--#{$prefix}-arrow-width) / -2)
            calc(
                var(--#{$prefix}-arrow-width) / 2 - var(--#{$prefix}-arrow-height) +
                    var(--#{$prefix}-arrow-corner-offset)
            ),
        right-end: calc(var(--#{$prefix}-arrow-width) / -2)
            calc(var(--#{$prefix}-arrow-width) / -2 - var(--#{$prefix}-arrow-corner-offset))
    );
}

// 1.3
@function -get-child-translate-map($prefix) {
    @return (
        top: var(--#{$prefix}-offset-orthogonal, -50%) calc(-1 * var(--#{$prefix}-offset, 0)),
        top-start: var(--#{$prefix}-offset-orthogonal, 0) calc(-1 * var(--#{$prefix}-offset, 0)),
        top-end: var(--#{$prefix}-offset-orthogonal, 0) calc(-1 * var(--#{$prefix}-offset, 0)),
        bottom: var(--#{$prefix}-offset-orthogonal, -50%) var(--#{$prefix}-offset, 0),
        bottom-start: var(--#{$prefix}-offset-orthogonal, 0) var(--#{$prefix}-offset, 0),
        bottom-end: var(--#{$prefix}-offset-orthogonal, 0) var(--#{$prefix}-offset, 0),
        left: calc(-1 * var(--#{$prefix}-offset, 0)) var(--#{$prefix}-offset-orthogonal, -50%),
        left-start: calc(-1 * var(--#{$prefix}-offset, 0)) var(--#{$prefix}-offset-orthogonal, 0),
        left-end: calc(-1 * var(--#{$prefix}-offset, 0)) var(--#{$prefix}-offset-orthogonal, 0),
        right: var(--#{$prefix}-offset, 0) var(--#{$prefix}-offset-orthogonal, -50%),
        right-start: var(--#{$prefix}-offset, 0) var(--#{$prefix}-offset-orthogonal, 0),
        right-end: var(--#{$prefix}-offset, 0) var(--#{$prefix}-offset-orthogonal, 0)
    );
}

// Function to get the cross-axis direction
// Example: get-cross-axis-direction('left') will return 'vertical'
// Example: get-cross-axis-direction('bottom') will return 'horizontal'
@function get-cross-axis-direction($main-axis) {
    @return if(string.index($main-axis, 'left') or string.index($main-axis, 'right'), 'vertical', 'horizontal');
}

// Function to check if a placement is logical
// Example: is-logical('top') will return false
// Example: is-logical('start') will return true
@function is-logical($placement) {
    @return if(string.index($placement, 'start') or string.index($placement, 'end'), true, false);
}

// Function to translate logical placement to its physical equivalent
// Example: translate-logical-to-physical('start', $direction: 'horizontal') will return 'left'
@function translate-logical-to-physical($logical, $direction) {
    @return map.get($_logical-to-physical-placement-map, $direction, $logical);
}

// Function to transform axis
// Example: transform-axis('left') will return 'left' (no transformation)
// Example: transform-axis('left', $inverse: true) will return 'right'
// Example: transform-axis('start', $inverse: false, $physical-direction: 'horizontal') will return 'left'
// Example: transform-axis('start', $inverse: true, $physical-direction: 'horizontal') will return 'right'
@function transform-axis($axis, $inverse: false, $physical-direction: null) {
    $axis-transformed: $axis;

    @if is-logical($axis-transformed) {
        @if $inverse {
            $axis-transformed: map.get($_logical-placement-inverse-map, $axis);
        }

        @if $physical-direction {
            $axis-transformed: translate-logical-to-physical($axis-transformed, $physical-direction);
        }
    } @else if $inverse {
        $axis-transformed: map.get($_physical-placement-inverse-map, $axis);
    }

    @return $axis-transformed;
}

// Function to transform placement
// Example: transform('top-start') will return 'top-start' (no transformation)
// Example: transform('top-start', $main-axis-inverse: true) will return 'bottom-start'
// Example: transform('top-start', $main-axis-inverse: true, $cross-axis-inverse: true) will return 'bottom-end'
// Example: transform('top-start', $main-axis-inverse: true, $cross-axis-inverse: true, $cross-axis-physical: true) will return 'bottom-right'
// Example: transform('top-start', $join-with: ' ') will return 'top start'
@function transform(
    $placement,
    $main-axis-inverse: false,
    $cross-axis-inverse: false,
    $cross-axis-physical: false,
    $join-with: $_hyphen
) {
    $placement-chunks: string.split($placement, $_hyphen);
    $main-axis: list.nth($placement-chunks, 1);
    $main-axis-transformed: transform-axis(
        $axis: $main-axis,
        $inverse: $main-axis-inverse,
    );

    @if list.length($placement-chunks) > 1 {
        $cross-axis: list.nth($placement-chunks, 2);
        $cross-axis-direction: get-cross-axis-direction($main-axis);
        $cross-axis-transformed: transform-axis(
            $axis: $cross-axis,
            $inverse: $cross-axis-inverse,
            $physical-direction: if($cross-axis-physical, $cross-axis-direction, null),
        );

        @return spirit-list.to-string(($main-axis-transformed, $cross-axis-transformed), $join-with);
    }

    @return $main-axis-transformed;
}

@mixin parent() {
    position: relative; // 1.1
}

@mixin child($z-index: 1) {
    position: absolute; // 1.2
    z-index: $z-index;
}

@mixin child-controlled($prefix, $offset) {
    --#{$prefix}-offset-orthogonal: 0; // 2.
    --#{$prefix}-offset: #{$offset}; // 2.

    inset: unset; // 2.
    translate: unset; // 2.
}

@mixin child-variant($prefix, $placement, $offset: 0, $is-controlled: false) {
    --#{$prefix}-offset: #{$offset}; // 1.3.a, 2.

    @if $is-controlled {
        --#{$prefix}-offset-orthogonal: 0; // 2.
    } @else {
        inset: map.get($_inset-map, $placement); // 1.2
        translate: map.get(-get-child-translate-map($prefix), $placement); // 1.3
    }

    transform-origin: string.unquote(
        transform($placement, $main-axis-inverse: true, $cross-axis-physical: true, $join-with: ' ')
    );
}

@mixin arrow($prefix, $width, $height, $corner-offset) {
    --#{$prefix}-arrow-width: #{$width};
    --#{$prefix}-arrow-height: #{$height};
    --#{$prefix}-arrow-corner-offset: #{$corner-offset};

    position: absolute; // 1.2
    transform-origin: bottom center;
}

@mixin arrow-variant($prefix, $placement, $is-controlled: false) {
    @if $is-controlled {
        transform-origin: center; // 2.
    } @else {
        inset: map.get($_inset-map, $placement); // 1.2
        translate: map.get(-get-arrow-translate-map($prefix), $placement); // 1.3
    }

    rotate: z map.get($_arrow-rotate-map, $placement);
}
