'use strict';

Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });

const isObject = require('./isObject.js');
const isPrimitive = require('../../predicate/isPrimitive.js');
const eq = require('../util/eq.js');

function isMatchWith(target, source, compare) {
    compare = typeof compare === 'function' ? compare : undefined;
    return isMatchWithInternal(target, source, function doesMatch(objValue, srcValue, key, object, source, stack) {
        const isEqual = compare?.(objValue, srcValue, key, object, source, stack);
        if (isEqual !== undefined) {
            return Boolean(isEqual);
        }
        return isMatchWithInternal(objValue, srcValue, doesMatch, stack);
    }, new Map());
}
function isMatchWithInternal(target, source, compare, stack) {
    if (source === target) {
        return true;
    }
    switch (typeof source) {
        case 'object': {
            return isObjectMatch(target, source, compare, stack);
        }
        case 'function': {
            const sourceKeys = Object.keys(source);
            if (sourceKeys.length > 0) {
                return isMatchWithInternal(target, { ...source }, compare, stack);
            }
            return eq.eq(target, source);
        }
        default: {
            if (!isObject.isObject(target)) {
                return eq.eq(target, source);
            }
            if (typeof source === 'string') {
                return source === '';
            }
            return true;
        }
    }
}
function isObjectMatch(target, source, compare, stack) {
    if (source == null) {
        return true;
    }
    if (Array.isArray(source)) {
        return isArrayMatch(target, source, compare, stack);
    }
    if (source instanceof Map) {
        return isMapMatch(target, source, compare, stack);
    }
    if (source instanceof Set) {
        return isSetMatch(target, source, compare, stack);
    }
    const keys = Object.keys(source);
    if (target == null) {
        return keys.length === 0;
    }
    if (keys.length === 0) {
        return true;
    }
    if (stack && stack.has(source)) {
        return stack.get(source) === target;
    }
    if (stack) {
        stack.set(source, target);
    }
    try {
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            if (!isPrimitive.isPrimitive(target) && !(key in target)) {
                return false;
            }
            if (source[key] === undefined && target[key] !== undefined) {
                return false;
            }
            if (source[key] === null && target[key] !== null) {
                return false;
            }
            const isEqual = compare(target[key], source[key], key, target, source, stack);
            if (!isEqual) {
                return false;
            }
        }
        return true;
    }
    finally {
        if (stack) {
            stack.delete(source);
        }
    }
}
function isMapMatch(target, source, compare, stack) {
    if (source.size === 0) {
        return true;
    }
    if (!(target instanceof Map)) {
        return false;
    }
    for (const [key, sourceValue] of source.entries()) {
        const targetValue = target.get(key);
        const isEqual = compare(targetValue, sourceValue, key, target, source, stack);
        if (isEqual === false) {
            return false;
        }
    }
    return true;
}
function isArrayMatch(target, source, compare, stack) {
    if (source.length === 0) {
        return true;
    }
    if (!Array.isArray(target)) {
        return false;
    }
    const countedIndex = new Set();
    for (let i = 0; i < source.length; i++) {
        const sourceItem = source[i];
        let found = false;
        for (let j = 0; j < target.length; j++) {
            if (countedIndex.has(j)) {
                continue;
            }
            const targetItem = target[j];
            let matches = false;
            const isEqual = compare(targetItem, sourceItem, i, target, source, stack);
            if (isEqual) {
                matches = true;
            }
            if (matches) {
                countedIndex.add(j);
                found = true;
                break;
            }
        }
        if (!found) {
            return false;
        }
    }
    return true;
}
function isSetMatch(target, source, compare, stack) {
    if (source.size === 0) {
        return true;
    }
    if (!(target instanceof Set)) {
        return false;
    }
    return isArrayMatch([...target], [...source], compare, stack);
}

exports.isMatchWith = isMatchWith;
exports.isSetMatch = isSetMatch;
