import { trim } from "./String.js";
import { Exception } from "./Util.js";
// RFC 4122 compliant. From https://stackoverflow.com/a/13653180/3922220
// const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
// Relax GUID parsing, see #1637
const guidRegex = /^[\(\{]{0,2}[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[\)\}]{0,2}$/;
const guidRegexNoHyphen = /^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/;
const guidRegexHex = /^\{0x[0-9a-f]{8},(0x[0-9a-f]{4},){2}\{(0x[0-9a-f]{2},){7}0x[0-9a-f]{2}\}\}$/;
const guidHexCaptures = /^([0-9a-f]{8})-(([0-9a-f]{4})-)(([0-9a-f]{4})-)([0-9a-f]{2})([0-9a-f]{2})-([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/;
export function toString(str, format, _provider) {
    if (format && format?.length > 0) {
        switch (format) {
            case "N":
                return str.replace(/-/g, '');
            case "D":
                return str;
            case "B":
                return "{" + str + "}";
            case "P":
                return "(" + str + ")";
            case "X":
                return str.replace(guidHexCaptures, "{0x$1,0x$3,0x$5,{0x$6,0x$7,0x$8,0x$9,0x$10,0x$11,0x$12,0x$13}}");
            default:
                throw new Exception("Unrecognized Guid print format");
        }
    }
    else {
        return str;
    }
}
/** Validates UUID as specified in RFC4122 (versions 1-5). */
export function parse(str) {
    function hyphenateGuid(str) {
        return str.replace(guidRegexNoHyphen, "$1-$2-$3-$4-$5");
    }
    const wsTrimAndLowered = str.trim().toLowerCase();
    if (guidRegex.test(wsTrimAndLowered)) {
        return trim(wsTrimAndLowered, "{", "}", "(", ")");
    }
    else if (guidRegexNoHyphen.test(wsTrimAndLowered)) {
        return hyphenateGuid(wsTrimAndLowered);
    }
    else if (guidRegexHex.test(wsTrimAndLowered)) {
        return hyphenateGuid(wsTrimAndLowered.replace(/[\{\},]|0x/g, ''));
    }
    else {
        throw new Exception("Guid should contain 32 digits with 4 dashes: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
    }
}
export function tryParse(str, defValue) {
    try {
        defValue.contents = parse(str);
        return true;
    }
    catch {
        return false;
    }
}
export function newGuid() {
    const bytes = new Uint8Array(16);
    crypto.getRandomValues(bytes);
    bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
    bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1
    const hex = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
    return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
// RFC 9562 UUID v7
export function createVersion7(timestamp) {
    const ms = timestamp != null ? timestamp.getTime() : Date.now();
    // 48-bit timestamp as hex
    const msHex = Math.floor(ms).toString(16).padStart(12, "0");
    // random bits
    const bytes = new Uint8Array(10);
    crypto.getRandomValues(bytes);
    const view = new DataView(bytes.buffer);
    const randA = view.getUint16(0) & 0x0fff; // 12 bits
    const randB1 = view.getUint16(2) & 0x3fff; // 14 bits
    const randB2 = view.getUint32(4); // 32 bits
    const randB3 = view.getUint16(8); // 16 bits
    const timeLow = msHex.slice(0, 8);
    const timeMid = msHex.slice(8, 12);
    const ver = (0x7000 | randA).toString(16).padStart(4, "0");
    const variantAndRandB = (0x8000 | randB1).toString(16).padStart(4, "0");
    const node = randB2.toString(16).padStart(8, "0") + randB3.toString(16).padStart(4, "0");
    return `${timeLow}-${timeMid}-${ver}-${variantAndRandB}-${node}`;
}
// Maps for number <-> hex string conversion
let _convertMapsInitialized = false;
let _byteToHex;
let _hexToByte;
function initConvertMaps() {
    _byteToHex = new Array(256);
    _hexToByte = {};
    for (let i = 0; i < 256; i++) {
        _byteToHex[i] = i.toString(16).padStart(2, '0');
        _hexToByte[_byteToHex[i]] = i;
    }
    _convertMapsInitialized = true;
}
/** Parse a UUID into it's component bytes */
// Adapted from https://github.com/zefferus/uuid-parse
export function guidToArray(s) {
    if (!_convertMapsInitialized) {
        initConvertMaps();
    }
    let i = 0;
    const buf = new Uint8Array(16);
    s.toLowerCase().replace(/[0-9a-f]{2}/g, ((oct) => {
        switch (i) {
            // .NET saves first three byte groups with different endianness
            // See https://stackoverflow.com/a/16722909/3922220
            case 0:
            case 1:
            case 2:
            case 3:
                buf[3 - i++] = _hexToByte[oct];
                break;
            case 4:
            case 5:
                buf[9 - i++] = _hexToByte[oct];
                break;
            case 6:
            case 7:
                buf[13 - i++] = _hexToByte[oct];
                break;
            case 8:
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
                buf[i++] = _hexToByte[oct];
                break;
        }
    }));
    // Zero out remaining bytes if string was short
    while (i < 16) {
        buf[i++] = 0;
    }
    return buf;
}
/** Convert UUID byte array into a string */
export function arrayToGuid(buf) {
    if (buf.length !== 16) {
        throw new Exception("Byte array for GUID must be exactly 16 bytes long");
    }
    if (!_convertMapsInitialized) {
        initConvertMaps();
    }
    const guid = _byteToHex[buf[3]] + _byteToHex[buf[2]] +
        _byteToHex[buf[1]] + _byteToHex[buf[0]] + "-" +
        _byteToHex[buf[5]] + _byteToHex[buf[4]] + "-" +
        _byteToHex[buf[7]] + _byteToHex[buf[6]] + "-" +
        _byteToHex[buf[8]] + _byteToHex[buf[9]] + "-" +
        _byteToHex[buf[10]] + _byteToHex[buf[11]] +
        _byteToHex[buf[12]] + _byteToHex[buf[13]] +
        _byteToHex[buf[14]] + _byteToHex[buf[15]];
    return guid;
}
