// ============================================================
//  _functions.scss
//  Pure Sass functions: unit conversion, color manipulation and
//  fluid typography. They emit no CSS by themselves — use them
//  inside your own rules or mixins.
// ============================================================
@use "sass:math";
@use "sass:color";
@use "sass:meta";
@use "sass:string";
@use "sass:list";

// Root font-size. Must match the --root-font-size you set in the reset
// (62.5% → 10px on 16px browsers). This makes 1rem = 10px, so rem()
// and px() produce values where rem ≈ px.
// Override before importing this module (!default semantics).
$base-font-size: 10px !default;

// ============================================================
//  PRIVATE HELPERS
// ============================================================

// Recursively replaces every occurrence of $search inside $string.
// Used by to-class() to escape CSS selectors.
@function _str-replace($string, $search, $replace: "") {
  $index: string.index($string, $search);
  @if not $index { @return $string; }
  @return string.slice($string, 1, $index - 1) + $replace +
    _str-replace(string.slice($string, $index + string.length($search)), $search, $replace);
}

// ============================================================
//  UNIT CONVERSION
//  Convert between px, rem and other CSS units.
//  All conversions respect $base-font-size (default 10px).
// ============================================================

// Convert a px value to rem. Accepts unitless numbers (treated as px)
// or values with a px unit.
// Example: f.rem(16px) → 1.6rem (16 / 10).
@function rem($size) {
  @if meta.type-of($size) != "number" {
    @error "rem(): se esperaba un número, recibió #{meta.inspect($size)}.";
  }
  $px: if(sass(math.is-unitless($size)): $size; else: math.div($size, 1px));
  $base: math.div($base-font-size, 1px);
  @return math.div($px, $base) * 1rem;
}

// Convert a rem value to px. Unitless numbers are treated as rem.
// Example: f.px(1.6rem) → 16px (1.6 * 10).
@function px($size) {
  @if meta.type-of($size) != "number" {
    @error "px(): se esperaba un número, recibió #{meta.inspect($size)}.";
  }
  $num: if(sass(math.is-unitless($size)): $size; else: math.div($size, 1rem));
  $base: math.div($base-font-size, 1px);
  @return $num * $base * 1px;
}

// Append a unit to a unitless number (values that already have a
// unit are returned unchanged). Valid units: rem, px, em, %.
// Example: f.to-unit(4, "rem") → 4rem.
@function to-unit($value, $unit: "rem") {
  @if meta.type-of($value) != "number" {
    @error "to-unit(): se esperaba un número, recibió #{meta.inspect($value)}.";
  }
  @if not math.is-unitless($value) { @return $value; }
  $valid: ("rem", "px", "em", "%");
  @if not list.index($valid, $unit) {
    @error "to-unit(): unidad '#{$unit}' no válida. Use: rem, px, em, %.";
  }
  @if $unit == "rem" { @return $value * 1rem; }
  @if $unit == "px"  { @return $value * 1px; }
  @if $unit == "em"  { @return $value * 1em; }
  @if $unit == "%"   { @return $value * 1%; }
  @error "to-unit(): caso no cubierto para '#{$unit}'.";
}

// Remove the unit from a number by dividing by its own unit.
// Example: f.strip-unit(16px) → 16.
@function strip-unit($value) {
  @if meta.type-of($value) != "number" { @error "strip-unit(): se esperaba un número."; }
  @return math.div($value, ($value * 0 + 1));
}

// ============================================================
//  FLUID TYPOGRAPHY (clamp)
// ============================================================

// Builds a clamp() value that scales linearly from $min at
// $min-vw viewport width to $max at $max-vw. Unitless numbers are
// treated as px. Uses the classic slope/intercept formula:
// preferred = slope * 100vw ± intercept, clamped between min and max.
// Example: f.fluid(16, 24) →
//   clamp(16px, 0.5vw + 14.4px, 24px) between 320px and 1920px.
@function fluid($min, $max, $min-vw: 320px, $max-vw: 1920px) {
  $min-val: if(sass(math.is-unitless($min)): $min * 1px; else: $min);
  $max-val: if(sass(math.is-unitless($max)): $max * 1px; else: $max);
  $vw-min: if(sass(math.is-unitless($min-vw)): $min-vw * 1px; else: $min-vw);
  $vw-max: if(sass(math.is-unitless($max-vw)): $max-vw * 1px; else: $max-vw);

  $slope: math.div(
    strip-unit($max-val) - strip-unit($min-val),
    strip-unit($vw-max) - strip-unit($vw-min)
  );
  $intercept: strip-unit($min-val) - ($slope * strip-unit($vw-min));

  $sign: if(sass($intercept >= 0): "+"; else: "-");
  $preferred: #{$slope * 100}vw #{$sign} #{math.abs($intercept)}px;

  @return clamp(#{$min-val}, #{$preferred}, #{$max-val});
}

// ============================================================
//  COLOR MANIPULATION
// ============================================================

// Mix a color toward white. $amount is a percentage (unitless
// numbers are treated as %). f.tint($c, 20%) = 20% white + 80% $c.
@function tint($color, $amount: 10%) {
  @if meta.type-of($color) != "color" { @error "tint(): se esperaba un color."; }
  $pct: if(sass(math.is-unitless($amount)): $amount * 1%; else: $amount);
  @return color.mix(white, $color, $pct);
}

// Mix a color toward black. Inverse of tint().
@function shade($color, $amount: 10%) {
  @if meta.type-of($color) != "color" { @error "shade(): se esperaba un color."; }
  $pct: if(sass(math.is-unitless($amount)): $amount * 1%; else: $amount);
  @return color.mix(black, $color, $pct);
}

// Scale saturation up/down by $amount percentage points.
@function saturate-color($color, $amount: 10%) { @return color.scale($color, $saturation: $amount); }
@function desaturate-color($color, $amount: 10%) { @return color.scale($color, $saturation: -$amount); }
// Hue-rotate the color by 180° (the complementary color).
@function complement($color) { @return color.complement($color); }

// Returns black or white (or your own pair) depending on which has
// better contrast against $color, using perceptual luminance weights
// (0.299/0.587/0.114). Bright colors return $dark, dark ones $light.
@function contrast($color, $light: white, $dark: black) {
  $brightness: (color.red($color) * 0.299 + color.green($color) * 0.587 + color.blue($color) * 0.114) / 255;
  @return if(sass($brightness > 0.5): $dark; else: $light);
}

// Builds a CSS color-mix() that blends a custom property with
// another color at runtime. $var-name is accepted with or without
// the var() wrapper. Example:
// f.color-mix-var(--purple-500, 20%) →
//   color-mix(in srgb, var(--purple-500) 80%, white).
@function color-mix-var($var-name, $amount: 10%, $mix-with: white) {
  $name: if(sass(string.index($var-name, "var(") == 1): $var-name; else: "var(#{$var-name})");
  $pct: if(sass(math.is-unitless($amount)): $amount * 1%; else: $amount);
  $color-pct: 100% - $pct;
  @return color-mix(in srgb, #{$name} #{$color-pct}, #{$mix-with});
}

// Escapes characters that are invalid in CSS class selectors
// ('.', '/', '%') so a token key can be used as a class name.
@function to-class($key) {
  $str: "#{$key}";
  $str: _str-replace($str, ".", "\\.");
  $str: _str-replace($str, "/", "\\/");
  $str: _str-replace($str, "%", "\\%");
  @return $str;
}
