@use 'sass:list';
@use 'sass:map';
@use 'sass:math' as sass-math;
////
/// @group utils.typography
////

@use 'math.scss' as math;
@use 'collections.scss' as collections;

$typescale-default: 1.175;
$typescale-minor-second: 1.06667;
$typescale-major-second: 9 * 0.125;
$typescale-minor-third: 6 * 0.2;
$typescale-major-third: 5 * 0.25;
$typescale-perfect-fourth: 1.33333;
$typescale-aug-fourth: 1.4142;
$typescale-perfect-fifth: 3 * 0.5;
$typescale-minor-sixth: 8 * 0.2;
$typescale-golden-section: 1.618;

/// Generate a modular type size
///
/// @param {number} $base The base size used to generate the type scale (always
/// expressed in px).
///
/// @param {number} $ratio The ratio to use for generating the modular type
/// scale (e.g. 1.25 or 4/3)
///
/// @param {number} $step The number of steps away from the base (pos/neg)
///
/// @return {number} A single value (in px or rem) that represents a font-size
/// for the given parameters
///
/// @link  https://www.modularscale.com/
/// @example modular-size(100px, 1.25, -1) // => 80px

@function modular-size($base, $ratio, $step) {
    $size: $base;

    @if ($step >= 0) {
        $size: $base * (math.pow($ratio, $step));
    } @else {
        $size: math.divide($base, math.pow($ratio, sass-math.abs($step)));
    }

    @return $size;
}

/// Generate a complete modular type scale, expressed as a map of type names and font sizes
///
/// @param {Number | List} $bases The base size or sizes used to generate the typescale
///
/// @param {number} $ratio The ratio used to generate the typescale
///
/// @param {List} $range Optional -- either $range or $names must be provided. A list of two values
/// that represent the range of sizes to be created (e.g. -3, 10), with the size 0 being equivalent
/// to the base.
///
/// @param {List} $names Optional -- either $range or $names must be provided. The names for the
/// generated sizes (these will be the keys in the map that gets returned)
///
/// @param {Bool} $rems [true] Whether or not to convert to rems. Note that rems will be based on
/// the first (or only) value in $bases
///
/// @param {number} $round [4] The fractional rounding amount (e.g. value of 4 means 1/4 so round to
/// 0.25, 0.50, etc.)
///
/// @link  https://www.modularscale.com/
///
/// @return {Map} A map of named type sizes, e.g. `(small: 0.75rem, -base: 1rem, large: 1.33rem)`
///
/// @example $typescale: modular-scale(16px, 0.75, null, (sm base lg)); // => (sm: 0.75rem, base: 1rem, lg: 1.25rem)

@function modular-scale(
    $bases,
    $ratio: $typescale-perfect-fourth,
    $range: (-2, 5),
    $names: null,
    $rems: false,
    $round: 4
) {
    // if $names isn't provided but range is, we use numeric values for names
    @if (not($names)) {
        $from: list.nth($range, 1);
        $to: list.nth($range, 2);
        $names: ();

        @for $n from list.nth($range, 1) through list.nth($range, 2) {
            @if ($n == 0) {
                $names: list.append($names, 'base');
            } @else {
                $names: list.append($names, $n);
            }
        }
    }

    $scale: ();
    $bases: if(
        sass(is-list($bases)): $bases;
            else: (
                $bases,
            )
    );
    $remsfs: list.nth($bases, 1);

    $base-index: list.index($names, 'base') - 1;

    $count: list.length($names);
    $step-from: $base-index * -1;
    $step-to: $count - ($base-index + 1);

    // get the sizes for all bases
    $sizes: ();
    @each $b in $bases {
        @for $step from $step-from through $step-to {
            $s: modular-size($b, $ratio, $step);
            $sizes: list.append($sizes, $s);
        }
    }

    // sort the sizes
    $sizes: collections.list-sort-num($sizes);

    // find the index of the original first base
    $size-base-index: list.index($sizes, list.nth($bases, 1));
    $sizes-from: $size-base-index + $step-from;
    $sizes-to: $sizes-from + ($count - 1);

    // match generated sizes to the list of size names
    @for $step from $sizes-from through $sizes-to {
        // find the size
        $size: list.nth($sizes, $step);

        // convert to rems if specified (this will also round)
        @if ($rems) {
            //$size: -rems($size, $remsfs, $round);
            $size: math.divide($size, $remsfs) * 1rem;
            $size: if(sass($round > 0): math.round-f($size, $round) ; else: $size);
        }

        // otherwise just round
        @else {
            @if ($round > 0) {
                $size: math.round-f($size, $round);
            }
        }

        $name: list.nth($names, ($step - $sizes-from) + 1);
        $scale: map.merge($scale, (#{$name}: $size));
    }

    @return $scale;
}
