"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.splitSetCookieHeader = exports.PonyfillHeaders = exports.isHeadersLike = void 0;
const node_util_1 = require("node:util");
function isHeadersLike(headers) {
    return headers?.get && headers?.forEach;
}
exports.isHeadersLike = isHeadersLike;
class PonyfillHeaders {
    constructor(headersInit) {
        this.headersInit = headersInit;
        this.objectNormalizedKeysOfHeadersInit = [];
        this.objectOriginalKeysOfHeadersInit = [];
    }
    // perf: we don't need to build `this.map` for Requests, as we can access the headers directly
    _get(key) {
        // If the map is built, reuse it
        if (this._map) {
            return this._map.get(key.toLowerCase()) || null;
        }
        // If the map is not built, try to get the value from the this.headersInit
        if (this.headersInit == null) {
            return null;
        }
        const normalized = key.toLowerCase();
        if (Array.isArray(this.headersInit)) {
            return this.headersInit.find(header => header[0].toLowerCase() === normalized)?.[1] || null;
        }
        else if (isHeadersLike(this.headersInit)) {
            return this.headersInit.get(normalized);
        }
        else {
            const initValue = this.headersInit[key] || this.headersInit[normalized];
            if (initValue != null) {
                return initValue;
            }
            if (!this.objectNormalizedKeysOfHeadersInit.length) {
                Object.keys(this.headersInit).forEach(k => {
                    this.objectOriginalKeysOfHeadersInit.push(k);
                    this.objectNormalizedKeysOfHeadersInit.push(k.toLowerCase());
                });
            }
            const index = this.objectNormalizedKeysOfHeadersInit.indexOf(normalized);
            if (index === -1) {
                return null;
            }
            const originalKey = this.objectOriginalKeysOfHeadersInit[index];
            return this.headersInit[originalKey];
        }
    }
    // perf: Build the map of headers lazily, only when we need to access all headers or write to it.
    // I could do a getter here, but I'm too lazy to type `getter`.
    getMap() {
        if (!this._map) {
            if (this.headersInit != null) {
                if (Array.isArray(this.headersInit)) {
                    this._map = new Map(this.headersInit);
                }
                else if (isHeadersLike(this.headersInit)) {
                    this._map = new Map();
                    this.headersInit.forEach((value, key) => {
                        this._map.set(key, value);
                    });
                }
                else {
                    this._map = new Map();
                    for (const initKey in this.headersInit) {
                        const initValue = this.headersInit[initKey];
                        if (initValue != null) {
                            const normalizedKey = initKey.toLowerCase();
                            this._map.set(normalizedKey, initValue);
                        }
                    }
                }
            }
            else {
                this._map = new Map();
            }
        }
        return this._map;
    }
    append(name, value) {
        const key = name.toLowerCase();
        const existingValue = this.getMap().get(key);
        const finalValue = existingValue ? `${existingValue}, ${value}` : value;
        this.getMap().set(key, finalValue);
    }
    get(name) {
        const value = this._get(name);
        if (value == null) {
            return null;
        }
        return value;
    }
    has(name) {
        return !!this._get(name); // we might need to check if header exists and not just check if it's not nullable
    }
    set(name, value) {
        const key = name.toLowerCase();
        this.getMap().set(key, value);
    }
    delete(name) {
        const key = name.toLowerCase();
        this.getMap().delete(key);
    }
    forEach(callback) {
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    this.headersInit.forEach(([key, value]) => {
                        callback(value, key, this);
                    });
                    return;
                }
                if (isHeadersLike(this.headersInit)) {
                    this.headersInit.forEach(callback);
                    return;
                }
                Object.entries(this.headersInit).forEach(([key, value]) => {
                    if (value != null) {
                        callback(value, key, this);
                    }
                });
            }
            return;
        }
        this.getMap().forEach((value, key) => {
            callback(value, key, this);
        });
    }
    keys() {
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    return this.headersInit.map(([key]) => key)[Symbol.iterator]();
                }
                if (isHeadersLike(this.headersInit)) {
                    return this.headersInit.keys();
                }
                return Object.keys(this.headersInit)[Symbol.iterator]();
            }
        }
        return this.getMap().keys();
    }
    values() {
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    return this.headersInit.map(([, value]) => value)[Symbol.iterator]();
                }
                if (isHeadersLike(this.headersInit)) {
                    return this.headersInit.values();
                }
                return Object.values(this.headersInit)[Symbol.iterator]();
            }
        }
        return this.getMap().values();
    }
    entries() {
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    return this.headersInit[Symbol.iterator]();
                }
                if (isHeadersLike(this.headersInit)) {
                    return this.headersInit.entries();
                }
                return Object.entries(this.headersInit)[Symbol.iterator]();
            }
        }
        return this.getMap().entries();
    }
    getSetCookie() {
        const setCookieHeader = this.get('set-cookie');
        if (!setCookieHeader) {
            return [];
        }
        return splitSetCookieHeader(setCookieHeader);
    }
    [Symbol.iterator]() {
        return this.entries();
    }
    [Symbol.for('nodejs.util.inspect.custom')]() {
        const record = {};
        this.forEach((value, key) => {
            if (key === 'set-cookie') {
                record['set-cookie'] = this.getSetCookie();
            }
            else {
                record[key] = value.includes(',') ? value.split(',').map(el => el.trim()) : value;
            }
        });
        return `Headers ${(0, node_util_1.inspect)(record)}`;
    }
}
exports.PonyfillHeaders = PonyfillHeaders;
function splitSetCookieHeader(setCookieHeader) {
    const setCookieHeaders = [];
    let currentStr = '';
    let ignoreComma = false;
    for (const ch of setCookieHeader) {
        if (currentStr.endsWith('Expires=')) {
            ignoreComma = true;
        }
        if (ignoreComma) {
            if (ch === ';') {
                ignoreComma = false;
            }
            if (ch === ',' && currentStr.split('Expires=')[1].length > 3) {
                ignoreComma = false;
            }
        }
        if (ch === ',' && !ignoreComma) {
            setCookieHeaders.push(currentStr.trim());
            currentStr = '';
        }
        else {
            currentStr += ch;
        }
    }
    if (currentStr) {
        setCookieHeaders.push(currentStr.trim());
    }
    return setCookieHeaders;
}
exports.splitSetCookieHeader = splitSetCookieHeader;
