@use 'sass:math';
@use 'sass:meta';
@use 'sass:string';
@use 'sass:map';
@use 'is-alias' as *;
@use 'is-angle' as *;
@use 'is-number' as *;
@use 'is-string' as *;
@use 'strip-unit' as *;
@use 'pi' as *;

/// Takes a given $angle in either turns (turn), degrees (deg), gradians (grad),
/// or radian (rad) units, and converts that angle into an alternate unit type
/// defined by $convert-to.
///
/// @param {Number | String} $angle - An angle value. Can be in turn, deg, grad,
/// or rad units. Can also be a keyword string with one of the following values:
/// `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`,
/// `left`, or `top-left`.
/// @param {String} $convert-to ['deg'] - The type of angle notation the given
/// $angle should be converted to.
///
/// @return {Number} The converted angle.
///
/// @access public
/// @group Utilities
/// @require {function} is-alias
/// @require {function} is-angle
/// @require {function} is-number
/// @require {function} is-string
/// @require {function} strip-unit
/// @require {function} pi
///
/// @throw Invalid $angle unit type error.
/// @throw Invalid $angle keyword string error.
/// @throw Invalid $angle data type error.
/// @throw Invalid $convert-to data type error.
/// @throw Invalid $convert-to angle unit keyword string.
/// @throw No value for $convert-to warning. Assuming 'deg' units.
@function conv-angle($angle, $convert-to: 'deg') {
  $start-unit: '';
  $directions-to-degrees: (
    'top': 0deg,
    'top-right': 45deg,
    'right': 90deg,
    'bottom-right': 135deg,
    'bottom': 180deg,
    'bottom-left': 225deg,
    'left': 270deg,
    'top-left': 315deg
  );
  $keyword-found: false;

  @if is-number($angle) {
    @if math.is-unitless($angle) {
      $angle: $angle * 1deg;
    }

    @if is-angle($angle) {
      $start-unit: math.unit($angle);
    } @else {
      @error 'Invalid $angle unit type passed to the [ conv-angle() ] function.' +
        'Valid angle units are `deg`, `turn`, `rad`, or `grad`.';
    }
  } @else if is-string($angle) {
    $angle: string.to-lower-case($angle);

    @each $direction, $value in $directions-to-degrees {
      @if not $keyword-found {
        @if is-alias($direction, $angle) {
          $keyword-found: true;
          $angle: $value;
          $start-unit: math.unit($angle);
        }
      }
    }

    @if not $keyword-found {
      @error 'Invalid $angle keyword passed to the [ conv-angle() ] function. ' +
          'Please either pass a valid angle, or use one of the following ' +
          'keyword options: `top`, `top-right`, `right`, `bottom-right`, ' +
          '`bottom`, `bottom-left`, `left`, or `top-left`.';
    }
  } @else {
    @error 'Invalid $angle data type of #{meta.inspect(meta.type-of($angle))} ' +
        'passed to the [ conv-angle() ] function. You must pass either a ' +
        'number or a string for $angle.';
  }

  @if $convert-to {
    @if is-string($convert-to) {
      $convert-to: string.to-lower-case($convert-to);
    } @else {
      @error 'Invalid data type of #{meta.inspect(meta.type-of($convert-to))} ' +
          'for $convert-to passed to the [ conv-angle() ] function. This ' +
          'value must be a \'string\' representing an angle unit. Valid ' +
          'angle units are `deg`, `turn`, `rad`, or `grad`.';
    }

    @if is-alias('turn', $convert-to) {
      $convert-to: turn;
    } @else if is-alias('deg', $convert-to) {
      $convert-to: deg;
    } @else if is-alias('rad', $convert-to) {
      $convert-to: rad;
    } @else if is-alias('grad', $convert-to) {
      $convert-to: grad;
    } @else {
      @error 'Invalid $convert-to unit name passed to the [ conv-angle() ] ' +
          'function. Valid angle units are `deg`, `turn`, `rad`, or `grad`.';
    }
  } @else {
    $convert-to: deg;

    @warn 'No value for $convert-to passed to the [ conv-angle() ] function. ' +
        'Assuming `deg` units.';
  }

  // If the angle is the same type as $convert-to, simply return it as-is.
  @if math.unit($angle) == $convert-to {
    @return $angle;
  }

  $angle: strip-unit($angle);

  @if $start-unit == 'turn' {
    @if $convert-to == 'deg' {
      $angle: 360deg * $angle;
    } @else if $convert-to == 'rad' {
      $angle: pi(2rad) * $angle;
    } @else if $convert-to == 'grad' {
      $angle: 400grad * $angle;
    }
  } @else if $start-unit == 'deg' {
    @if $convert-to == 'turn' {
      $angle: math.div($angle, 360) * 1turn;
    } @else if $convert-to == 'rad' {
      $angle: $angle * math.div(pi(), 180) * 1rad;
    } @else if $convert-to == 'grad' {
      $angle: $angle * math.div(200, 180) * 1grad;
    }
  } @else if $start-unit == 'rad' {
    @if $convert-to == 'turn' {
      $angle: math.div($angle, pi(2)) * 1turn;
    } @else if $convert-to == 'deg' {
      $angle: $angle * math.div(180, pi()) * 1deg;
    } @else if $convert-to == 'grad' {
      $angle: $angle * math.div(200, pi()) * 1grad;
    }
  } @else if $start-unit == 'grad' {
    @if $convert-to == 'turn' {
      $angle: math.div($angle, 400) * 1turn;
    } @else if $convert-to == 'deg' {
      $angle: $angle * math.div(180, 200) * 1deg;
    } @else if $convert-to == 'rad' {
      $angle: $angle * math.div(pi(), 200) * 1rad;
    }
  }

  @return $angle;
}

/// Takes a given $angle in either turns (turn), degrees (deg), gradians (grad),
/// or radian (rad) units, and converts that angle into an alternate unit type
/// defined by $convert-to.
///
/// @param {Number | String} $angle - An angle value. Can be in turn, deg, grad,
/// or rad units. Can also be a keyword string with one of the following values:
/// `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`,
/// `left`, or `top-left`.
/// @param {String} $convert-to ['deg'] - The type of angle notation the given
/// $angle should be converted to.
///
/// @return {Number} The converted angle.
///
/// @access public
/// @group Utilities
/// @require {function} conv-angle
///
/// @throw Invalid $angle unit type error.
/// @throw Invalid $angle keyword string error.
/// @throw Invalid $angle data type error.
/// @throw Invalid $convert-to data type error.
/// @throw Invalid $convert-to angle unit keyword string.
/// @throw No value for $convert-to warning. Assuming 'deg' units.
///
/// @alias conv-angle
@function convert-angle($angle, $convert-to: 'deg') {
  @return conv-angle($angle, $convert-to);
}
