/* eslint-disable @typescript-eslint/no-explicit-any */
/**
 * randexp v0.4.6
 * Create random strings that match a given regular expression.
 *
 * Copyright (C) 2017 by fent (https://github.com/fent)
 * MIT License
 */
import { ret, types } from './lib/ret.js';
class DRange {
    low;
    high;
    length;
    constructor(low, high) {
        this.low = low;
        this.high = high;
        this.length = 1 + high - low;
    }
    overlaps(range) {
        return !(this.high < range.low || this.low > range.high);
    }
    touches(range) {
        return !(this.high + 1 < range.low || this.low - 1 > range.high);
    }
    add(range) {
        if (this.touches(range)) {
            return new DRange(Math.min(this.low, range.low), Math.max(this.high, range.high));
        }
        return null;
    }
    subtract(range) {
        if (!this.overlaps(range)) {
            return [this.clone()];
        }
        if (range.low <= this.low && range.high >= this.high) {
            return [];
        }
        if (range.low > this.low && range.high < this.high) {
            return [new DRange(this.low, range.low - 1), new DRange(range.high + 1, this.high)];
        }
        if (range.low <= this.low) {
            return [new DRange(range.high + 1, this.high)];
        }
        // if (range.high >= this.high)
        return [new DRange(this.low, range.low - 1)];
    }
    toString() {
        return this.low === this.high ? String(this.low) : `${this.low}-${this.high}`;
    }
    clone() {
        return new DRange(this.low, this.high);
    }
}
class DiscontinuousRange {
    ranges = [];
    length = 0;
    constructor(low, high) {
        if (low !== undefined) {
            this.add(low, high);
        }
    }
    _setLength() {
        this.length = this.ranges.reduce((sum, range) => sum + range.length, 0);
    }
    add(low, high) {
        const addRange = (range) => {
            const newRanges = [];
            let i = 0;
            while (i < this.ranges.length && !range.touches(this.ranges[i])) {
                newRanges.push(this.ranges[i].clone());
                i++;
            }
            while (i < this.ranges.length && range.touches(this.ranges[i])) {
                range = range.add(this.ranges[i]);
                i++;
            }
            newRanges.push(range);
            while (i < this.ranges.length) {
                newRanges.push(this.ranges[i].clone());
                i++;
            }
            this.ranges = newRanges;
            this._setLength();
        };
        if (low instanceof DiscontinuousRange) {
            low.ranges.forEach(addRange);
        }
        else if (low instanceof DRange) {
            addRange(low);
        }
        else {
            if (high === undefined)
                high = low;
            addRange(new DRange(low, high));
        }
        return this;
    }
    subtract(low, high) {
        const subtractRange = (range) => {
            const newRanges = [];
            let i = 0;
            while (i < this.ranges.length && !range.overlaps(this.ranges[i])) {
                newRanges.push(this.ranges[i].clone());
                i++;
            }
            while (i < this.ranges.length && range.overlaps(this.ranges[i])) {
                newRanges.push(...this.ranges[i].subtract(range));
                i++;
            }
            while (i < this.ranges.length) {
                newRanges.push(this.ranges[i].clone());
                i++;
            }
            this.ranges = newRanges;
            this._setLength();
        };
        if (low instanceof DiscontinuousRange) {
            low.ranges.forEach(subtractRange);
        }
        else if (low instanceof DRange) {
            subtractRange(low);
        }
        else {
            if (high === undefined)
                high = low;
            subtractRange(new DRange(low, high));
        }
        return this;
    }
    index(i) {
        let j = 0;
        while (j < this.ranges.length && this.ranges[j].length <= i) {
            i -= this.ranges[j].length;
            j++;
        }
        if (j >= this.ranges.length) {
            return null;
        }
        return this.ranges[j].low + i;
    }
    toString() {
        return `[ ${this.ranges.join(', ')} ]`;
    }
    clone() {
        return new DiscontinuousRange(this);
    }
}
export class RandExp {
    max = 10; // Reduced from 100 to 10 for more reasonable string lengths
    defaultRange = new DiscontinuousRange(32, 126);
    randInt = (min, max) => {
        return min + Math.floor(Math.random() * (max - min + 1));
    };
    tokens;
    ignoreCase;
    multiline;
    constructor(regexp, flags, options) {
        this.defaultRange = this.defaultRange.clone();
        // Apply options if provided
        if (options) {
            if (typeof options.max === 'number') {
                this.max = options.max;
            }
            if (options.defaultRange instanceof DiscontinuousRange) {
                this.defaultRange = options.defaultRange;
            }
            if (typeof options.randInt === 'function') {
                this.randInt = options.randInt;
            }
        }
        if (regexp instanceof RegExp) {
            this.ignoreCase = regexp.ignoreCase;
            this.multiline = regexp.multiline;
            this._setDefaults(regexp);
            regexp = regexp.source;
        }
        else if (typeof regexp === 'string') {
            this.ignoreCase = flags?.includes('i') ?? false;
            this.multiline = flags?.includes('m') ?? false;
        }
        else {
            throw new Error('Expected a regexp or string');
        }
        this.tokens = ret(regexp);
    }
    _setDefaults(regexp) {
        if (typeof regexp.max === 'number') {
            this.max = regexp.max;
        }
        if (regexp.defaultRange instanceof DiscontinuousRange) {
            this.defaultRange = regexp.defaultRange;
        }
        if (typeof regexp.randInt === 'function') {
            this.randInt = regexp.randInt;
        }
    }
    gen() {
        return this._gen(this.tokens, []);
    }
    _gen(token, groups) {
        let stack, str, i, l;
        switch (token.type) {
            case types.ROOT:
            case types.GROUP:
                if (token.followedBy || token.notFollowedBy)
                    return '';
                if (token.remember && token.groupNumber === undefined) {
                    token.groupNumber = groups.push(null) - 1;
                }
                stack = token.options ? this._choice(token.options) : token.stack;
                str = '';
                for (i = 0, l = stack.length; i < l; i++) {
                    str += this._gen(stack[i], groups);
                }
                if (token.remember) {
                    groups[token.groupNumber] = str;
                }
                return str;
            case types.POSITION:
                return '';
            case types.SET: {
                const set = this._processSet(token);
                if (!set.length)
                    return '';
                return String.fromCharCode(this._choice(set));
            }
            case types.REPETITION: {
                const n = this.randInt(token.min, token.max === Infinity ? token.min + this.max : token.max);
                str = '';
                for (i = 0; i < n; i++) {
                    str += this._gen(token.value, groups);
                }
                return str;
            }
            case types.REFERENCE:
                return groups[token.value - 1] || '';
            case types.CHAR: {
                const code = this.ignoreCase && this._coin() ? RandExp._toCaseInverse(token.value) : token.value;
                return String.fromCharCode(code);
            }
        }
        return '';
    }
    _processSet(token) {
        if (token.type === types.CHAR) {
            return new DiscontinuousRange(token.value);
        }
        if (token.type === types.RANGE) {
            return new DiscontinuousRange(token.from, token.to);
        }
        const dr = new DiscontinuousRange();
        for (const char of token.set) {
            const sub = this._processSet(char);
            dr.add(sub);
            if (this.ignoreCase) {
                for (let j = 0; j < sub.length; j++) {
                    const charCode = sub.index(j);
                    const otherCase = RandExp._toCaseInverse(charCode);
                    if (charCode !== otherCase) {
                        dr.add(otherCase);
                    }
                }
            }
        }
        return token.not ? this.defaultRange.clone().subtract(dr) : dr;
    }
    static _toCaseInverse(code) {
        if (code >= 97 && code <= 122)
            return code - 32;
        if (code >= 65 && code <= 90)
            return code + 32;
        return code;
    }
    _coin() {
        return this.randInt(0, 1) === 0;
    }
    _choice(choices) {
        if (choices instanceof DiscontinuousRange) {
            return choices.index(this.randInt(0, choices.length - 1));
        }
        return choices[this.randInt(0, choices.length - 1)];
    }
    static randexp(regexp, flags, options) {
        let randexpInstance;
        if (regexp._randexp === undefined) {
            randexpInstance = new RandExp(regexp, flags, options);
            regexp._randexp = randexpInstance;
        }
        else {
            randexpInstance = regexp._randexp;
        }
        randexpInstance._setDefaults(regexp);
        return randexpInstance.gen();
    }
    static sugar() {
        ;
        RegExp.prototype.gen = function () {
            return RandExp.randexp(this);
        };
    }
}
//# sourceMappingURL=RandExp.js.map