function normalizeToArray(items) {
    if (items === null || items === undefined)
        return [];
    if (typeof items[Symbol.iterator] === 'function') {
        return Array.from(items);
    }
    return Array.from(items);
}
function stableArray(items) {
    return items.slice();
}
function firstMatch(snapshot, fn) {
    if (!fn)
        return snapshot[0];
    for (let i = 0; i < snapshot.length; i += 1) {
        if (fn(snapshot[i], i))
            return snapshot[i];
    }
    return undefined;
}
function lastMatch(snapshot, fn) {
    if (!fn)
        return snapshot.at(-1);
    for (let i = snapshot.length - 1; i >= 0; i -= 1) {
        if (fn(snapshot[i], i))
            return snapshot[i];
    }
    return undefined;
}
function uniqueItems(snapshot, keySelector) {
    const seen = new Set();
    const out = [];
    for (const item of snapshot) {
        const key = keySelector ? keySelector(item) : item;
        if (seen.has(key))
            continue;
        seen.add(key);
        out.push(item);
    }
    return out;
}
function sortByKey(snapshot, keySelector) {
    const out = snapshot.slice();
    out.sort((a, b) => {
        const ka = keySelector(a);
        const kb = keySelector(b);
        const aNil = ka === null || ka === undefined;
        const bNil = kb === null || kb === undefined;
        if (aNil && bNil)
            return 0;
        if (aNil)
            return 1;
        if (bNil)
            return -1;
        if (typeof ka === 'number' && typeof kb === 'number')
            return ka - kb;
        return String(ka).localeCompare(String(kb));
    });
    return out;
}
function chunkItems(snapshot, size) {
    const n = Math.floor(size);
    if (!Number.isFinite(n) || n <= 0)
        return [];
    const out = [];
    for (let i = 0; i < snapshot.length; i += n) {
        out.push(snapshot.slice(i, i + n));
    }
    return out;
}
function keyByItems(snapshot, keySelector) {
    const out = new Map();
    for (const item of snapshot) {
        out.set(keySelector(item), item);
    }
    return out;
}
function groupByItems(snapshot, keySelector) {
    const groups = new Map();
    for (const item of snapshot) {
        const key = keySelector(item);
        const bucket = groups.get(key);
        if (bucket)
            bucket.push(item);
        else
            groups.set(key, [item]);
    }
    const out = new Map();
    for (const [k, bucket] of groups.entries()) {
        out.set(k, makeCollection(bucket));
    }
    return out;
}
function makeCollection(items) {
    const snapshot = stableArray(items);
    const api = Object.freeze({
        all: () => snapshot.slice(),
        toArray: () => snapshot.slice(),
        count: () => snapshot.length,
        isEmpty: () => snapshot.length === 0,
        map: (fn) => {
            if (typeof fn !== 'function') {
                throw new TypeError('Map callback must be a function');
            }
            return makeCollection(snapshot.map((item, index) => fn(item, index)));
        },
        filter: (fn) => {
            if (typeof fn !== 'function') {
                throw new TypeError('Filter callback must be a function');
            }
            return makeCollection(snapshot.filter((item, index) => fn(item, index)));
        },
        reduce: (fn, initial) => {
            if (typeof fn !== 'function') {
                throw new TypeError('Reduce callback must be a function');
            }
            return snapshot.reduce((accumulator, element, index) => fn(accumulator, element, index), initial);
        },
        first: (fn) => {
            if (fn && typeof fn !== 'function') {
                throw new TypeError('First callback must be a function');
            }
            return firstMatch(snapshot, fn);
        },
        last: (fn) => {
            if (fn && typeof fn !== 'function') {
                throw new TypeError('Last callback must be a function');
            }
            return lastMatch(snapshot, fn);
        },
        pluck: (key) => makeCollection(snapshot.map((item) => item[key])),
        where: (key, value) => makeCollection(snapshot.filter((item) => item[key] === value)),
        unique: (keySelector) => {
            if (keySelector && typeof keySelector !== 'function') {
                throw new TypeError('Unique key selector must be a function');
            }
            return makeCollection(uniqueItems(snapshot, keySelector));
        },
        sortBy: (keySelector) => {
            if (typeof keySelector !== 'function') {
                throw new TypeError('Sort key selector must be a function');
            }
            return makeCollection(sortByKey(snapshot, keySelector));
        },
        chunk: (size) => makeCollection(chunkItems(snapshot, size)),
        take: (n) => {
            const count = Math.max(0, Math.floor(n));
            return makeCollection(snapshot.slice(0, count));
        },
        skip: (n) => {
            const count = Math.max(0, Math.floor(n));
            return makeCollection(snapshot.slice(count));
        },
        keyBy: (keySelector) => keyByItems(snapshot, keySelector),
        groupBy: (keySelector) => groupByItems(snapshot, keySelector),
        tap: (fn) => {
            fn(snapshot.slice());
            return makeCollection(snapshot);
        },
        [Symbol.iterator]: function* () {
            for (const item of snapshot)
                yield item;
        },
    });
    return api;
}
export const Collection = Object.freeze({
    from: (items) => makeCollection(normalizeToArray(items)),
    of: (...items) => makeCollection(items),
    isCollection: (value) => {
        if (value === null || value === undefined)
            return false;
        return (typeof value === 'object' &&
            typeof value.all === 'function' &&
            typeof value.map === 'function' &&
            typeof value[Symbol.iterator] === 'function');
    },
});
export function collect(items) {
    return Collection.from(items);
}
