@use 'sass:math';
@use 'sass:list';
@use 'sass:map';
@use 'sass:color';

@function getred($color) {
  @return color.channel($color, 'red');
}

@function getgreen($color) {
  @return color.channel($color, 'green');
}

@function getblue($color) {
  @return color.channel($color, 'blue');
}

@function get-luminance($color) {
  $colors: (
    'red': getred($color),
    'green': getgreen($color),
    'blue': getblue($color),
  );

  @each $name, $value in $colors {
    $adjusted: 0;
    $value: math.div($value, 255);

    @if $value < 0.03928 {
      $value: math.div($value, 12.92);
    } @else {
      $value: math.div($value + 0.055, 1.055);
      $value: math.pow($value, 2.4);
    }

    $colors: map.merge(
      $colors,
      (
        $name: $value,
      )
    );
  }

  @return (
    (map.get($colors, 'red') * 0.2126) + (map.get($colors, 'green') * 0.7152) + (map.get($colors, 'blue') * 0.0722)
  );
}

@function interpolate($c1, $c2, $f) {
  $c1r: getred($c1);
  $c1g: getgreen($c1);
  $c1b: getblue($c1);
  $c2r: getred($c2);
  $c2g: getgreen($c2);
  $c2b: getblue($c2);

  @return rgb($c1r + $f * ($c2r - $c1r), $c1g + $f * ($c2g - $c1g), $c1b + $f * ($c2b - $c1b));
}

@function set-luminance($color, $lum) {
  $itersLeft: 20;
  $low: rgb(0 0 0);
  $high: rgb(255 255 255);

  @if get-luminance($color) > $lum {
    $high: $color;
  } @else {
    $low: $color;
  }

  $mid: interpolate($low, $high, 0.5);
  $lm: get-luminance($mid);

  /* stylelint-disable-next-line scss/operator-no-unspaced */
  @while math.abs($lum - $lm) >= 1e-7 and $itersLeft > 0 {
    $itersLeft: $itersLeft - 1;
    $mid: interpolate($low, $high, 0.5);
    $lm: get-luminance($mid);

    @if $lm > $lum {
      $high: $mid;
    } @else {
      $low: $mid;
    }
  }

  @return $mid;
}

@function color-triplet($color) {
  @return #{getred($color)}, #{getgreen($color)}, #{getblue($color)};
}

// LAB <-> RGB
$oneThird: math.div(1, 3);
$_16OVER116: math.div(16, 116);
$_1OVER2P4: math.div(1, 2.4);

@function rgb2lab($color) {
  $r: math.div(getred($color), 255);
  $g: math.div(getgreen($color), 255);
  $b: math.div(getblue($color), 255);

  @if $r > 0.04045 {
    $r: math.pow(math.div($r + 0.055, 1.055), 2.4);
  } @else {
    $r: math.div($r, 12.92);
  }

  @if $g > 0.04045 {
    $g: math.pow(math.div($g + 0.055, 1.055), 2.4);
  } @else {
    $g: math.div($g, 12.92);
  }

  @if $b > 0.04045 {
    $b: math.pow(math.div($b + 0.055, 1.055), 2.4);
  } @else {
    $b: math.div($b, 12.92);
  }

  $x: math.div($r * 0.4124 + $g * 0.3576 + $b * 0.1805, 0.95047);
  /* stylelint-disable-next-line prettier/prettier */
  $y: math.div($r * 0.2126 + $g * 0.7152 + $b * 0.0722, 1.00000);
  $z: math.div($r * 0.0193 + $g * 0.1192 + $b * 0.9505, 1.08883);

  @if $x > 0.008856 {
    $x: math.pow($x, $oneThird);
  } @else {
    $x: (7.787 * $x) + $_16OVER116;
  }

  @if $y > 0.008856 {
    $y: math.pow($y, $oneThird);
  } @else {
    $y: (7.787 * $y) + $_16OVER116;
  }

  @if $z > 0.008856 {
    $z: math.pow($z, $oneThird);
  } @else {
    $z: (7.787 * $z) + $_16OVER116;
  }

  $l: (116 * $y) - 16;
  $a: 500 * ($x - $y);
  $b: 200 * ($y - $z);

  @return ('L': $l, 'a': $a, 'b': $b);
}

@function lab2rgb($lab) {
  $y: math.div(map.get($lab, 'L') + 16, 116);
  $x: math.div(map.get($lab, 'a'), 500) + $y;
  $z: $y - math.div(map.get($lab, 'b'), 200);
  $x3: math.pow($x, 3);
  $y3: math.pow($y, 3);
  $z3: math.pow($z, 3);

  @if $x3 > 0.008856 {
    $x: $x3;
  } @else {
    $x: math.div($x - $_16OVER116, 7.787);
  }

  @if $y3 > 0.008856 {
    $y: $y3;
  } @else {
    $y: math.div($y - $_16OVER116, 7.787);
  }

  @if $z3 > 0.008856 {
    $z: $z3;
  } @else {
    $z: math.div($z - $_16OVER116, 7.787);
  }

  $x: $x * 0.95047;
  /* stylelint-disable-next-line prettier/prettier */
  $y: $y * 1.00000;
  $z: $z * 1.08883;
  $r: $x * 3.2406 + $y * -1.5372 + $z * -0.4986;
  $g: $x * -0.9689 + $y * 1.8758 + $z * 0.0415;
  $b: $x * 0.0557 + $y * -0.204 + $z * 1.057;

  @if $r > 0.0031308 {
    $r: 1.055 * math.pow($r, $_1OVER2P4) - 0.055;
  } @else {
    $r: 12.92 * $r;
  }

  @if $g > 0.0031308 {
    $g: 1.055 * math.pow($g, $_1OVER2P4) - 0.055;
  } @else {
    $g: 12.92 * $g;
  }

  @if $b > 0.0031308 {
    $b: 1.055 * math.pow($b, $_1OVER2P4) - 0.055;
  } @else {
    $b: 12.92 * $b;
  }

  @return rgb(math.clamp(0, $r, 1) * 255, math.clamp(0, $g, 1) * 255, math.clamp(0, $b, 1) * 255);
}

@function lab-gradient($s, $e, $start, $stop) {
  $lst: ();

  @for $i from $start to $stop {
    $lst: list.append($lst, lab-gradient-step($s, $e, $i, $stop));
  }

  @return $lst;
}

@function lab-gradient-step($s, $e, $i, $stop) {
  $s: rgb2lab($s);
  $e: rgb2lab($e);
  $a: math.div($i, $stop - 1);
  $ia: 1 - $a;

  @return lab2rgb(
    (
      'L': $ia * map.get($s, 'L') + $a * map.get($e, 'L'),
      'a': $ia * map.get($s, 'a') + $a * map.get($e, 'a'),
      'b': $ia * map.get($s, 'b') + $a * map.get($e, 'b'),
    )
  );
}
