import { primordials } from "ext:core/mod.js";
const {
  ArrayIsArray,
  ArrayPrototypePush,
  ArrayPrototypeSort,
  ArrayPrototypeJoin,
  ArrayPrototypeSplice,
  ObjectFromEntries,
  ObjectHasOwn,
  ObjectPrototypeIsPrototypeOf,
  RegExpPrototypeTest,
  Symbol,
  SymbolFor,
  SymbolIterator,
  StringPrototypeReplaceAll,
  StringPrototypeCharCodeAt,
  TypeError,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import {
  byteLowerCase,
  collectHttpQuotedString,
  collectSequenceOfCodepoints,
  HTTP_TAB_OR_SPACE_PREFIX_RE,
  HTTP_TAB_OR_SPACE_SUFFIX_RE,
  HTTP_TOKEN_CODE_POINT_RE,
  httpTrim,
} from "ext:deno_web/00_infra.js";
const _headerList = Symbol("header list");
const _iterableHeaders = Symbol("iterable headers");
const _iterableHeadersCache = Symbol("iterable headers cache");
const _guard = Symbol("guard");
const _brand = webidl.brand;
function normalizeHeaderValue(potentialValue) {
  return httpTrim(potentialValue);
}
function fillHeaders(headers, object) {
  if (ArrayIsArray(object)) {
    for (let i = 0; i < object.length; ++i) {
      const header = object[i];
      if (header.length !== 2) {
        throw new TypeError(
          `Invalid header: length must be 2, but is ${header.length}`,
        );
      }
      appendHeader(headers, header[0], header[1]);
    }
  } else {
    for (const key in object) {
      if (!ObjectHasOwn(object, key)) {
        continue;
      }
      appendHeader(headers, key, object[key]);
    }
  }
}
function checkForInvalidValueChars(value) {
  for (let i = 0; i < value.length; i++) {
    const c = StringPrototypeCharCodeAt(value, i);
    if (c === 0x0a || c === 0x0d || c === 0x00) {
      return false;
    }
  }
  return true;
}
let HEADER_NAME_CACHE = { __proto__: null };
let HEADER_CACHE_SIZE = 0;
const HEADER_NAME_CACHE_SIZE_BOUNDARY = 4096;
function checkHeaderNameForHttpTokenCodePoint(name) {
  const fromCache = HEADER_NAME_CACHE[name];
  if (fromCache !== undefined) {
    return fromCache;
  }
  const valid = RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name);
  if (HEADER_CACHE_SIZE > HEADER_NAME_CACHE_SIZE_BOUNDARY) {
    HEADER_NAME_CACHE = { __proto__: null };
    HEADER_CACHE_SIZE = 0;
  }
  HEADER_CACHE_SIZE++;
  HEADER_NAME_CACHE[name] = valid;
  return valid;
}
function appendHeader(headers, name, value) {
    value = normalizeHeaderValue(value);
    if (!checkHeaderNameForHttpTokenCodePoint(name)) {
    throw new TypeError(`Invalid header name: "${name}"`);
  }
  if (!checkForInvalidValueChars(value)) {
    throw new TypeError(`Invalid header value: "${value}"`);
  }
    if (headers[_guard] == "immutable") {
    throw new TypeError("Cannot change header: headers are immutable");
  }
    const list = headers[_headerList];
  const lowercaseName = byteLowerCase(name);
  for (let i = 0; i < list.length; i++) {
    if (byteLowerCase(list[i][0]) === lowercaseName) {
      name = list[i][0];
      break;
    }
  }
  ArrayPrototypePush(list, [name, value]);
}
function getHeader(list, name) {
  const lowercaseName = byteLowerCase(name);
  const entries = [];
  for (let i = 0; i < list.length; i++) {
    if (byteLowerCase(list[i][0]) === lowercaseName) {
      ArrayPrototypePush(entries, list[i][1]);
    }
  }
  if (entries.length === 0) {
    return null;
  } else {
    return ArrayPrototypeJoin(entries, "\x2C\x20");
  }
}
function getDecodeSplitHeader(list, name) {
  const initialValue = getHeader(list, name);
  if (initialValue === null) return null;
  const input = initialValue;
  let position = 0;
  const values = [];
  let value = "";
  while (position < initialValue.length) {
        const res = collectSequenceOfCodepoints(
      initialValue,
      position,
      (c) => c !== "\u0022" && c !== "\u002C",
    );
    value += res.result;
    position = res.position;
    if (position < initialValue.length) {
      if (input[position] === "\u0022") {
        const res = collectHttpQuotedString(input, position, false);
        value += res.result;
        position = res.position;
        if (position < initialValue.length) {
          continue;
        }
      } else {
        if (input[position] !== "\u002C") throw new TypeError("Unreachable");
        position += 1;
      }
    }
    value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, "");
    value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, "");
    ArrayPrototypePush(values, value);
    value = "";
  }
  return values;
}
class Headers {
  
  [_headerList] = [];
  
  [_guard];
  get [_iterableHeaders]() {
    const list = this[_headerList];
    if (
      this[_guard] === "immutable" &&
      this[_iterableHeadersCache] !== undefined
    ) {
      return this[_iterableHeadersCache];
    }
            const seenHeaders = { __proto__: null };
    const entries = [];
    for (let i = 0; i < list.length; ++i) {
      const entry = list[i];
      const name = byteLowerCase(entry[0]);
      const value = entry[1];
      if (value === null) throw new TypeError("Unreachable");
                              if (name === "set-cookie") {
        ArrayPrototypePush(entries, [name, value]);
      } else {
                                        const seenHeaderIndex = seenHeaders[name];
        if (seenHeaderIndex !== undefined) {
          const entryValue = entries[seenHeaderIndex][1];
          entries[seenHeaderIndex][1] = entryValue.length > 0
            ? entryValue + "\x2C\x20" + value
            : value;
        } else {
          seenHeaders[name] = entries.length;           ArrayPrototypePush(entries, [name, value]);
        }
      }
    }
    ArrayPrototypeSort(
      entries,
      (a, b) => {
        const akey = a[0];
        const bkey = b[0];
        if (akey > bkey) return 1;
        if (akey < bkey) return -1;
        return 0;
      },
    );
    this[_iterableHeadersCache] = entries;
    return entries;
  }
  
  constructor(init = undefined) {
    if (init === _brand) {
      this[_brand] = _brand;
      return;
    }
    const prefix = "Failed to construct 'Headers'";
    if (init !== undefined) {
      init = webidl.converters["HeadersInit"](init, prefix, "Argument 1");
    }
    this[_brand] = _brand;
    this[_guard] = "none";
    if (init !== undefined) {
      fillHeaders(this, init);
    }
  }
  
  append(name, value) {
    webidl.assertBranded(this, HeadersPrototype);
    const prefix = "Failed to execute 'append' on 'Headers'";
    webidl.requiredArguments(arguments.length, 2, prefix);
    name = webidl.converters["ByteString"](name, prefix, "Argument 1");
    value = webidl.converters["ByteString"](value, prefix, "Argument 2");
    appendHeader(this, name, value);
  }
  
  delete(name) {
    webidl.assertBranded(this, HeadersPrototype);
    const prefix = "Failed to execute 'delete' on 'Headers'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    name = webidl.converters["ByteString"](name, prefix, "Argument 1");
    if (!checkHeaderNameForHttpTokenCodePoint(name)) {
      throw new TypeError(`Invalid header name: "${name}"`);
    }
    if (this[_guard] == "immutable") {
      throw new TypeError("Cannot change headers: headers are immutable");
    }
    const list = this[_headerList];
    const lowercaseName = byteLowerCase(name);
    for (let i = 0; i < list.length; i++) {
      if (byteLowerCase(list[i][0]) === lowercaseName) {
        ArrayPrototypeSplice(list, i, 1);
        i--;
      }
    }
  }
  
  get(name) {
    webidl.assertBranded(this, HeadersPrototype);
    const prefix = "Failed to execute 'get' on 'Headers'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    name = webidl.converters["ByteString"](name, prefix, "Argument 1");
    if (!checkHeaderNameForHttpTokenCodePoint(name)) {
      throw new TypeError(`Invalid header name: "${name}"`);
    }
    const list = this[_headerList];
    return getHeader(list, name);
  }
  getSetCookie() {
    webidl.assertBranded(this, HeadersPrototype);
    const list = this[_headerList];
    const entries = [];
    for (let i = 0; i < list.length; i++) {
      if (byteLowerCase(list[i][0]) === "set-cookie") {
        ArrayPrototypePush(entries, list[i][1]);
      }
    }
    return entries;
  }
  
  has(name) {
    webidl.assertBranded(this, HeadersPrototype);
    const prefix = "Failed to execute 'has' on 'Headers'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    name = webidl.converters["ByteString"](name, prefix, "Argument 1");
    if (!checkHeaderNameForHttpTokenCodePoint(name)) {
      throw new TypeError(`Invalid header name: "${name}"`);
    }
    const list = this[_headerList];
    const lowercaseName = byteLowerCase(name);
    for (let i = 0; i < list.length; i++) {
      if (byteLowerCase(list[i][0]) === lowercaseName) {
        return true;
      }
    }
    return false;
  }
  
  set(name, value) {
    webidl.assertBranded(this, HeadersPrototype);
    const prefix = "Failed to execute 'set' on 'Headers'";
    webidl.requiredArguments(arguments.length, 2, prefix);
    name = webidl.converters["ByteString"](name, prefix, "Argument 1");
    value = webidl.converters["ByteString"](value, prefix, "Argument 2");
    value = normalizeHeaderValue(value);
        if (!checkHeaderNameForHttpTokenCodePoint(name)) {
      throw new TypeError(`Invalid header name: "${name}"`);
    }
    if (!checkForInvalidValueChars(value)) {
      throw new TypeError(`Invalid header value: "${value}"`);
    }
    if (this[_guard] == "immutable") {
      throw new TypeError("Cannot change headers: headers are immutable");
    }
    const list = this[_headerList];
    const lowercaseName = byteLowerCase(name);
    let added = false;
    for (let i = 0; i < list.length; i++) {
      if (byteLowerCase(list[i][0]) === lowercaseName) {
        if (!added) {
          list[i][1] = value;
          added = true;
        } else {
          ArrayPrototypeSplice(list, i, 1);
          i--;
        }
      }
    }
    if (!added) {
      ArrayPrototypePush(list, [name, value]);
    }
  }
  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    if (ObjectPrototypeIsPrototypeOf(HeadersPrototype, this)) {
      return `${this.constructor.name} ${
        inspect(ObjectFromEntries(this), inspectOptions)
      }`;
    } else {
      return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
    }
  }
}
webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1);
webidl.configureInterface(Headers);
const HeadersPrototype = Headers.prototype;
webidl.converters["HeadersInit"] = (V, prefix, context, opts) => {
    if (webidl.type(V) === "Object" && V !== null) {
    if (V[SymbolIterator] !== undefined) {
      return webidl.converters["sequence<sequence<ByteString>>"](
        V,
        prefix,
        context,
        opts,
      );
    }
    return webidl.converters["record<ByteString, ByteString>"](
      V,
      prefix,
      context,
      opts,
    );
  }
  throw webidl.makeException(
    TypeError,
    "The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'",
    prefix,
    context,
  );
};
webidl.converters["Headers"] = webidl.createInterfaceConverter(
  "Headers",
  Headers.prototype,
);
function headersFromHeaderList(list, guard) {
  const headers = new Headers(_brand);
  headers[_headerList] = list;
  headers[_guard] = guard;
  return headers;
}
function headerListFromHeaders(headers) {
  return headers[_headerList];
}
function guardFromHeaders(headers) {
  return headers[_guard];
}
function headersEntries(headers) {
  return headers[_iterableHeaders];
}
export {
  fillHeaders,
  getDecodeSplitHeader,
  getHeader,
  guardFromHeaders,
  headerListFromHeaders,
  Headers,
  headersEntries,
  headersFromHeaderList,
};