@import '_modular-scale';
@import '_variables';
@import '_conversion';
@import '_font-face';
@import '_type-space';

/// Quickly generate a font-size in rems (with a pixel fallback if `$rem-px-fallback = true`). Optionally calculates baseline-aligned line-height a using `$base-line-height` and the `baseline-calc` function.
/// @param {Pixel} $font-size - Pixel value to be converted.
/// @param {Boolean | String | Number} $line-height ['auto'] -
/// * `'auto'` or `true` will automatically create a line-height that is aligned to the `$base-line-height`.
/// * Pixel values will be converted to a unitless value.
/// * Unitless numbers will be passed straight through.
/// * `false` will result in no line-height declaration.
/// @group core
/// @param {Boolean} $important [false] - Whether to add `!important` after the font-size.
/// @param {Pixel} $base-font [$base-font-size]
/// @requires {function} baseline-calc
/// @requires {function} cache
///
@mixin font-size(
    $font-size,
    $line-height: 'auto',
    $important: false,
    $base-font: $base-font-size
) {
    // Allow for base-font to be declared as third argument
    @if type-of($important) == 'number' {
        $base-font: $important;
        $important: false;
    }
    //Create REM-based font-size
    @include px-to-rems('font-size', $font-size, $important, $base-font);

    @if $line-height == true or $line-height == 'auto' {
        line-height: #{cache(baseline-calc, $font-size)};
    } @else if type-of($line-height) == 'number' {
        //Convert to unitless
        @if not unitless($line-height) {
            @if unit($line-height) == 'px' {
                $line-height: $line-height / $font-size;
            } @else {
                @warn 'Line-height for #{$font-size} must be unitless or in pixel values';
            }
        }

        line-height: $line-height;
    } @else if $line-height == 'inherit' or $line-height == 'normal' {
        line-height: $line-height;
    }
}

/// @alias font-size
///
@mixin type-font-size(
    $font-size,
    $line-height: 'auto',
    $important: false,
    $base-font: $base-font-size
) {
    @include font-size($font-size, $line-height, $important, $base-font);
}

/// @alias font-size
///
@mixin fs(
    $font-size,
    $line-height: 'auto',
    $important: false,
    $base-font: $base-font-size
) {
    @include font-size($font-size, $line-height, $important, $base-font);
}

/// Aligns a font-size to a baseline (`$baseline`). Returns a unitless value relative to the font-size (equivilent to ems). An optional multiple can be used to make a variation on the given baseline (using decimals will divide the the baseline).
/// @param {number} $font-size - Font size to calculate against.
/// @param {number} $baseline [$base-line-height] - Pixel value of the base line-height.
/// @param {number} $multiple [1]
/// @param {boolean} $floor [false] - Calculate the lowest possible number? Rarely useful.
/// @returns {number}
///
@function baseline(
    $font-size,
    $baseline: $base-line-height,
    $multiple: 1,
    $floor: false
) {
    @if ($floor) {
        @return floor($font-size / ($baseline * $multiple)) *
            ($baseline * $multiple) / $font-size;
    }
    @return ceil($font-size / ($baseline * $multiple)) * ($baseline * $multiple) /
        $font-size;
}

/// Calculates a where a number(`$x`) sits along a parabolic arc (curve). Change the curve's severity by changing its `$accel`.
/// An `$accel` of 1 is a smooth arc.
/// Uses this formula: ` y = (sqrt((x^1)+x) - sqrt(x^1)) / .4142135623730951 `
///  ( [Visual](https://www.desmos.com/calculator/qswvc6q9kt) )
///
/// @param {number} $x - Find the equivilent number on the curve.
/// @param {number} $accel [1] - Accepts any number between -2 and 2.35. A negative will invert the curve. 1 is a smooth curve.
///
@function _baseline-curve($x, $accel: 1) {
    //Cache this exponent since it uses a decimal and requires a lot of computation
    $x2: cache(pow, $x, $accel);
    $x3: $x2 + $x;
    $x3-root: sqrt($x3);
    $x2-root: sqrt($x2);
    $const: $SQRT2 - 1; // value used by logarithm (.4142135623730951)

    $curve: ($x3-root - $x2-root) / $const;

    @return min(1, $curve);
}

$__font-index: ();
$__font-warn: false;

/// **TL;DR:** Automatically calculate the baseline of any particular font-size.
///
/// Creates unitless line-heights that align to the baseline grid with consideration to the aesthetics of a given font size. Essentially makes sure larger font-sizes have smaller line-heights, while smaller font-sizes have larger ones.
/// Uses constraints based on upper and lower limits of pleasing line-heights, and finds where a given font-size should sit within those constraints. `$baseline-slack` increases the upper constraints, allowing for taller line-heights on larger font sizes.
///
/// The rate of change from large to small line-height is mostly a logarithmic curve.
///
/// Depends on `baseline()` for calculation and `_baseline-curve()` to determine the deviance from the upper and lower constraints.
/// @group core
/// @param {number} $font-size [$base-font-size] - font-size in need of a baseline
/// @param {number} $baseline-slack [$auto-line-height-looseness] - Percentage.
/// @returns {number}
///
@function baseline-calc(
    $font-size: $base-font-size,
    $baseline-slack: $auto-line-height-looseness
) {
    @if type-of($font-size) != 'number' {
        @warn "Font-size: #{$font-size} must be a number, not a #{type-of($font-size)}.";
    }
    $smallest-size: if(
        length($type-small-size) > 1,
        nth($type-small-size, 1),
        $type-small-size
    );
    $smallest-size: max(10px, $smallest-size);
    $largest-size: if(
        length($type-h1-size) > 1,
        nth($type-h1-size, 1),
        $type-h1-size
    );
    $i: 0;

    //Make sure baseline-slack is a percentage
    @if unit($baseline-slack) != '%' {
        @warn "Baseline-slack ($auto-line-height-looseness) should be a percentage.";
        $basline-slack: strip-units($baseline-slack);
        $baseline-slack: percentage($baseline-slack);
    }

    $slack: ($baseline-slack / 100%);

    //Smallest/Largest font sizes to calculate against
    $sm-font-size: 9 + ($slack * 10);
    $lg-font-size: 72 + ($slack * 50);

    //Base Highest and lowest line-height multiples
    $base-lh-upper-bound: 1.9;
    $base-lh-lower-bound: 1.1;

    //smallest font-size's highest/lowest line-height multiple
    // Use global settings to help determine, where possible
    $sm-lh-upper-bound: 2 + ($slack / 2);
    $sm-lh-lower-bound: 1 + ($slack / 2);

    //Largest font-size's highest/lowest line-height multiple
    // Use baseline slack to determine constraints
    $lg-lh-upper-bound: 1.05 + ($slack / 2);
    $lg-lh-lower-bound: 0.8 + ($slack / 2);

    //_baseline-curve progress percentages
    $percent-along-current: 0;
    $percent-along-upper: 0;
    $percent-along-lower: 0;

    //Non-adjusted placement of the font-size between largest and smallest font sizes
    $true-percent-along: (
        ($font-size - $sm-font-size) / ($lg-font-size - $sm-font-size)
    );
    $true-percent-along: strip-units($true-percent-along);

    //Make sure handle fonts larger than our initial assumptions
    @if $font-size <= $sm-font-size {
        //Set progress to 0%
        $base-lh-lower-bound: 1;
        $base-lh-upper-bound: $sm-lh-upper-bound;

        @if $font-size > $smallest-size {
            $true-percent-along: ($slack / 10);
        } @else {
            $true-percent-along: 0.001;
        }
    } @else if $font-size >= $lg-font-size {
        //Set progress to 100%
        $percent-along-current: 1;
        $percent-along-lower: 1;
        $percent-along-upper: 1;
        $base-lh-lower-bound: $lg-lh-lower-bound;
        $base-lh-upper-bound: $lg-lh-upper-bound;
        $lg-font-size: $font-size + ($slack * 50);
    }
    // Determine where we're at on the curve
    @else {
        //Standard curve
        $percent-along-current: cache(_baseline-curve, $true-percent-along, 1);
        //Upper boundary curve
        $percent-along-upper: cache(_baseline-curve, $true-percent-along, 1.3);
        //Lower boundary curve
        $percent-along-lower: cache(_baseline-curve, $true-percent-along, 1.9);
    }

    $upper-boundary: ($sm-lh-upper-bound - $lg-lh-upper-bound);
    $lower-boundary: ($sm-lh-lower-bound - $lg-lh-lower-bound);

    //Adjust percentages to line-height values
    $base-lh-upper-bound: (
        $sm-lh-upper-bound - ($percent-along-upper * $upper-boundary)
    );
    $base-lh-lower-bound: (
        $sm-lh-lower-bound - ($percent-along-lower * $lower-boundary)
    );

    // stylelint-disable
    @if $debug-fonts {
        @if index($__font-index, $font-size) == null {
            @debug '\A'+ 'font-size: #{$font-size}\A' +
                '    true%: #{percentage($true-percent-along)} \A' +
                '       %u: #{percentage($percent-along-upper)}\A' +
                '       %l: #{percentage($percent-along-lower)}\A' +
                '       ub: #{$base-lh-upper-bound} (#{$font-size * $base-lh-upper-bound}) ' +
                '\A' +
                '       lb: #{$base-lh-lower-bound} (#{$font-size * $base-lh-lower-bound})' +
                '\A' +
                ' sm-lh-ub: #{$sm-lh-upper-bound} | sm-lh-lb: #{$sm-lh-lower-bound}\A' +
                ' lg-lh-ub: #{$lg-lh-upper-bound} | lg-lh-lb: #{$lg-lh-lower-bound}\A' +
                '    slack: #{$slack}\A' +
                '  base-lh: #{$base-line-height} | base-fs: #{$base-font-size}';
        }
    }
    // stylelint-enable

    // If the lower bound is somehow higher than the upper bound,
    // let's just reset the bounds to simple values
    @if $base-lh-lower-bound > $base-lh-upper-bound {
        $base-lh-lower-bound: 1;
        $base-lh-upper-bound: 2;
    }

    //First attempt
    $baseline: $base-line-height or 24px;
    $line-height: baseline($font-size, $baseline);

    //Second attempt
    @if ($line-height < $base-lh-lower-bound) or
        ($line-height > $base-lh-upper-bound)
    {
        //Attempt using a negative line-height for very large sizes
        @if ($true-percent-along > 0.8) {
            $line-height: baseline($font-size, $baseline, 0.5, $floor: true);
        } @else if $font-size < $base-font-size {
            $line-height: baseline($font-size, $baseline, 1.5);
        } @else {
            $line-height: baseline($font-size, $baseline, 0.5);
        }
    }

    //Set up while loop
    $quit: false;

    //Test baselines until we find a match
    @while (
        (
                $line-height <
                    $base-lh-lower-bound or
                    $line-height >
                    $base-lh-upper-bound
            ) and
            (not $quit)
    ) {
        $i: $i + 0.5;
        //Try baselines in half increments
        $line-height: baseline($font-size, $baseline, $i);

        // Bail-out function to prevent infinite loops
        // Set $quit to true so we can escape this hellish loop
        @if $i > (($font-size / 1px) / 2) {
            @if (not $__font-warn) and ($debug-fonts) {
                @warn ""+
                "Your font-looseness might be too high or low," +
                    " watch for weird baselines and slow compile times.\A"+
                "FS: #{$font-size} / LH: #{$line-height}\A"+
                "Tolerances: #{$base-lh-upper-bound} / #{$base-lh-lower-bound}\A"+
                "loops: #{$i * 2}\A";
                $__font-warn: true !global; //Prevent seeing this warning multiple times
            }

            //default to normal calculated line-height
            $line-height: baseline($font-size, $baseline);
            $quit: true;
        }
    }

    @if $debug-fonts {
        @if index($__font-index, $font-size) == null {
            $__font-index: append($__font-index, $font-size) !global;
            // stylelint-disable
            @debug ''+ '\A        lh: #{$line-height * $font-size} / #{$line-height} '+
                '\A     loops: #{$i * 2}' + '\A  --------\A';
            // stylelint-enable
        }
    }

    @return $line-height;
}

/// Style any number of headings all at once.
/// @param {Number} $from [1] - Starting heading number
/// @param {Number} $to [6] - Ending heading number
/// @example
///    @include headings(1, 3){color:#BADA55;}
///    // outputs:
///    h1, h2, h3 {color:#BADA55;}
@mixin headings($from: 1, $to: 6, $class-type: null) {
    //Used for multiple calls to this mixin.
    // Ensures no double extends.
    $_headings-use: $_headings-use + 1 !global;

    %base-headings#{$_headings-use} {
        @content;
    }

    @if $from >= 1 and $to <= 6 {
        @for $i from $from through $to {
            $selector: '#{$class-type}h#{$i}';
            #{$selector} {
                @extend %base-headings#{$_headings-use};
            }
        }
    } @else {
        @warn 'You need to supply numbers between 1 and 6' +
            ' for headings mixin to work';
    }
}

/// Checks if a type-size is declared as larger than 1, then either extracts the second value or calculates a line-height.
/// @param {List|Number} $type-size
/// @returns {number} The line-height
///
@function _extract-line-height($type-size) {
    $line-height: if(
        length($type-size) > 1,
        nth($type-size, 2),
        baseline-calc($type-size)
    );

    @if not unitless($line-height) {
        $line-height: ($line-height / nth($type-size, 1));
    }

    @return $line-height;
}
