// ***************************************
//
// # Object
// -> Define and call reusable css objects
//    without bloat.
//
// Author: TxHawks (tofu.hawks@gmail.com)
//
// ***************************************

@charset 'UTF-8';

// -------------------------------------
// Dependencies
// -------------------------------------

@if (not (global-variable-exists(jigsass-breakpoints) and mixin-exists(jigsass-mq))) {
  @error '`jigsass-tools-selectors` has a dependency on `jigsass-tools-mq`. Please import it.';
}

@if (not function-exists(jigsass-strip-unit)) {
  @error '`jigsass-tools-selectors` has a dependency on `jigsass-tools-typography`. ' +
    'Please import it before this file.';
}
@if (not function-exists(jigsass-deep-merge)) {
  @error '`jigsass-tools-selectors` has a dependency on `jigsass-tools-maps`. ' +
    'Please import it before this file.';
}





// -------------------------------------
// Functions
// -------------------------------------

/// Get media query arguments of current context.
/// For use inside `jigsass-object`.
///
/// When called inside the `@contnet` block without arguments,
/// it will return a map with `from`, `until` and `misc` keys,
/// holding the arguments passed to the media query in the
/// current context of `@content`.
///
/// When called with an argument of `from`, `until` or `misc`, it
/// will return the value of of that key from the above map.
/// ---
/// @param {String | Boolean} $type [false]
///   The type of media-query argument to check.
///   May be `from`, `until` or `misc`.
///   If false, Will return a map of all of the above.
/// ---
/// @return {String | Map}
///   The value attached to the queried `$type`, or a map of all
///   three types.
/// ---
/// @group Selectors - 04 Define
/// ---
@function jigsass-get-obj-mq($type: false) {
  @if($jigsass-obj-mq-args) {
    @return if(
      $type,
      map-get($jigsass-obj-mq-args, $type),
      $jigsass-obj-mq-args
    );
  }

  @return null;
}





// -------------------------------------
// Variables
// -------------------------------------

/// The current `$modifier` argument in the running
/// context of the `jigsass-classname` mixin to
/// be used inside its `@content` block.
/// ---
/// @type Null | String
/// ---
/// @group Selectors - 05 Require
/// ---
$jigsass-obj-modifier: null;

/// The current media query arguments in the running
/// context of the `jigsass-object` mixin to
/// be used inside its `@content` block.
/// ---
/// @prop {Boolean | Number} from [false]
///   The min-width condition of the current context.
/// @prop {Boolean | Number} until [false]
///   The max-width condition of the current context.
/// @prop {Boolean | String | List} misc [false]
///   The miscellaneous media-query conditions of the current context.
/// ---
/// @type Map
/// ---
/// @group Selectors - 05 Require
/// ---
$jigsass-obj-mq-args: (from: false, until: false, misc: false);

/// The name of the object in the current running
/// context of the `jigsass-classname` mixin to
/// be used inside its `@content` block.
/// ---
/// @type Null | String
/// ---
/// @group Selectors - 05 Require
/// ---
$jigsass-obj-name: null;

/// The cofiguration map of the object in the current
/// running context of the `jigsass-object` mixin
//  for be use inside `jigsass-classname` mixin.
/// ---
/// @type Null | Map
/// ---
/// @group Selectors - 05 Require
/// ---
$jigsass-obj-config: null;





// -------------------------------------
// Mixins
// -------------------------------------

/// Define a reusable object class and generate it once,
/// if it is enabled in configuration map.
///
/// By default, including the `jigsass-object` mixin will not generate
/// any css. For any output to be generated, classes must be
/// explicitly enabled in the associated configuration map (passed
/// to the mixin as the `$config` param) _BEFORE_ the object is
/// `@include`d.
///
/// The structure of configuration maps is:
/// ```scss
/// // _objects.foo.scss
/// $foo-map: (
///   no-breakpoint: (
///     no-modifier: true,  // Enables generation of the `.o-foo`
///                         // class outside of any media query.
///     bar: true,          // Enables generation of the `.o-foo--bar`
///                         // modifier class outside of any media query.
///   ),
///   from-<bp-name>: (
///     no-modifier: true,  // Enables generation of the `.o-foo--from-<bp-name>`
///                         // class inside a min-width media query
///                         // defined ins `$jigsass-breakpoints.length`.
///     bar: true,          // Enables generation of the `.o-foo--bar--from-<bp-name>`
///                         // class inside a min-width media query
///                         // defined ins `$jigsass-breakpoints.length`.
///   ),
///   until-<bp-name>: (
///     no-modifier: true,  // Enables generation of the `.o-foo--until-<bp-name>`
///                         // class inside a max-width media query
///                         // defined ins `$jigsass-breakpoints.length`.
///     bar: true,          // Enables generation of the `.o-foo--bar--until-<bp-name>`
///                         // class inside a max-width media query
///                         // defined ins `$jigsass-breakpoints.length`.
///   ),
///   when-<bp-name>: (
///     no-modifier: true,  // Enables generation of the `.o-foo--when-<bp-name>`
///                         // class inside a misc media query
///                         // defined ins `$jigsass-breakpoints.features`.
///     bar: true,          // Enables generation of the `.o-foo--bar--when-<bp-name>`
///                         // class inside a misc media query
///                         // defined ins `$jigsass-breakpoints.features`.
///   ),
///   from-<bp-name>-until-<bp-name>: (...);
///   from-<bp-name>-when-<bp-name>: (...);
///   until-<bp-name>-when-<bp-name>: (...);
///   from-<bp-name>-until-<bp-name>-when-<bp-name>: (...);
/// );
/// ```
///
/// Regardless of how many times the `jigsass-object` mixin is
/// included, each selector will only be generated once.
///
/// If `$jigsass-silent` is set to `true` when the mixin
/// is first included for a certain selector, the selector
/// will not be generated, even if the mixin is later included
/// again for that selector when `$jigsass-silent` is set to false.
///
///
/// Use the `jigsass-classname` mixin inside this mixin's
/// body to define the base object and each of it's modifiers
/// **(See example below)**.
///
/// When calling the `jigsass-get-obj-mq` function without arguments
/// inside the `@contnet` block, it will return a map with `from`,
/// `until` and `misc` keys, holding the arguments passed to the
/// media query in the current context of `@content`.
///
/// When called with an argument of `from`, `until` or `misc`, the
/// `jigsass-get-obj-mq` will return the value of of that key from
/// the above map.
/// ---
/// @param {String} $name
///   The name of the object class being defined, sans modifiers.
///   A modifier's name
/// @param {Map} $conf
///   The configuration map in which the generation of the object's class
///   and modifier classes are enabled.
/// ---
/// @example
///   @include jigsass-object(o-box, $box-config) {
///     // Defines rules for `.o-box`
///     @include jigsass-classname() {
///       padding: if(jigsass-get-obj-mq(misc) == landscape, 24px 48px, 24px));
///     }
///
///     // Defines rules for `.box--cramped`
///     @include jigsass-classname(box, $modifier: cramped) {
///       padding: 12px;
///     }
///   }
/// ---
/// @group Selectors - 04 Define
/// ---
@mixin jigsass-object($name, $config) {
  // Reset media query args in global scope
  $jigsass-obj-mq-args: (from: false, until: false, misc: false) !global;

  // Set object name in global scope, so that it is
  // available to `@content` blocks.
  $jigsass-obj-name: $name !global;

  // Set object config map in global scope, so that it is
  // available to `@content` blocks.
  $jigsass-obj-config: $config !global;

  $_lengths: jigsass-mq-sort-length-breakpoints();
  $_features: map-get($jigsass-breakpoints, features);

  @if (
    not (
      global-variable-exists(_jigsass-selectors) and
      map-has-key($_jigsass-selectors, $name)
    )
  ) {
    $_jigsass-selectors: map-merge(
      $_jigsass-selectors,
      _jigsass-generate-selector-map($name)
    );
  }



  // Width breakpoints
  @if($_lengths) {
    // Min width breakpoints
    @each $bp-min, $length-min in $_lengths {
      $_bp-str: _jigsass-bp-string($bp-min, false, false);
      @if (map-has-key($config, $_bp-str)) {
        @include jigsass-mq($from: $bp-min) {
          @if ($length-min != 0) {
            // Set current mq args in global scope, so that
            // they are available to `@content` blocks.
            $jigsass-obj-mq-args: (from: $bp-min, until: false, misc: false) !global;
          }

          @content;
        }
      }
    }

    // Max width breakpoints
    @each $bp-max, $length-max in $_lengths {
      $_bp-str: _jigsass-bp-string(false, $bp-max, false);
      @if (map-has-key($config, $_bp-str)) {
        @if (jigsass-strip-unit($length-max) != 0) {
          @include jigsass-mq($until: $bp-max) {
            // Set current mq args in global scope, so that
            // they are available to `@content` blocks.
            $jigsass-obj-mq-args: (from: false, until: $bp-max, misc: false) !global;

            @content;
          }
        }
      }
    }

    // Min and max width breakpoints
    @each $bp-min, $length-min in $_lengths {
      @each $bp-max, $length-max in $_lengths {
        $_bp-str: _jigsass-bp-string($bp-min, $bp-max, false);
        @if (map-has-key($config, $_bp-str)) {
          @if (
            jigsass-strip-unit($length-min) != 0 and
            jigsass-strip-unit($length-max) != 0 and
            $length-max > $length-min
          ) {
            @include jigsass-mq($from: $bp-min, $until: $bp-max) {
              // Set current mq args in global scope, so that
              // they are available to `@content` blocks.
              $jigsass-obj-mq-args: (from: $bp-min, until: $bp-max, misc: false) !global;

              @content;
            }
          }
        }
      }
    }
  }

  // Misc media query features breakpoints
  @if ($_features) {
    @each $misc-bp, $expression in $_features {
      $_bp-str: _jigsass-bp-string(false, false, $misc-bp);
      @if (map-has-key($config, $_bp-str)) {
        @include jigsass-mq($misc: $misc-bp) {
          // Set current mq args in global scope, so that
          // they are available to `@content` blocks.
          $jigsass-obj-mq-args: (from: false, until: false, misc: $misc-bp) !global;

          @content;
        }
      }
    }

    // Width and misc media queries
    @if($_lengths) {
      // Min width and feature breakpoints
      @each $bp-min, $length-min in $_lengths {
        @if (jigsass-strip-unit($length-min) != 0) {
          @each $misc-bp, $expression in $_features {
            $_bp-str: _jigsass-bp-string($bp-min, false, $misc-bp);
            @if (map-has-key($config, $_bp-str)) {
              @include jigsass-mq($from: $bp-min, $misc: $misc-bp) {
                // Set current mq args in global scope, so that
                // they are available to `@content` blocks.
                $jigsass-obj-mq-args: (from: $bp-min, until: false, misc: $misc-bp) !global;

                @content;
              }
            }
          }
        }
      }

      // Max width and feature breakpoints
      @each $bp-max, $length-max in $_lengths {
        @if (jigsass-strip-unit($length-max) != 0) {
          @each $misc-bp, $expression in $_features {
            $_bp-str: _jigsass-bp-string(false, $bp-max, $misc-bp);
            @if (map-has-key($config, $_bp-str)) {
              @include jigsass-mq($until: $bp-max, $misc: $misc-bp) {
                // Set current mq args in global scope, so that
                // they are available to `@content` blocks.
                $jigsass-obj-mq-args: (from: false, until: $bp-max, misc: $misc-bp) !global;

                @content;
              }
            }
          }
        }
      }

      // Min and max width and feature breakpoints
      @each $bp-min, $length-min in $_lengths {
        @each $bp-max, $length-max in $_lengths {
          @if (
            jigsass-strip-unit($length-min) != 0 and
            jigsass-strip-unit($length-max) != 0 and
            $length-max > $length-min
          ) {
            @each $misc-bp, $expression in $_features {
              $_bp-str: _jigsass-bp-string($bp-min, $bp-max, $misc-bp);
              @if (map-has-key($config, $_bp-str)) {
                @include jigsass-mq($from: $bp-min, $until: $bp-max, $misc: $misc-bp) {
                  // Set current mq args in global scope, so that
                  // they are available to `@content` blocks.
                  $jigsass-obj-mq-args: (from: $bp-min, until: $bp-max, misc: $misc-bp) !global;
                  @content;
                }
              }
            }
          }
        }
      }
    }
  }

  // Reset in global scope
  $jigsass-obj-mq-args: (from: false, until: false, misc: false) !global;
  $jigsass-obj-name: null !global;
  $jigsass-obj-config: null !global;
}


/// Generate an object or object-modifier class name inside
/// `jigsass-object`
/// ---
/// @param {String | Number | false} $modifier [false]
///   A modifier's name
/// @param {String} $name [$jigsass-obj-name]
///   The object's base name. defaults to the name that was
///   passed in the calling `jigsass-object` mixin
/// ---
/// @example
///   @include jigsass-object(box) {
///     // Defines rules from `.box`
///     @include jigsass-classname() {
///       padding: if(jigsass-get-obj-mq(misc) == landscape, 24px 48px, 24px));
///     }
///
///     // Defines rules for `.box--cramped`
///     @include jigsass-classname() {
///     @include jigsass-classname(box, $modifier: cramped) {
///       padding: 12px;
///     }
///   }
/// ---
/// @see {mixin} jigsass-object
/// ---
/// @group Selectors - 04 Define
/// ---
@mixin jigsass-classname($modifier: false, $name: $jigsass-obj-name) {
  @if (not $name) {
    @error 'jigsass-classname: A `$name` is required for generating a selector.';
  }
  $from: jigsass-get-obj-mq(from);
  $until: jigsass-get-obj-mq(until);
  $misc: jigsass-get-obj-mq(misc);
  $bp-str: _jigsass-bp-string($from, $until, $misc);

  @if (
    map-get($jigsass-obj-config, $bp-str) and
    jigsass-get($jigsass-obj-config, $bp-str, $modifier or no-modifier)
  ) {
    // Make the current modifier available to `@content`
    $jigsass-obj-modifier: $modifier !global;

    $name: $jigsass-obj-name;
    $selector-str: _jigsass-gen-class-name($name, $from, $until, $misc, $modifier);

    // Make sure $modifier is not called `no-modifier`.
    @if ($modifier == 'no-modifier') {
      @error 'jigsass-classname: A jigsass class modifier cannot be called ' +
        '`no-modifier` (from #{$name}).';
    }

    // Check if selector was already generated
    $_was-included: _jigsass-class-was-included($name, $from, $until, $misc, $modifier);

    // Generate selector.
    // Will only generate CSS once, where it was first included,
    // regardless of how many times `jigsass-object` was called
    // with the same arguments.
    @if (not $_was-included) {
      // Check that we are not in silent mode
      @if (not $jigsass-silent) {
        $selector-str: _jigsass-gen-class-name($name, $from, $until, $misc, $modifier);

        .#{$selector-str} {
          @content;
        }
      }

      // Set class as defined in the `$_jigsass-selectors` map.
      $_selector-map: (
        $name: (
          $bp-str: (
            $modifier or no-modifier: if($jigsass-silent, silent, true)
          )
        )
      );

      @if (not global-variable-exists(_jigsass-selectors)) { $_jigsass-selectors: () !global; }
      $_jigsass-selectors: jigsass-deep-merge($_jigsass-selectors, $_selector-map) !global;
    }


    // Reset the current modifier globally
    $jigsass-obj-modifier: null !global;
  }
}
