////
/// Threads Core
/// Utilities
/// @group core
/// @author Curtis Dulmage
////

/// Returns the list of available names in a given map.
/// @param {Map} $map - The map of data to list the names from.
/// @param {Number} $map (optional) - The level of depth to get names from.
/// @return {String} The list of names in the map.
@function available-names($map, $level: 1) {
  @if type-of($map) != map {
    @return null;
  }

  $output: '';
  $newline: '\A ';

  @if $level == 1 {
    @each $key, $value in $map {
      $output: $output + '#{$newline}- #{$key} #{available-names($value, $level + 1)}';
    }
  } @else {
    $output: '(';
    $i: 1;

    @each $key, $value in $map {
      $sep: if($i < length($map), ', ', '');
      $output: $output + '#{$key}#{$sep}#{available-names($value, $level + 1)}';
      $i: $i + 1;
    }

    $output: $output + ')';
  }

  @return $output;
}

/// Fetch nested keys from a map.
/// @param {Map} $map - The map you want to check against.
/// @param {Arglist} $keys - Keys to fetch
/// @return {Map} The value held by the key you want fetched.
@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);
  }

  @return $map;
}

/// Merge multiple maps into one.
/// @param {Map} $map - Initial default map.
/// @param {ArgList} $maps - Other maps to merge.
/// @return {Map} The final merged map.
@function map-extend($map, $maps...) {
  @for $i from 1 through length($maps) {
    @each $key, $value in nth($maps, $i) {
      @if (type-of($value) == map) and (type-of(map-get($map, $key)) == map) {
        $value: map-extend(map-get($map, $key), $value);
      }

      $map: map-merge($map, ($key: $value));
    }
  }

  @return $map;
}

/// Returns the map value for a given map name and group.
/// @param {Map} $data - The data map for the given thread.
/// @param {String} $key - The map's key.
/// @param {String} $variant - The map's variant.
/// @return {Value} The requested value.
@function threads-value-get($data, $key, $variant) {
  $value-is-map: type-of(map-get($data, $key)) == map;

  $fetched-value: if(
    $value-is-map,
    map-deep-get($data, $key, $variant),
    map-get($data, $key)
  );

  // need to perform this craziness because
  // SASS seems to think `purple == #800080` is true
  $options: map-keys($data);
  $options-index: index($options, $fetched-value);
  $options-item: if(
    $options-index,
    nth($options, $options-index),
    null
  );

  $has-key: map-has-key($data, $fetched-value);
  $equal-keys: '#{$options-item}' == '#{$fetched-value}';

  // if the fetched value matches another key, get the value of that matching key
  // if the value is a map, the final fetched value will be the `threads-default-value`,
  // which is defined in config.scss
  @if $has-key and $equal-keys {
    $value-is-map: type-of(map-get($data, $fetched-value)) == map;

    $fetched-value: if(
      $value-is-map,
      map-deep-get($data, $fetched-value, $threads-default-value),
      map-get($data, $fetched-value)
    );
  }

  @return $fetched-value;
}

////
/// Threads Core
/// Configuration
/// @group core
/// @author Curtis Dulmage
////

/// Global map to hold all Threads properties
/// @type {Map} Contains key value pairs
$threads-properties: ();

/// Global default property name
/// @type {String} Allows for a default property value
$threads-default-value: 'base';

/// Merges a new Threads property into the global map.
/// Used internally by the corresponding mixin.
/// @access private
/// @param {String} $property-name - Name of the key you wish to target or define.
/// @param {Map} $custom-map - The custom map containing new and/or override keys + values.
/// @return {Map} The new merged map, to be assigned within the consumer mixin.
/// @throw Property `cyan` not found. Available properties: (red, green, blue)
@function _threads-update-property($property-name, $custom-map) {
  @if map-has-key($threads-properties, $property-name) {
    // since this function is executed within a mixin,
    // the updated variable must be declared as `!global`
    $threads-properties: map-extend(
      $threads-properties, ($property-name: $custom-map)
    ) !global;

    @return $threads-properties;
  } @else {
    @error 'Property `#{$property-name}` not found. Available properties: #{available-names($threads-properties)}';
  }
}

/// The preffered method for updating all Threads property maps.
/// Assigns the updated maps to a $threads-config "shell" variable.
/// @param {String} $property-name - Name of the key you wish to target or define.
/// @param {Map} $custom-map - The custom map containing new and/or override keys + values.
@mixin threads-update-property($property-name, $custom-map) {
  $threads-config: _threads-update-property($property-name, $custom-map);
}

////
/// Threads Properties
/// Border
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$border-label: 'border';

$threads-properties: map-merge(
  $threads-properties, (
    $border-label: ()
  )
);

/// Returns the shorthand border value for the given group and variant.
/// @param {String} $group (optional) - The key for the border group.
/// @param {String} $variant (optional) - The key for the border variant.
/// @return {List} The shorthand border value with width, style, and color.
@function get-border($group: $threads-default-value, $variant: $threads-default-value) {
  $border-data: map-get($threads-properties, $border-label);
  $fetched-border: threads-value-get($border-data, $group, $variant);

  @if type-of($fetched-border) == number or type-of($fetched-border) == string or type-of($fetched-border) == list {
    @return $fetched-border;
  } @else {
    @error 'Border `#{$group} - #{$variant}` not found. Available options: #{available-names($border-data)}';
  }
}

////
/// Threads Properties
/// Breakpoint
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$breakpoint-label: 'breakpoint';

$threads-properties: map-merge(
  $threads-properties, (
    $breakpoint-label: ()
  )
);

/// Returns the breakpoint width for the given group and variant.
/// @param {String} $group (optional) - The key for the breakpoint group. [helpful to think of this as `orientation` (portrait / landscape)]
/// @param {String} $variant (optional) - The key for the breakpoint variant. [helpful to think of this as `device` (mobile / tablet / desktop)]
/// @return {Number} The breakpoint width for the given group and variant.
@function get-breakpoint($group: $threads-default-value, $variant: $threads-default-value) {
  $breakpoint-data: map-get($threads-properties, $breakpoint-label);
  $fetched-breakpoint: threads-value-get($breakpoint-data, $group, $variant);

  @if type-of($fetched-breakpoint) == number {
    @return $fetched-breakpoint;
  } @else {
    @error 'Breakpoint `#{$group} - #{$variant}` not found. Available options: #{available-names($breakpoint-data)}';
  }
}

////
/// Threads Properties
/// Color
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$color-label: 'color';

$threads-properties: map-merge(
  $threads-properties, (
    $color-label: ()
  )
);

/// Returns the color value for the given group and variant.
/// @param {String} $group (optional) - The key for the color group. [helpful to think of this as `hue` or `action` (red / blue / primary / secondary)]
/// @param {String} $variant (optional) - The key for the color variant. [helpful to think of this as `shade` (lightness / darkness)]
/// @return {Color} The color value.
@function get-color($group: $threads-default-value, $variant: $threads-default-value) {
  $color-data: map-get($threads-properties, $color-label);
  $fetched-color: threads-value-get($color-data, $group, $variant);

  @if type-of($fetched-color) == color {
    @return $fetched-color;
  } @else {
    @error 'Color `#{$group} - #{$variant}` not found. Available options: #{available-names($color-data)}';
  }
}

////
/// Threads Properties
/// Cursor
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$cursor-label: 'cursor';

$threads-properties: map-merge(
  $threads-properties, (
    $cursor-label: ()
  )
);

/// Returns the url path and fallback value for the given group and variant.
/// @param {String} $group (optional) - The key for the cursor group.
/// @param {String} $variant (optional) - The key for the cursor variant.
/// @return {String} The url path to the image asset and the fallback value.
@function get-cursor($group: $threads-default-value, $variant: $threads-default-value) {
  $cursor-data: map-get($threads-properties, $cursor-label);
  $fetched-cursor: threads-value-get($cursor-data, $group, $variant);

  @if type-of($fetched-cursor) == string or type-of($fetched-cursor) == list {
    @return $fetched-cursor;
  } @else {
    @error 'Cursor `#{$group} - #{$variant}` not found. Available options: #{available-names($cursor-data)}';
  }
}

////
/// Threads Properties
/// Duration
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$duration-label: 'duration';

$threads-properties: map-merge(
  $threads-properties, (
    $duration-label: ()
  )
);

/// Returns the animation or transition duration for the given group and variant.
/// @param {String} $group (optional) - The key for the duration group.
/// @param {String} $variant (optional) - The key for the duration variant.
/// @return {Number} The animation or transition duration.
@function get-duration($group: $threads-default-value, $variant: $threads-default-value) {
  $duration-data: map-get($threads-properties, $duration-label);
  $fetched-duration: threads-value-get($duration-data, $group, $variant);

  @if type-of($fetched-duration) == number {
    @return $fetched-duration;
  } @else {
    @error 'Duration `#{$group} - #{$variant}` not found. Available options: #{available-names($duration-data)}';
  }
}

////
/// Threads Properties
/// Easing
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$easing-label: 'easing';

$threads-properties: map-merge(
  $threads-properties, (
    $easing-label: ()
  )
);

/// Returns the animation or transition timing function for the given group and variant.
/// @param {String} $group (optional) - The key for the easing group.
/// @param {String} $variant (optional) - The key for the easing variant.
/// @return {String} The animation or transition timing function.
@function get-easing($group: $threads-default-value, $variant: $threads-default-value) {
  $easing-data: map-get($threads-properties, $easing-label);
  $fetched-easing: threads-value-get($easing-data, $group, $variant);

  @if type-of($fetched-easing) == string {
    @return $fetched-easing;
  } @else {
    @error 'Animation timing `#{$variant}` not found. Available timings: #{available-names($easing-data)}';
  }
}

////
/// Threads Properties
/// Font Stack
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$font-stack-label: 'font-stack';

$threads-properties: map-merge(
  $threads-properties, (
    $font-stack-label: ()
  )
);

/// Returns the font-stack for the given group and variant.
/// @param {String} $group (optional) - The key for the font-stack group. (helpful to think of this as the `family` (Futura / Courier))
/// @param {String} $variant (optional) - The key for the font-stack variant. (helpful to think of this as the `font variant` (thin / light / bold / heavy))
/// @return {List} The font-stack for the requested family.
@function get-font-stack($group: $threads-default-value, $variant: $threads-default-value) {
  $font-stack-data: map-get($threads-properties, $font-stack-label);
  $fetched-font-stack: threads-value-get($font-stack-data, $group, $variant);

  @if type-of($fetched-font-stack) == string or type-of($fetched-font-stack) == list {
    @return $fetched-font-stack;
  } @else {
    @error 'Font-stack `#{$group} - #{$variant}` not found. Available options: #{available-names($font-stack-data)}';
  }
}

////
/// Threads Properties
/// Layout Length
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$layout-length-label: 'layout-length';

$threads-properties: map-merge(
  $threads-properties, (
    $layout-length-label: (),
  )
);

/// Returns the layout length for the given group and variant.
/// @param {String} $group (optional) - The key for the layout-length group.
/// @param {String} $variant (optional) - The key for the layout-length variant.
/// @return {Number} The layout length.
@function get-layout-length($group: $threads-default-value, $variant: $threads-default-value) {
  $layout-length-data: map-get($threads-properties, $layout-length-label);
  $fetched-layout-length: threads-value-get($layout-length-data, $group, $variant);

  @if type-of($fetched-layout-length) == number {
    @return $fetched-layout-length;
  } @else {
    @error 'Layout length `#{$group} - #{$variant}` not found. Available options: #{available-names($layout-length-data)}';
  }
}

////
/// Threads Properties
/// Radius
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$radius-label: 'radius';

$threads-properties: map-merge(
  $threads-properties, (
    $radius-label: ()
  )
);

/// Returns the border-radius for the given group and variant.
/// @param {String} $group (optional) - The key for the border-radius group.
/// @param {String} $variant (optional) - The key for the border-radius variant.
/// @return {Number | List} The border-radius value.
@function get-radius($group: $threads-default-value, $variant: $threads-default-value) {
  $radius-data: map-get($threads-properties, $radius-label);
  $fetched-radius: threads-value-get($radius-data, $group, $variant);

  @if type-of($fetched-radius) == number or type-of($fetched-radius) == list {
    @return $fetched-radius;
  } @else {
    @error 'Border `#{$group} - #{$variant}` not found. Available options: #{available-names($radius-data)}';
  }
}

////
/// Threads Properties
/// Shadow
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$shadow-label: 'shadow';

$threads-properties: map-merge(
  $threads-properties, (
    $shadow-label: ()
  )
);

/// Returns the box/text-shadow value for the given group and variant.
/// @param {String} $group (optional) - The key for the shadow group.
/// @param {String} $variant (optional) - The key for the shadow variant.
/// @return {List} The shorthand shadow value with width, style, and color.
@function get-shadow($group: $threads-default-value, $variant: $threads-default-value) {
  $shadow-data: map-get($threads-properties, $shadow-label);
  $fetched-shadow: threads-value-get($shadow-data, $group, $variant);

  @if type-of($fetched-shadow) == number or type-of($fetched-shadow) == string or type-of($fetched-shadow) == list {
    @return $fetched-shadow;
  } @else {
    @error 'Shadow `#{$group} - #{$variant}` not found. Available options: #{available-names($shadow-data)}';
  }
}

////
/// Threads Properties
/// Spacing
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$spacing-label: 'spacing';

$threads-properties: map-merge(
  $threads-properties, (
    $spacing-label: ()
  )
);

/// Returns the spacing for the given group and variant.
/// @param {String} $group (optional) - The key for the spacing group.
/// @param {String} $variant (optional) - The key for the spacing variant.
/// @return {Number} The spacing value.
@function get-spacing($group: $threads-default-value, $variant: $threads-default-value) {
  $spacing-data: map-get($threads-properties, $spacing-label);
  $fetched-spacing: threads-value-get($spacing-data, $group, $variant);

  @if type-of($fetched-spacing) == number {
    @return $fetched-spacing;
  } @else {
    @error 'Spacing `#{$group} - #{$variant}` not found. Available options: #{available-names($spacing-data)}';
  }
}

////
/// Threads Properties
/// Type Scale
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$type-scale-label: 'type-scale';

$threads-properties: map-merge(
  $threads-properties, (
    $type-scale-label: ()
  )
);

/// Returns the type-scale for the given group and variant.
/// @param {String} $group (optional) - The key for the type-scale group.
/// @param {String} $variant (optional) - The key for the type-scale variant.
/// @return {Number} The requested type-scale.
@function get-type-scale($group: $threads-default-value, $variant: $threads-default-value) {
  $type-scale-data: map-get($threads-properties, $type-scale-label);
  $fetched-type-scale: threads-value-get($type-scale-data, $group, $variant);

  @if type-of($fetched-type-scale) == number {
    @return $fetched-type-scale;
  } @else {
    @error 'type-scale `#{$group} - #{$variant}` not found. Available options: #{available-names($type-scale-data)}';
  }
}

////
/// Threads Properties
/// Z-Index
/// @group properties
/// @author Curtis Dulmage
////

/// Label used within $threads-properties
/// @type {String} Used when merging values into $threads-properties
$z-index-label: 'z-index';

$threads-properties: map-merge(
  $threads-properties, (
    $z-index-label: ()
  )
);

/// Returns the z-index for the given group and variant.
/// @param {String} $group (optional) - The key for the z-index group.
/// @param {String} $variant (optional) - The key for the z-index variant.
/// @return {Number} The z-index value.
@function get-z-index($group: $threads-default-value, $variant: $threads-default-value) {
  $z-index-data: map-get($threads-properties, $z-index-label);
  $fetched-z-index: threads-value-get($z-index-data, $group, $variant);

  @if type-of($fetched-z-index) == number {
    @return $fetched-z-index;
  } @else {
    @error 'z-index `#{$group} - #{$variant}` not found. Available options: #{available-names($z-index-data)}';
  }
}
