import {refProperties} from './util/ref_properties';
import type {LayerSpecification} from './types.g';

function stringify(obj: any): string {
    const type = typeof obj;
    if (
        type === 'number' ||
        type === 'boolean' ||
        type === 'string' ||
        obj === undefined ||
        obj === null
    )
        return JSON.stringify(obj);

    if (Array.isArray(obj)) {
        let str = '[';
        for (const val of obj) {
            str += `${stringify(val)},`;
        }
        return `${str}]`;
    }

    const keys = Object.keys(obj).sort();

    let str = '{';
    for (let i = 0; i < keys.length; i++) {
        str += `${JSON.stringify(keys[i])}:${stringify(obj[keys[i]])},`;
    }
    return `${str}}`;
}

function getKey(layer: LayerSpecification): string {
    let key = '';
    for (const k of refProperties) {
        key += `/${stringify(layer[k])}`;
    }
    return key;
}

/**
 * Groups layers by their layout-affecting properties.
 * These are the properties that were formerly used by explicit `ref` mechanism
 * for layers: 'type', 'source', 'source-layer', 'minzoom', 'maxzoom',
 * 'filter', and 'layout'.
 *
 * The input is not modified. The output layers are references to the
 * input layers.
 *
 * @param layers - an array of {@link LayerSpecification}.
 * @param cachedKeys - an object to keep already calculated keys.
 * @returns an array of arrays of {@link LayerSpecification} objects, where each inner array
 * contains layers that share the same layout-affecting properties.
 */
export function groupByLayout(
    layers: LayerSpecification[],
    cachedKeys?: Record<string, string>
): LayerSpecification[][] {
    const groups: Record<string, LayerSpecification[]> = {};

    for (let i = 0; i < layers.length; i++) {
        const k: string = (cachedKeys && cachedKeys[layers[i].id]) || getKey(layers[i]);
        // update the cache if there is one
        if (cachedKeys) cachedKeys[layers[i].id] = k;

        let group = groups[k];
        if (!group) {
            group = groups[k] = [];
        }
        group.push(layers[i]);
    }

    const result: LayerSpecification[][] = [];

    for (const k in groups) {
        result.push(groups[k]);
    }

    return result;
}
