// 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 '../tools';
@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
@use 'sass:string';

@use 'validators';

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

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

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

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

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

/// Stores all the tokens for getting raw values.
/// @access private
$_ds-token-registry: ();

/// Creates a theme token.
/// @param {string} $key
/// @param {*} $value
/// @return {*} returns a token as CSS custom properties with prefix and context.
@mixin create($key, $value) {
  --#{_sanitize-prefix($ds-prefix)}#{_sanitize-context($ds-context)}#{_sanitize-key-in-custom-property($key)}: #{$value};
  @include _register-token($key, $value);
}

/// Configures a set of tokens, either using maps or separate arguments, to a selector.
/// @param {map} $token-map
/// @param {args} $tokens...
/// @return {*} CSS-ized tokens.
@mixin config($map: (), $tokens...) {
  @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 create('#{$key}', $_value);
            }
          } @else {
            @if meta.type-of($_value) == map {
              @include config(('#{$key}#{$ds-separator}#{$_key}': $_value));
            } @else {
              @include create('#{$key}#{$ds-separator}#{$_key}', $_value);
            }
          }
        }
      } @else {
        @include create('#{$key}', $value);
      }
    }
  }

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

        @include create($key, $value);

      }
    }
  }
}

/// Retrieves an existing theme token.
/// @param {string} $key
/// @param {boolean} $important
/// @return {string} tokenized css custom property selector.
@function get($key, $important: false) {
  @if _is-registered-token($key) {
    @return var(--#{_sanitize-prefix($ds-prefix)}#{_sanitize-context($ds-context)}#{_sanitize-key-in-custom-property($key)});
  }
  @if not $validation {
    @return var(--#{_sanitize-prefix($ds-prefix)}#{_sanitize-context($ds-context)}#{_sanitize-key-in-custom-property($key)});
  }
  @if $important {
    @return var(--#{_sanitize-prefix($ds-prefix)}#{_sanitize-context($ds-context)}#{_sanitize-key-in-custom-property($key)}) !important;
  }
}

/// Retrieves the raw value of an existing theme token.
/// @param {string} $key
/// @param {boolean} $important
/// @return {*} token value.
@function get-raw($key, $important: false) {
  @if _is-registered-token($key) {
    @return map.get($_ds-token-registry, $key);
  }
  @if not $validation {
    @return map.get($_ds-token-registry, $key);
  }
  @if $important {
    @return map.get($_ds-token-registry, $key) !important;
  }
}

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

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

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

/// Check if the query is a token or not. If the query is a token, output a token. If not, output as is.
/// @param {*} $query
/// @param {*} $fallback
/// @param {boolean} $important
/// @return {token|*} token or raw value.
@function switch($query, $fallback: (), $important: false) {
  @if not check($query) {
    @if not validators.is-empty($fallback) {
      @if $important {
        @return $fallback !important;
      }
      @return $fallback;
    }
    @if $important {
      @return $query !important;
    }
    @return $query;
  }
  @if $important {
    @return get($query) !important;
  }
  @return get($query);
}

/// Check if the query is a token or not. If the query is a token, output a raw token value. If not, output as is.
/// @param {*} $query
/// @param {*} $fallback
/// @param {boolean} $important
/// @return {token|*} token raw value or raw value.
@function switch-raw($query, $fallback: (), $important: false) {
  @if not check($query) {
    @if not validators.is-empty($fallback) {
      @if $important {
        @return $fallback !important;
      }
      @return $fallback;
    }
    @if $important {
      @return $query !important;
    }
    @return $query;
  }
  @if $important {
    @return get-raw($query) !important;
  }
  @return get-raw($query);
}

/// Checks if the query is a valid registered token. This is a different method from the private one.
/// @param {*} query
/// @return {boolean} validated query.
@function check($query) {
  @return _is-registered-token($query, false);
}

/// Sanitizes and verifies a key.
/// @access private
/// @param {string} $key
/// @return {string} sanitized and validated key.
@function _sanitize-key($key) {
  @if string.index($key, '$') {
    @return string.slice($key, 2);
  }
  @return $key;
}

/// Sanitizes and verifies a context.
/// @access private
/// @param {string} $context
/// @return {string} sanitized and validated context.
@function _sanitize-context($context) {
  @if validators.is-empty($context) {
    @return '';
  }
  @return '#{$context}-';
}

/// Sanitizes and verifies a prefix.
/// @access private
/// @param {string} $prefix
/// @return {string} sanitized and validated prefix.
@function _sanitize-prefix($prefix) {
  @if validators.is-empty($prefix) {
    @return '';
  }
  @return '#{$ds-prefix}-';
}

/// Sanitizes and verifies a key.
/// @access private
/// @param {string} $key
/// @return {string} sanitized and validated context.
@function _sanitize-key-in-custom-property($key) {
  @if $ds-separator != '-' {
    @return tools.string-replace(_sanitize-key($key), $ds-separator, '-');
  }
  @return _sanitize-key($key);
}

/// Registers a token to the token registry variable.
/// @access private
/// @param {string} $key
/// @param {*} $value
/// @return {void} token registry
@mixin _register-token($key, $value) {
  $_ds-token-registry: map.set($_ds-token-registry, _sanitize-key($key), $value) !global;
}

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