////////////////////////////////////////////////////////////////////////////////
/// Cast to number 
////////////////////////////////////////////////////////////////////////////////

/// Cast a value to a number if possible or return 0
/// @author 3rdthemagical
/// @link https://stackoverflow.com/questions/47630616/scss-arithmetic-operation-with-string
/// @param {string} $value
/// @return {number}

@use 'sass:list';
@use 'sass:map';
@use 'sass:string';
@use 'sass:meta';
@use 'sass:math';

// Constants 
$numbers    : ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$units      : ('px', 'cm', 'mm', '%', 'ch', 'pc', 'in', 'em', 'rem', 'pt', 'ex', 'vw', 'vh', 'vmin', 'vmax', 'ms', 's', 'deg', 'rad', 'grad', 'turn', 'Hz', 'kHz', 'dpi', 'dpcm', 'dppx');
$unit-value : (1px, 1cm, 1mm, 1%, 1ch, 1pc, 1in, 1em, 1rem, 1pt, 1ex, 1vw, 1vh, 1vmin, 1vmax, 1ms, 1s, 1deg, 1rad, 1grad, 1turn, 1Hz, 1kHz, 1dpi, 1dpcm, 1dppx);

/// Cast a value to a number if possible or return 0
///
/// @author Hugo Giraudel
///
/// @link https://github.com/HugoGiraudel/SassyCast
///
/// @param {string} $value
///
/// @return {number}

@function number($value) {
  $type: meta.type-of($value);

  // If the value is already a number, we can safely return it.
  @if ($type == 'number') {
    @return $value;
  }

  // If the value is the `true` boolean, we return 1.
  @if ($value == true)  {
    @return 1;
  }

  // If the value is the `false` boolean, we return 0.
  @if ($value == false) {
    @return 0;
  }

  // If the type is not a string, there is no way we can convert it to a number,
  // so we warn and return the default number value (or _throw in strict mode).
  @if ($type != 'string') {
    @return 0;
  }

  // At this point we have discarded all the edge cases so we can start trying
  // to cast the value into a number.
  $pointer: 1;
  $result: 0;
  $first-character: string.slice($value, $pointer, $pointer);
  $allowed-first-character: list.join(('-', '.'), $numbers);

  // We perform an early check for errors. If value starts with neither a number
  // nor a minus sign, it cannot be casted to a number. Therefore we warn and
  // return the default number value (or _throw in strict mode).
  @if not list.index($allowed-first-character, $first-character) {
    @return 0;
  }

  // In case the value starts with a dot, we assume it is a float and pad it
  // with a zero.
  @if ($first-character == '.') {
    $value: '0' + $value;
  }

  // We find the integer part of the value.
  $find-integer: _find-integer($value, $pointer);
  $pointer: list.nth($find-integer, 1);
  $result:  list.nth($find-integer, 2);

  // If there still is a dot, it means we have to check for the digits part of
  // the value.
  @if (string.slice($value, $pointer, $pointer) == '.') {
    $find-digits: _find-digits($value, $pointer);
    $pointer: list.nth($find-digits, 1);
    $digits:  list.nth($find-digits, 2);
    $result: ($result + $digits);
  }

  // If the first character is a minus symbol, it means the number is negative
  // so we can multiply it per -1.
  @if ($first-character == '-') {
    $result: ($result * -1);
  }

  // If we are still not done with the evaluation, it means there could be a CSS
  // _unit, so we check for it.
  @if ($pointer <= string.length($value)) {

    $index: list.index($units, string.slice($value, $pointer));

    @if not $index {
      $result: 0;
    }
  
    $result: ($result * list.nth($unit-value, $index));

  }

  @return $result;
}

////////////////////////////////////////////////////////////////////////////////
/// Find Digits                                                     #find-digits
////////////////////////////////////////////////////////////////////////////////
///
/// Finding the digits part of a stringified number
///
/// @author Hugo Giraudel
///
/// @link https://github.com/HugoGiraudel/SassyCast
///
/// @access private
///
/// @group helpers-numbers
///
/// @param {string} $source - string source
///
/// @param {number} $pointer - current pointer
///
/// @return {list} - new pointer, parsed number

@function _find-digits($source, $pointer) {
  $source: string.to-lower-case($source);
  $length: string.length($source);
  $numbers: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
  $result: 0;
  $runs: 1;

  @while $pointer <= $length {
    $token: string.slice($source, $pointer, $pointer);
    $index: list.index($numbers, $token);

    @if $token == "." {
      // @continue;
    }
    @else if $index and $index > 0 {
      $runs: $runs * 10;
      $result: $result * 10 + ($index - 1);
    }
    @else {
      @return $pointer, math.div($result, $runs);
    }

    $pointer: $pointer + 1;
  }

  @return $pointer, math.div($result, $runs);
}

////////////////////////////////////////////////////////////////////////////////
/// Find Interger                                                  #find-integer
////////////////////////////////////////////////////////////////////////////////
///
/// Finding the digits part of a stringified number
///
/// @author Hugo Giraudel
///
/// @link https://github.com/HugoGiraudel/SassyCast
///
/// @access private
///
/// @group helpers-numbers
///
/// @param {string} $source  - string source
///
/// @param {number} $pointer - current pointer
///
/// @return {list} new pointer, parsed number

@function _find-integer($source, $pointer) {
  $source: string.to-lower-case($source);
  $length: string.length($source);
  $numbers: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
  $result: 0;

  @while $pointer <= $length {
    $token: string.slice($source, $pointer, $pointer);
    $index: index($numbers, $token);

    @if $token == "-" {
      // @continue;
    }
    @else if $index {
      $result: $result * 10 + ($index - 1);
    }
    @else {
      @return $pointer, $result;
    }

    $pointer: $pointer + 1;
  }

  @return $pointer, $result;
}
