@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
////
/// @group site.config
////

// Default map that contains all config settings for the site (private)
$-config: () !default;

@use '../utils/strings.scss' as *;
@use '../utils/collections.scss' as *;
@use '../utils/flatmap.scss' as *;
@use '../utils/units.scss' as *;

/// Sets the current configuration. This can be used when a module is loaded to ensure that settings
/// are shared between modules.
/// @param {*} $config

@mixin set-config-map($config) {
    @if (list.length(map.keys($-config)) > 0) {
        @warn 'The px-styles configuration map has already been set. This could cause unexpected behavior.';
    }
    $-config: $config !global;
}

/// Gets the current configuration.

@function get-config-map() {
    @return $-config;
}

/// Check if a configuration setting exists.
///
/// @param {string} $path The path of the setting with keys separated by a period.

@function config-exists($path) {
    @return flat-has-key($-config, $path);
}

/// Retrieve a configuration setting.
///
/// @param {string or list} $paths The path (string) or list of paths (list) where the setting should
/// be found. Each path is one or more keys separated by a period (e.g. 'colors.accent' would find the
/// value accent in the colors map). If a list of strings is provided, they will searched in order
/// and the first non-null value will be returned.
///
/// @param {*} $fallback [null] The value to return if the path doesn't exist.
///
/// @param {*} $throw-not-found [null] Whether to throw an errow if the value can't be retrieved
///
/// @example
/// $value: get('colors.mycolor', red);

@function get($paths, $fallback: 'INVALID_FALLBACK') {
    // $paths is a list so we get the first non-null value
    @if (meta.type-of($paths) == 'list') {
        @each $path in $paths {
            @if (flat-has-key($-config, $path)) {
                @return flat-get($-config, $path);
            }
        }
    }

    // $paths is a string
    @else {
        @if (flat-has-key($-config, $paths)) {
            @return flat-get($-config, $paths);
        }
    }

    @if ($fallback == 'INVALID_FALLBACK') {
        $path-str: if(sass(meta.type-of($paths) == 'list'): list-join($paths, ', ') ; else: $paths);
        @warn '[CONFIG] Could not find required config value for path "#{$path-str}".';
    }

    @return $fallback;
}

/// Retrieve a configuration setting if and only if the provided $value is null.
///
/// @param {string or list} $value The value to check for null
/// @param {string or list} $paths Path for the value to retrieve if $value is null (see details for get())
/// @param {*} $throw-not-found [null] Whether to throw an errow if the value can't be retrieved
///
/// @example
/// $value: if-null-get(null, 'colors:mycolor'); // => the config value for 'colors:mycolor'
///
/// @example
/// $value: if-null-get(red, 'colors:mycolor'); // => red

@function if-null-get($value, $paths, $throw-not-found: true) {
    @return if-null(
        $value,
        get($paths, $fallback: if(sass($throw-not-found): 'INVALID_FALLBACK' ; else: null))
    );
}

/// ---------------------------------------------------------------------------
/// Add a configuration setting.
///
/// @param {*} $path The path of the setting with keys separated by periods (e.g.
/// 'colors.accent' would set the value accent in the colors map).
///
/// @param {*} $val [null] The value to be added.
/// ---------------------------------------------------------------------------

@mixin config($path, $val) {
    $-config: flat-set($-config, $path + '', $val) !global;
}

/// ---------------------------------------------------------------------------
/// Add a default configuration setting (provides the same functionality as config, but will not
/// overwrite an existing setting).
///
/// @param {*} $path The path of the setting with keys separated by periods (e.g. 'colors.accent'
/// would set the value accent in the colors map).
///
/// @param {*} $val [null] The value to be added.
/// ---------------------------------------------------------------------------

@mixin default($path, $val) {
    $current: get($path, $fallback: 'NOTFOUND');

    // if the value is not found, then we can just write it
    @if ($current == 'NOTFOUND') {
        @include config($path, $val);
    }

    // if the value exists and is a map then we do deep merge
    @else {
        @if (is-map($current) and is-map($val)) {
            @include config($path, flat-merge($val, $current));
        }
    }
}

/// ---------------------------------------------------------------------------
/// Set multiple defaults using a map where the key is the path and the value is the default value.
///
/// @param {map} $defaults A map of paths and default values.
/// ---------------------------------------------------------------------------
@mixin defaults($defaults) {
    @each $path, $val in $defaults {
        @include default($path, $val);
    }
}

/// ---------------------------------------------------------------------------
/// Handles the unique scenario of isolating an "instance" of px-styles from other configurations.
/// px-styles can be reinitialized within the content block of this mixin without effecting other
/// global configurations. This is pretty experimental and could have side effects. Use with caution
/// for now.
///
/// @example
///
///     // configure px with a blue accent and initialize
///     @include config('colors.accent', blue);
///     @include init();
///
///     .px-og-class1 {
///         /* should be blue */
///         color: accent();
///     }
///
///     @include scoped-config() {
///         /// configure px with a green accent and initialize
///         @include config('colors:accent', green);
///         @include init();
///         .px-scope-class {
///             /* should be green */
///             color: accent();
///         }
///     }
///
///     .px-og-class2 {
///         /* should be blue */
///         color: accent();
///     }
/// ---------------------------------------------------------------------------

@mixin scoped-config() {
    // backup the existing config
    $prev-config: $-config;

    // reset the config to empty (it will need to be reinitialized in the @content block)
    $-config: () !global;

    // do stuff
    @content;

    // restore the previous config
    $-config: $prev-config !global;
}
