@use 'sass:math';
@use 'sass:list';
@use 'is-integer' as *;

/// Calculates the nth Fibonacci number or a range of Fibonacci numbers. This
/// can be used in calculations for a range of possible effects.
///
/// If one argument is provided, returns the nth Fibonacci number.
/// If two arguments are provided, returns a list of Fibonacci numbers in the
/// given range.
///
/// The function caps at generating Fibonacci numbers up to the 79th in the
/// sequence to avoid exceeding the maximum safe integer value in Dart and
/// JavaScript (2^53 - 1). It is unlikely anyone will need a number anywhere
/// close to that, let alone higher.
///
/// @param {Number} $n - The position in the Fibonacci sequence or the
/// start of the range.
/// @param {Number} $end [null] - The end of the range in the Fibonacci sequence
/// (optional).
///
/// @return {Number | List} - The nth Fibonacci number or a list of numbers in
/// the range.
///
/// @access public
/// @group Utilities
/// @require {function} is-integer
/// @since 0.8.1
///
/// @throw Invalid input error. Both arguments must be integers.
/// @throw Invalid input error. Cannot return anything past the 79th number.
/// @throw Invalid input error. If $end is passed, it must be larger than $n.
@function fibonacci($n, $end: null) {
  // Pre-calculated Fibonacci sequence to optimize performance.
  $fibonacci-sequence: (
    0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987,
    1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393,
    196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887,
    9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141,
    267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073,
    4807526976, 7778742049, 12586269025, 20365011074, 32951280099,
    53316291173, 86267571272, 139583862445, 225851433717, 365435296162,
    591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881,
    6557470319842, 10610209857723, 17167680177565, 27777890035288,
    44945570212853, 72723460248141, 117669030460994, 190392490709135,
    308061521170129, 498454011879264, 806515533049393, 1304969544928657,
    2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464
  );

  $cap: list.length($fibonacci-sequence);

  $error-msg-section: 'Passing a single integer will ' +
        'return the nth value in the fibonacci sequence. Passing two ' +
        'arguments will return the value inclusive range between those ' +
        'numbers in the fibonacci sequence as a comma-separated list.';

  @if not is-integer($n) or ($end and not is-integer($end)) {
    @error 'Invalid input for the [ fibonacci() ] function. Any value passed ' +
        'as an argument must be an integer. #{$error-msg-section}';
  }

  @if $n > $cap or ($end and $end > $cap) {
    @error 'The [ fibonnaci() ] function is capped at generating only up to ' +
        '#{$cap} Fibonacci sequence numbers. Please provide a smaller number. ' +
        '#{$error-msg-section}';
  }

  @if $end and $end < $n {
    @error 'Invalid input for the [ fibonacci() ] function. The second argument' +
        ', $end, cannot be smaller than the first argument, $n. #{$error-msg-section}';
  }

  @if $end == $n {
    $end: null;
  }

  @if not $end {
    @return list.nth($fibonacci_sequence, $n + 1);
  } @else {
    $range: ();

    @for $i from $n through $end {
      $range: list.append($range, list.nth($fibonacci_sequence, $i + 1));
    }

    @return $range;
  }
}
