function keyOf(name) {
    return name.trim().toLowerCase();
}
function toError(error) {
    return error instanceof Error ? error : new Error(String(error));
}
function removeConverter(converters, primaryNames, converter) {
    for (const alias of [converter.name, ...(converter.aliases ?? [])]) {
        converters.delete(keyOf(alias));
    }
    primaryNames.delete(keyOf(converter.name));
}
function requireCapability(converter, name, capability) {
    const method = converter[capability];
    if (typeof method !== "function") {
        throw new Error(`Converter '${name}' does not support ${capability}()`);
    }
    return method;
}
function detectConfidence(name, text, converter) {
    const normalizedName = keyOf(converter.name || name);
    const trimmed = text.trim();
    if (!trimmed) {
        return 0;
    }
    let accepted = false;
    if (converter.is) {
        accepted = converter.is(text);
    }
    let decodable = false;
    try {
        converter.decode(text);
        decodable = true;
    }
    catch {
        decodable = false;
    }
    if (!accepted && !decodable) {
        return 0;
    }
    switch (normalizedName) {
        case "pem":
            return /-----BEGIN [^-]+-----/.test(text) ? 1 : 0;
        case "hex": {
            const compact = trimmed.replace(/^0x/i, "").replace(/[\s:.-]/g, "");
            if (!compact || /[^0-9a-f]/i.test(compact) || compact.length % 2 !== 0) {
                return 0;
            }
            if (/^0x/i.test(trimmed) || /[:\s.-]/.test(trimmed)) {
                return 0.95;
            }
            if (/[a-f]/.test(trimmed) || /[A-F]/.test(trimmed)) {
                return 0.8;
            }
            return 0.45;
        }
        case "base64url":
            if (/[-_]/.test(trimmed)) {
                return 0.95;
            }
            if (/=/.test(trimmed)) {
                return 0.1;
            }
            return 0.6;
        case "base64":
            if (/[+/=]/.test(trimmed)) {
                return 0.9;
            }
            return 0.55;
        case "binary":
        case "utf8":
        case "utf16be":
        case "utf16le":
            return 0;
        default:
            return accepted && decodable ? 0.75 : 0.5;
    }
}
export function createConverterRegistry(initialConverters = []) {
    const converters = new Map();
    const primaryNames = new Set();
    const api = {
        register(converter, options = {}) {
            if (!converter.name || !keyOf(converter.name)) {
                throw new TypeError("Converter name is required");
            }
            const names = [...new Set([converter.name, ...(converter.aliases ?? [])].map(keyOf))];
            const conflicts = new Set();
            for (const name of names) {
                const existing = converters.get(name);
                if (!existing) {
                    continue;
                }
                if (!options.override) {
                    throw new Error(`Converter '${name}' is already registered`);
                }
                conflicts.add(existing);
            }
            for (const conflicting of conflicts) {
                removeConverter(converters, primaryNames, conflicting);
            }
            for (const name of names) {
                converters.set(name, converter);
            }
            primaryNames.add(keyOf(converter.name));
            return this;
        },
        unregister(name) {
            const converter = converters.get(keyOf(name));
            if (!converter) {
                return false;
            }
            removeConverter(converters, primaryNames, converter);
            return true;
        },
        has(name) {
            return converters.has(keyOf(name));
        },
        get(name) {
            const converter = converters.get(keyOf(name));
            if (!converter) {
                throw new Error(`Converter '${name}' is not registered`);
            }
            return converter;
        },
        list() {
            return [...primaryNames].map((name) => this.get(name));
        },
        encode(name, data, options) {
            return this.get(name).encode(data, options);
        },
        decode(name, text, options) {
            return this.get(name).decode(text, options);
        },
        tryDecode(name, text, options) {
            try {
                return { ok: true, bytes: this.decode(name, text, options) };
            }
            catch (error) {
                return { ok: false, error: toError(error) };
            }
        },
        normalize(name, text, options) {
            const converter = this.get(name);
            return requireCapability(converter, name, "normalize").call(converter, text, options);
        },
        parse(name, text, options) {
            const converter = this.get(name);
            return requireCapability(converter, name, "parse").call(converter, text, options);
        },
        format(name, data, format) {
            const converter = this.get(name);
            return requireCapability(converter, name, "format").call(converter, data, format);
        },
        transcode(text, options) {
            const bytes = this.decode(options.from, text, options.fromOptions);
            return this.encode(options.to, bytes, options.toOptions);
        },
        detect(text, options = {}) {
            const formatNames = options.formats?.length
                ? options.formats.map((name) => String(name))
                : this.list()
                    .map((converter) => converter.name)
                    .filter((name) => !["binary", "utf8", "utf16be", "utf16le"].includes(keyOf(name)));
            const detections = new Map();
            for (const requestedName of formatNames) {
                const converter = this.get(requestedName);
                const confidence = detectConfidence(requestedName, text, converter);
                if (confidence <= 0) {
                    continue;
                }
                const format = converter.name;
                const current = detections.get(format);
                if (!current || confidence > current.confidence) {
                    detections.set(format, { format, confidence });
                }
            }
            return [...detections.values()].sort((left, right) => right.confidence - left.confidence);
        },
    };
    for (const converter of initialConverters) {
        api.register(converter);
    }
    return api;
}
