////
/// @group breakpoint
/// Utilities for working with breakpoints
////

@use "sass:map";
@use "sass:list";
@use "utils";

/// Module Settings
/// @type Map
/// @prop {Number} base  [16px] Assumed pixel base, can change based on users font settings so this is just to get us in the ballpark. Note this is not the base font size but the user agent's or user's browser preference. This number is just being used for calculating estimated em sizes from average base. Since pixels are easier to understand but since we allow the user to set their font size. All of our css is relative to that, including most of the layout (rems, other relative units)
/// @prop {Number} gap  [0.01em] The amount to offset min from max media queries incase you are using both (prevent overlap)
/// @prop {String} null-name ["none"] The name of the space from 0 to the first breakpoint (doesn't really matter) used when passing breakpoints to JS via content property (see breakpoint.embed-for-scripts() or cssvar.declare-breakpoint-sizes()) to pass breakpoints to JS. The js ui/breakpoints.js module provides methods for interacting with breakpoints in JS.
/// @prop {String} default ["small"] The name of the breakpoint that is considered the major change (ie desktop to mobile) used by other modules/components

$config: (
  "base":      16px,
  "default" :  "small",
  "gap":       0.01em,
  "null-name": "none",
) !default;

/// Change modules $config
/// @example scss Change default name
///   @include ulu.breakpoint-set(( "default" : "mini" ));
/// @param {Map} $changes Map of changes

@mixin set($changes) {
  $config: map.merge($config, $changes) !global;
}

/// Get a config option
/// @example scss {compile} Example usage
///   .test-get {
///     font-size: ulu.breakpoint-get("base");
///   }
/// @param {Map} $name Name of property
/// @return {*} Property Value

@function get($name) {
  @return utils.require-map-get($config, $name, 'breakpoint [config]');
}

/// The default breakpoint sizes
/// - Map of breakpoints
/// - Each property is the breakpoints name
/// - Each value is that breakpoints point (set in em by default)
/// @type Map

$sizes: (
  "small"  : utils.pixel-to-em(680px, get("base")),
  "medium" : utils.pixel-to-em(1200px, get("base")),
  "large"  : utils.pixel-to-em(1500px, get("base"))
) !default;

/// Update the breakpoint sizes map
/// @example scss Changing the medium breakpoint and adding jumbo
///   @include ulu.breakpoints-set-sizes((
///     "medium" : 50em,
///     "jumbo" : 100em
///   ));
/// @param {Map} $changes A map to merge into the breakpoints map
/// @param {Map} $merge-mode [null] Merge strategy see, utils.map-merge options

@mixin set-sizes($changes, $merge-mode: null) {
  $sizes: utils.map-merge($sizes, $changes, $merge-mode) !global;
}

/// Get all breakpoint sizes
/// @compiler
/// @example scss {compile} Example usage
///   .test-get-sizes {
///     $sizes: ulu.breakpoint-get-sizes();
///     height: map.get($sizes, "medium");
///   }
/// @return {Map} Map of breakpoints (equivalent to $sizes)

@function get-sizes() {
  @return $sizes;
}

/// Get a specific breakpoint's raw value/size
/// @param {String} $size The name of the size to get
/// @compiler
/// @example scss {compile} Example usage
///   .test-get-size {
///     height: ulu.breakpoint-get-size("medium");
///   }
/// @return {Number} The sizes value

@function get-size($size) {
  @return utils.require-map-get($sizes, $size, "breakpoint size");
}

/// Get a specific breakpoint's size's value and optionally specify max to get the ending/max value for a breakpoint
/// @param {Boolean} $max [false] Get the max value
/// @compiler
/// @example scss {compile} Example usage
///   .test-get-size-value {
///     height: ulu.breakpoint-get-size-value("medium", true);
///     max-height: ulu.breakpoint-get-size-value("medium");
///   }
/// @return {Number} The value for the given size

@function get-size-value($size, $max: false) {
  @if ($max) {
    @return get-size($size) - get("gap");
  } @else {
    @return get-size($size);
  }
}

/// Check if a specific size exist
/// @param {String} $name The breakpoint size to check if exists
/// @return {Boolean} 
/// @example scss {compile} Example usage
///   .test-exists {
///     @if(ulu.breakpoint-exists("medium")) {
///       @include ulu.breakpoint-min("medium") {
///         padding: 2rem;
///       }
///     }
///     // The below content doesn't print because the size doesn't exist.
///     @if(ulu.breakpoint-exists("too-large")) {
///       @include ulu.breakpoint-min("too-large") {
///         padding: 20000rem;
///       }
///     }
///   }

@function exists($name) {
  $size: map.get($sizes, $name);
  @return if($size != null, true, false);
}

/// Create a media query that matches the min-width for a given size
/// @example scss
///   @include ulu.breakpoints-min("small") {
///     // Your styles
///   }
/// @example css
///   @media screen and (min-width: 42.5em) {
///      // Your Styles
///   }
/// @param {String} $size The name of the breakpoint size

@mixin min($size) {
  // Adding the small fraction to prevent overlap
  $min: get-size-value($size); 
  @media screen and (min-width: $min) {
    @content;
  }
}

/// Create a media query that matches the max-width for a given size
/// @example scss
///   @include breakpoints.max("medium") {
///     // Your styles
///   }
/// @example css
///   @media screen and (max-width: 42.4em) {
///      // Your Styles
///   }
/// @param {Number} $size The name of the breakpoint size

@mixin max($size) {
  // Adding the small fraction to prevent overlap
  $max: get-size-value($size, true); 
  @media screen and (max-width: $max) {
    @content;
  }
}

/// Create a media query that matches between two breakpoint sizes
/// @example scss
///   @include breakpoints.min-max("small", "medium") {
///     // Your styles
///   }
/// @example css
///   @media screen and (min-width: 42.5) and (max-width: 75em) {
///      // Your Styles
///   }
/// @param {String} $size-min The name of the smallest breakpoint size
/// @param {String} $size-max The name of the largest breakpoint size

@mixin min-max($size-min, $size-max) {
  // Adding the small fraction to prevent overlap
  $min: get-size($size-min); 
  $max: get-size($size-max); 
  @media screen and (min-width: $min) and (max-width: $max) {
    @content;
  }
}

/// Create a media query from a specific size in either direction 
/// - This is for mostly programmatic usage, so that a user could pass a breakpoint configuration that may contain values that go in either direction
/// - This way you don't need to repeat conditions (ie if min ... else ...)
/// @example scss
///   $size: map.get($user-breakpoint, "size");
///   $dir: map.get($user-breakpoint, "direction");
///   @include breakpoints.from($size, $dir) {
///     // Your styles
///   }
/// @param {String} $name The name of the breakpoint size
/// @param {String} $direction The direction the media query should target (min|up, max|down)

@mixin from($name, $direction: "min") {
  @if ($direction == "min" or $direction == "up") {
    @include min($name) {
      @content;
    }
  } @else if ($direction == "max" or $direction == "down") {
    @include max($name) {
      @content;
    }
  } @else {
    @error "ULU: Mixin error (breakpoint.from), incorrect argument '$direction' use either (min, max, up, down)";
  }
}

/// Utility Method for iterating over a map of breakpoints and apply styles
/// @example scss
///   @include breakpoints.fromEach($breakpoints) using ($props) {
///     width: map.get($props, "width");
///   }
/// @param {String} $breakpoints A map with breakpoints direction will be pulled from each items "direction" property, if direction is missing no breakpoint will wrap the code (as convention we call the default non-breakpoint direction "default")
/// @param {String} $options A map with options to change the behavior
/// @param {Boolean} $options.directionRequired Require direction throw error if missing direction

@mixin from-each($breakpoints, $options: ()) {
  $directionRequired: map.get($options, "directionRequired");
  @each $name, $props in $breakpoints {
    $direction: map.get($props, "direction");
    // If direction print with breakpoint
    @if ($direction) {
      @include from($name, $direction) {
        @content($props);
      }
    // If user requires direction throw error if missing
    } @else if ($directionRequired) {
      @debug $breakpoints;
      @error 'ULU: Missing required "direction" in breakpoints map (above).';
    // Else direction wasn't required print without media query (useful for default style)
    } @else {
      @content($props);
    }
  }
}

/// Attaches breakpoints to an element pseudo content for access via script
/// - Note you can also use cssvar.declare-breakpoints to get a similar implementation with css custom-properties
/// - Use with ulu/js/breakpoints (class has options for content property or css custom property)
/// - Breakpoints always min-width (upwards) for javascript setup

@mixin embed-for-scripts() {
  &::before {
    display: none;
    content: get("null-name");
    @each $size, $breakpoint in $sizes {
      @include min($size) {
        content: $size;
      }
    }
  }
}
