"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfusableMatcher = void 0;
const binding_1 = require("./binding");
const DEFAULT_OPTIONS = {
    matchOnWordBoundary: false,
    matchRepeating: true,
    needlePosPointers: null,
    startFromEnd: false,
    startIndex: 0,
    timeoutNs: 5e6,
};
class ConfusableMatcher {
    constructor(maps = [], skips = [], addDefaultValues = true) {
        this._maps = maps;
        this._skips = new Set(skips);
        if (addDefaultValues) {
            const spaces = [
                ' ',
                '\u00A0',
                '᠎',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                ' ',
                '​',
                ' ',
                ' ',
                '　',
            ];
            this._addMappings(spaces.map((s) => [' ', s]), false);
            for (let c = 'A'.charCodeAt(0); c <= 'Z'.charCodeAt(0); c++) {
                const lc = String.fromCharCode(c);
                const uc = String.fromCharCode(c + 0x20);
                this._addMappings([
                    [lc, lc],
                    [lc, uc],
                    [uc, uc],
                    [uc, lc],
                ], false);
            }
            for (let c = '0'.charCodeAt(0); c <= '9'.charCodeAt(0); c++) {
                const char = String.fromCharCode(c);
                this._addMappings([[char, char]], false);
            }
        }
        this._rebuildInstance();
    }
    addMapping(key, value) {
        this._addMappings([[key, value]], true);
    }
    addMappings(mappings) {
        this._addMappings(mappings, true);
    }
    removeMapping(key, value) {
        this._removeMappings([[key, value]], true);
    }
    removeMappings(mappings) {
        this._removeMappings(mappings, true);
    }
    getMappings() {
        return this._maps;
    }
    getKeyMappings(value) {
        this._validateValue(value, 'Value');
        return this._instance.getKeyMappings(value);
    }
    addSkip(skip) {
        this._addSkips([skip], true);
    }
    addSkips(skips) {
        this._addSkips(skips, true);
    }
    removeSkip(skip) {
        this._removeSkips([skip], true);
    }
    removeSkips(skips) {
        this._removeSkips(skips, true);
    }
    getSkips() {
        return [...this._skips];
    }
    computeStringPosPointers(needle) {
        return this._instance.computeStringPosPointers(needle);
    }
    freeStringPosPointers(pointer) {
        this._instance.freeStringPosPointers(pointer);
    }
    indexOfSync(input, needle, options) {
        let utf8;
        if (typeof (options === null || options === void 0 ? void 0 : options.startIndex) === 'number' && options.startIndex !== 0) {
            utf8 = Buffer.from(input);
            options.startIndex = utf8.toString('utf-8', 0, options.startIndex).length;
        }
        const result = this._instance.indexOf(input, needle, this._fillDefaultOptions(options));
        if (result.start >= 0) {
            if (!utf8) {
                utf8 = Buffer.from(input);
            }
            const start = utf8.toString('utf-8', 0, result.start);
            const size = utf8.toString('utf-8', result.start, result.start + result.size);
            return {
                size: size.length,
                start: start.length,
                status: result.status,
            };
        }
        return result;
    }
    indexOf(input, needle, options) {
        let utf8;
        if (typeof (options === null || options === void 0 ? void 0 : options.startIndex) === 'number' && options.startIndex !== 0) {
            utf8 = Buffer.from(input);
            options.startIndex = utf8.toString('utf-8', 0, options.startIndex).length;
        }
        return new Promise((resolve) => {
            this._instance.indexOfAsync((result) => {
                if (result.start >= 0) {
                    if (!utf8) {
                        utf8 = Buffer.from(input);
                    }
                    const start = utf8.toString('utf-8', 0, result.start);
                    const size = utf8.toString('utf-8', result.start, result.start + result.size);
                    resolve({
                        size: size.length,
                        start: start.length,
                        status: result.status,
                    });
                }
            }, input, needle, this._fillDefaultOptions(options));
        });
    }
    lastIndexOfSync(input, needle, options) {
        return this.indexOfSync(input, needle, Object.assign(Object.assign({}, options), { startFromEnd: true, startIndex: input.length - 1 }));
    }
    lastIndexOf(input, needle, options) {
        return this.indexOf(input, needle, Object.assign(Object.assign({}, options), { startFromEnd: true, startIndex: input.length - 1 }));
    }
    containsSync(input, needle, options) {
        const { status } = this._instance.indexOf(input, needle, this._fillDefaultOptions(options));
        return status === binding_1.EReturnStatus.MATCH;
    }
    async contains(input, needle, options) {
        return new Promise((resolve) => {
            this._instance.indexOfAsync(({ status }) => resolve(status === binding_1.EReturnStatus.MATCH), input, needle, this._fillDefaultOptions(options));
        });
    }
    _validateValue(value, name) {
        if (value.length === 0) {
            throw new Error(`${name} cannot be empty.`);
        }
        if (value[0] == '\x00' || value[0] == '\x01') {
            throw new Error(String.raw `${name} cannot start with \x00 or \x01.`);
        }
    }
    _addMappings(mappings, rebuild) {
        for (const [key, value] of mappings) {
            this._validateValue(key, 'Key');
            this._validateValue(value, 'Value');
            this._maps.push([key, value]);
        }
        if (rebuild) {
            this._rebuildInstance();
        }
    }
    _removeMappings(mappings, rebuild) {
        let didRemoveAny = false;
        for (const [key, value] of mappings) {
            this._validateValue(key, 'Key');
            this._validateValue(value, 'Value');
            for (let i = 0; i < this._maps.length; i++) {
                const mapping = this._maps[i];
                if (mapping[0] !== key) {
                    continue;
                }
                if (mapping[1] !== value) {
                    continue;
                }
                this._maps.splice(i, 1);
                didRemoveAny = true;
                break;
            }
        }
        if (didRemoveAny && rebuild) {
            this._rebuildInstance();
        }
    }
    _addSkips(skips, rebuild) {
        for (const skip of skips) {
            this._validateValue(skip, 'Skip');
            this._skips.add(skip);
        }
        if (rebuild) {
            this._rebuildInstance();
        }
    }
    _removeSkips(skips, rebuild) {
        let didRemoveAny = false;
        for (const skip of skips) {
            this._validateValue(skip, 'Skip');
            if (this._skips.delete(skip)) {
                didRemoveAny = true;
            }
        }
        if (didRemoveAny && rebuild) {
            this._rebuildInstance();
        }
    }
    _rebuildInstance() {
        this._instance = new binding_1.ConfusableMatcherInterop([...this._maps], [...this._skips], false);
    }
    _fillDefaultOptions(options) {
        return Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
    }
}
exports.ConfusableMatcher = ConfusableMatcher;
//# sourceMappingURL=wrapper.js.map