@function __pop($list) {
    $length: length($list);
    $index: 2;
    $popped-list: ();

    @while $index <= $length {
        $popped-list: append($popped-list, nth($list, $index));
        $index: $index + 1;
    }

    @return $popped-list;
}


@function __list-get($list, $index) {
    $index: __parse-float($index);

    @if type-of($index) != 'number'
        or $index < 1 {
        @return $__undefined__;
    }

    @if $index > length($list) {
        @return $__undefined__;
    }

    @return nth($list, $index);
}


@function __string-get($string, $index, $default: null) {
    $index: __parse-float($index);

    @if type-of($index) != 'number' {
        @error '[__string-get] $index #{inspect($index)} for $string #{inspect($string)} is not a number.';
        @return $default;
    }

    @if $index > str-length($string) {
        @return $default;
    }

    @return str-slice($string, $index, $index);
}



@function __to-keys($keys, $map) {
    @if type-of($keys) == 'string' {
        @if not (type-of($map) == 'map' and map-has-key($map, $keys)) {
            $keys: __string-split($keys, '.');
        }
    }

    @return $keys;
}


@function __get($map, $keys, $default: null) {
    $value-type: type-of($map);

    $original-key: $keys;
    $keys: __to-keys($keys, $map);

    @if $value-type == 'string' {
        @return __string-get($map, nth($keys, 1), $default);
    }

    @if $value-type != 'list' and $value-type != 'map' {
        @return $default;
    }

    @if $value-type == 'list' {
        $index: nth($keys, 1);

        $map: __list-get($map, $index);
    } @else if $value-type == 'map' {
        // Check if map has original key
        @if map-has-key($map, $original-key) {
            @return map-get($map, $original-key);
        }
        
        $key: nth($keys, 1);

        @if map-has-key($map, $key) {
            $map: map-get($map, nth($keys, 1));
        } @else {
            @return $default;
        }
    }

    $keys: __pop($keys);

    @if __is-falsey($map) and length($keys) > 0 {
        @return $default;
    }

    @if length($keys) == 0 {
        @return if(__is-undefined($map), $default, $map);
    }

    @return __get($map, $keys, $default);
}


@function __set-list($list, $index, $value) {
    $length: length($list);

    @if type-of($index) != 'number' {
        @error '[__set-list] $index #{$index} for list #{inspect($list)} is not a number.';
        @return null;
    }

    @if $index <= $length {
        @return set-nth($list, $index, $value);
    }

    @while $index > $length + 1 {
        $list: append($list, null);

        $index: $index - 1;
    }

    $list: append($list, $value);

    @return $list;
}


@function __set($map, $keys, $value) {
    $property: nth($keys, 1);
    $keys: __pop(__to-keys($keys, $map));
    $child-map: null;

    @if type-of($map) == 'list' and length($map) > 0 {
        @if $property > length($map) {
            $child-map: ();
        } @else {
            $child-map: nth($map, $property);
        }
    } @else if map-has-key($map, $property) {
        $child-map: map-get($map, $property);
    } @else {
        $child-map: ();
    }

    @if length($keys) == 0 {
        @if type-of($map) == 'list' and length($map) > 0 {
            @return __set-list($map, $property, $value);
        }

        @return map-merge($map, (
            $property: $value,
        ));
    }

    @if type-of($map) == 'list' and length($map) > 0 {
        @return __set-list($map, $property, __set($child-map, $keys, $value));
    }

    @return map-merge($map, (
        $property: __set($child-map, $keys, $value),
    ));
}


@function __push($map, $properties, $value) {
    $list: append(__get($map, $properties), $value);

    @return __set($map, $properties, $list);
}


@function __new($type, $params: (), $extends: false, $defaults: ()) {
    $instance-meta: (
        '_type': $type,
        '_extends': $extends
    );

    $instance: ();

    @if $defaults {
        $instance: map-merge($instance, $defaults);
    }

    @if $extends {
        $instance: map-merge($instance, __new($extends, $params));
    }

    $instance: map-merge($instance, call(get_function($type), $params...));
    $instance: map-merge($instance, $instance-meta);

    @return $instance;
}

@function __instance-of($value, $constructor) {
    @if not type-of($value) == 'map' {
        @return type-of($value) == $constructor;
    }

    @return __get($value, '_type') == $constructor;
}


/// Gets the value of a source `$value` from the specified key, or list
/// of keys for deep map retrieval, or `null` if any of the keys are
/// undefined.
/// 
/// If `$value` is list-like, indexes are used as keys.
/// 
/// 
/// @access public
/// @group Utility
/// @param {Map|List|String|Arglist} $value - Source value.
/// @param {*|List} $keys - Single key or list of keys for value retrieval
/// @returns {*} Returns the retrieved value.
/// @example scss
/// $palette: (
///     'primary': (
///         'default': red,
///         'dark': darken(red, 10%)
///     )
/// );
/// 
/// $color-primary: _get($palette, 'primary' 'default');
/// // => red
/// 
/// $color-dark: _get($palette, 'primary.dark');
/// // => #CC0000

@function _get($args...) {
    @return call(get-function('__get'), $args...);
}


/// Sets the value of a source `$value` at the specified key, or list
/// of keys for deep map setting.
/// 
/// If `$value` is list-like, indexes are used as keys.
/// 
/// 
/// @access public
/// @group Utility
/// @param {Map|List|String|Arglist} $value - Source value.
/// @param {*|List} $keys - Single key or list of keys for value setting
/// @returns {*} Returns a new `$value` with the set value.
/// @example scss
/// $palette: (
///     'primary': (
///         'default': red,
///         'dark': darken(red, 10%)
///     )
/// );
/// 
/// $palette: _set($palette, 'primary' 'default', blue);
/// // => (
/// //     'primary': (
/// //         'default': blue,
/// //         'dark': darken(red, 10%)
/// //     )
/// // );

@function _set($args...) {
    @return call(get-function('__set'), $args...);
}


@function _new($args...) {
    @return call(get-function('__new'), $args...);
}

@function _instance-of($args...) {
    @return call(get-function('__instance-of'), $args...);
}
