// Copyright (c) 2025 MatteuSan and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
@use 'sass:string';

@use 'validators';

/// Stores the separator for the design system.
/// @access public
$ds-separator: '-' !default;

/// Validation for breakpoints.
/// @access public
$validation: true !default;

/// Determines if errors contain extra information (i.e. valid values)
/// @access public
$verbose: false !default;

/// Stores all breakpoints of the design system. Reference for all mixins below
/// @access private
$_ds-breakpoint-registry: ();

/// Verifies a breakpoint.
/// @param {string} $query
/// @return {boolean} breakpoint verification.
@function check($query) {
  @return _is-registered-breakpoint($query, false);
}

/// Configures all the breakpoints to be used in the design system.
/// @param {*} $breakpoints...
/// @return {void} breakpoint configuration.
@mixin config($map: (), $breakpoints...) {
  @if not validators.is-empty($map) {
    @each $key, $value in $map {
      @if meta.type-of($value) == map {
        @each $_key, $_value in $value {
          @if $_key == 'default' {
            @if meta.type-of($_value) == map {
              @include config(('#{$key}': $_value))
            } @else {
              @include _register-breakpoint('#{$key}', $_value);
            }
          } @else {
            @if meta.type-of($_value) == map {
              @include config(('#{$key}#{$ds-separator}#{$_key}': $_value))
            } @else {
              @include _register-breakpoint('#{$key}#{$ds-separator}#{$_key}', $_value);
            }
          }
        }
      } @else {
        @include _register-breakpoint($key, $value);
      }
    }
  }

  @if meta.keywords($breakpoints) {
    @each $key, $value in meta.keywords($breakpoints) {
      @if meta.type-of($value) == map {
        @each $_key, $_value in $value {
          @if $_key == 'default' {
            @if meta.type-of($_value) == map {
              @include config(('#{$key}': $_value))
            } @else {
              @include _register-breakpoint('#{$key}', $_value);
            }
          } @else {
            @if meta.type-of($_value) == map {
              @include config(('#{$key}#{$ds-separator}#{$_key}': $_value))
            } @else {
              @include _register-breakpoint('#{$key}#{$ds-separator}#{$_key}', $_value);
            }
          }
        }
      } @else {
        @include _register-breakpoint($key, $value);
      }
    }
  }
}

/// Creates a breakpoint for an element.
/// @param {string} $query
/// @param {string} $context
/// @param {string} $property
/// @return {*} breakpoint.
@mixin create($query, $context: 'min', $property: 'width') {
  $_valid-contexts: ('min', 'max');
  $_valid-properties: ('width', 'height');

  @if not list.index($_valid-contexts, $context) {
    @if $verbose {
      @error 'ERROR [breakpoint-create()]: Invalid context: #{$context}. Expecting a valid context (#{$_valid-contexts}).';
    }
    @error 'ERROR [breakpoint-create()]: Invalid context: #{$context}.';
  }

  @if not list.index($_valid-properties, $property) {
    @if $verbose {
      @error 'ERROR [breakpoint-create()]: Invalid property: #{$property}. Expecting a valid property (#{$_valid-properties}).';
    }
    @error 'ERROR [breakpoint-create()]: Invalid property: #{$property}.';
  }

  @media screen and (#{$context}-#{$property}: _sanitize-breakpoint($query, $context)) {
    @content;
  }
}

/// Gets a breakpoint from query.
/// @param {string} $query
/// @return {int|null} breakpoint.
@function get($query) {
  @if _is-registered-breakpoint($query) {
    @return map.get($_ds-breakpoint-registry, $query);
  }
  @if not $validation {
    @return map.get($_ds-breakpoint-registry, $query);
  }
}

/// Retrieves the entire breakpoint registry.
/// @param {list<string>} $targets
/// @return {map} map of registered breakpoints.
@function registry-get($targets: (), $exceptions: ()) {
  @if not validators.is-empty($targets) {
    $targeted-breakpoints: ();
    @each $target in $targets {
      @each $key in map.keys($_ds-breakpoint-registry) {
        @if string.index($key, $target) == 1 {
          $targeted-breakpoints: list.append($targeted-breakpoints, $key, 'comma');
        }
      }
    }
    @return $targeted-breakpoints;
  }

  @if not validators.is-empty($exceptions) {
    $targeted-breakpoints: ();
    @each $key in map.keys($_ds-breakpoint-registry) {
      @if not list.index($exceptions, $key) {
        $targeted-breakpoints: list.append($targeted-breakpoints, $key, 'comma');
      }
    }
    @return $targeted-breakpoints;
  }

  @return map.keys($_ds-breakpoint-registry);
}

/// Registers a breakpoint to the registry.
/// @param {string} $key
/// @param {int} $value
/// @return {void} register breakpoint.
@mixin _register-breakpoint($key, $value) {
  $_ds-breakpoint-registry: map.set($_ds-breakpoint-registry, $key, $value) !global;
}

/// Validates a query if it's a registered token or not.
/// @access private
/// @param {string} $query
/// @param {string} $source
/// @return {boolean|error} is registered token
@function _is-registered-breakpoint($query, $errors: $validation, $source: '_is-registered-breakpoint') {
  @if not list.index(map.keys($_ds-breakpoint-registry), $query) {
    @if $errors {
      @if $verbose {
        @error 'ERROR [#{$source}()]: Invalid breakpoint: #{$query}. Please use a valid registered breakpoint: #{map.keys($_ds-breakpoint-registry)}';
      }
      @error 'ERROR [#{$source}()]: Invalid breakpoint: #{$query}.';
    } @else {
      @return false;
    }
  } @else {
    @return true;
  }
}

/// Cleans a breakpoint based on a given context.
/// @access private
/// @param {string} $query
/// @param {string} $context
/// @return {*} clean breakpoint.
@function _sanitize-breakpoint($query, $context) {
  @if not $context == 'min' or not $context == 'max' {
    @error 'Invalid context: #{$context}. Please select from the following valid contexts: min, max.';
  } @else if $context == 'min' {
    @return get($query);
  } @else if $context == 'max' {
    @return get($query) - 1;
  }
}