import { inspect } from 'node:util';
import { PonyfillIteratorObject } from './IteratorObject.js';
export function isHeadersLike(headers) {
    return headers?.get && headers?.forEach;
}
export class PonyfillHeaders {
    headersInit;
    _map;
    objectNormalizedKeysOfHeadersInit = [];
    objectOriginalKeysOfHeadersInit = [];
    _setCookies;
    constructor(headersInit) {
        this.headersInit = headersInit;
    }
    // perf: we don't need to build `this.map` for Requests, as we can access the headers directly
    _get(key) {
        const normalized = key.toLowerCase();
        if (normalized === 'set-cookie' && this._setCookies?.length) {
            return this._setCookies.join(', ');
        }
        // If the map is built, reuse it
        if (this._map) {
            return this._map.get(normalized) || null;
        }
        // If the map is not built, try to get the value from the this.headersInit
        if (this.headersInit == null) {
            return null;
        }
        if (Array.isArray(this.headersInit)) {
            const found = this.headersInit.filter(([headerKey]) => headerKey.toLowerCase() === normalized);
            if (found.length === 0) {
                return null;
            }
            if (found.length === 1) {
                return found[0][1];
            }
            return found.map(([, value]) => value).join(', ');
        }
        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) {
            this._setCookies ||= [];
            if (this.headersInit != null) {
                if (Array.isArray(this.headersInit)) {
                    this._map = new Map();
                    for (const [key, value] of this.headersInit) {
                        const normalizedKey = key.toLowerCase();
                        if (normalizedKey === 'set-cookie') {
                            if (Array.isArray(value)) {
                                this._setCookies.push(...value);
                            }
                            else if (value != null) {
                                this._setCookies.push(value);
                            }
                            continue;
                        }
                        this._map.set(normalizedKey, value);
                    }
                }
                else if (isHeadersLike(this.headersInit)) {
                    this._map = new Map();
                    this.headersInit.forEach((value, key) => {
                        if (key === 'set-cookie') {
                            this._setCookies ||= [];
                            if (Array.isArray(value)) {
                                this._setCookies.push(...value);
                            }
                            else if (value != null) {
                                this._setCookies.push(value);
                            }
                            return;
                        }
                        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();
                            if (normalizedKey === 'set-cookie') {
                                this._setCookies ||= [];
                                if (Array.isArray(initValue)) {
                                    this._setCookies.push(...initValue);
                                    continue;
                                }
                                this._setCookies.push(initValue);
                                continue;
                            }
                            this._map.set(normalizedKey, initValue);
                        }
                    }
                }
            }
            else {
                this._map = new Map();
            }
        }
        return this._map;
    }
    append(name, value) {
        const key = name.toLowerCase();
        if (key === 'set-cookie') {
            this._setCookies ||= [];
            this._setCookies.push(value);
            return;
        }
        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.toString();
    }
    has(name) {
        const key = name.toLowerCase();
        if (key === 'set-cookie') {
            return !!this._setCookies?.length;
        }
        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();
        if (key === 'set-cookie') {
            this._setCookies = [value];
            return;
        }
        if (!this._map && this.headersInit != null) {
            if (Array.isArray(this.headersInit)) {
                const found = this.headersInit.find(([headerKey]) => headerKey.toLowerCase() === key);
                if (found) {
                    found[1] = value;
                }
                else {
                    this.headersInit.push([key, value]);
                }
                return;
            }
            else if (isHeadersLike(this.headersInit)) {
                this.headersInit.set(key, value);
                return;
            }
            else {
                this.headersInit[key] = value;
                return;
            }
        }
        this.getMap().set(key, value);
    }
    delete(name) {
        const key = name.toLowerCase();
        if (key === 'set-cookie') {
            this._setCookies = [];
            return;
        }
        this.getMap().delete(key);
    }
    forEach(callback) {
        this._setCookies?.forEach(setCookie => {
            callback(setCookie, 'set-cookie', this);
        });
        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._setCookies?.length) {
            yield 'set-cookie';
        }
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    yield* this.headersInit.map(([key]) => key)[Symbol.iterator]();
                    return;
                }
                if (isHeadersLike(this.headersInit)) {
                    yield* this.headersInit.keys();
                    return;
                }
                yield* Object.keys(this.headersInit)[Symbol.iterator]();
                return;
            }
        }
        yield* this.getMap().keys();
    }
    keys() {
        return new PonyfillIteratorObject(this._keys(), 'HeadersIterator');
    }
    *_values() {
        if (this._setCookies?.length) {
            yield* this._setCookies;
        }
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    yield* this.headersInit.map(([, value]) => value)[Symbol.iterator]();
                    return;
                }
                if (isHeadersLike(this.headersInit)) {
                    yield* this.headersInit.values();
                    return;
                }
                yield* Object.values(this.headersInit)[Symbol.iterator]();
                return;
            }
        }
        yield* this.getMap().values();
    }
    values() {
        return new PonyfillIteratorObject(this._values(), 'HeadersIterator');
    }
    *_entries() {
        if (this._setCookies?.length) {
            yield* this._setCookies.map(cookie => ['set-cookie', cookie]);
        }
        if (!this._map) {
            if (this.headersInit) {
                if (Array.isArray(this.headersInit)) {
                    yield* this.headersInit;
                    return;
                }
                if (isHeadersLike(this.headersInit)) {
                    yield* this.headersInit.entries();
                    return;
                }
                yield* Object.entries(this.headersInit);
                return;
            }
        }
        yield* this.getMap().entries();
    }
    entries() {
        return new PonyfillIteratorObject(this._entries(), 'HeadersIterator');
    }
    getSetCookie() {
        if (!this._setCookies) {
            this.getMap();
        }
        return this._setCookies;
    }
    [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._setCookies || [];
            }
            else {
                record[key] = value?.includes(',') ? value.split(',').map(el => el.trim()) : value;
            }
        });
        return `Headers ${inspect(record)}`;
    }
}
