import { core, primordials } from "ext:core/mod.js";
const {
  ArrayPrototypeMap,
  ArrayPrototypeSlice,
  ArrayPrototypeSplice,
  ObjectKeys,
  ObjectPrototypeIsPrototypeOf,
  RegExpPrototypeExec,
  StringPrototypeStartsWith,
  Symbol,
  SymbolFor,
  TypeError,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { assert } from "ext:deno_web/00_infra.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import {
  byteUpperCase,
  HTTP_TOKEN_CODE_POINT_RE,
} from "ext:deno_web/00_infra.js";
import { URL } from "ext:deno_url/00_url.js";
import { extractBody, mixinBody } from "ext:deno_fetch/22_body.js";
import { getLocationHref } from "ext:deno_web/12_location.js";
import { extractMimeType } from "ext:deno_web/01_mimesniff.js";
import { blobFromObjectUrl } from "ext:deno_web/09_file.js";
import {
  fillHeaders,
  getDecodeSplitHeader,
  guardFromHeaders,
  headerListFromHeaders,
  headersFromHeaderList,
} from "ext:deno_fetch/20_headers.js";
import { HttpClientPrototype } from "ext:deno_fetch/22_http_client.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
const { internalRidSymbol } = core;
const _request = Symbol("request");
const _headers = Symbol("headers");
const _getHeaders = Symbol("get headers");
const _headersCache = Symbol("headers cache");
const _signal = Symbol("signal");
const _mimeType = Symbol("mime type");
const _body = Symbol("body");
const _url = Symbol("url");
const _method = Symbol("method");
const _brand = webidl.brand;
function processUrlList(urlList, urlListProcessed) {
  for (let i = 0; i < urlList.length; i++) {
    if (urlListProcessed[i] === undefined) {
      urlListProcessed[i] = urlList[i]();
    }
  }
  return urlListProcessed;
}
function newInnerRequest(method, url, headerList, body, maybeBlob) {
  let blobUrlEntry = null;
  if (
    maybeBlob &&
    typeof url === "string" &&
    StringPrototypeStartsWith(url, "blob:")
  ) {
    blobUrlEntry = blobFromObjectUrl(url);
  }
  return {
    methodInner: method,
    get method() {
      return this.methodInner;
    },
    set method(value) {
      this.methodInner = value;
    },
    headerListInner: null,
    get headerList() {
      if (this.headerListInner === null) {
        try {
          this.headerListInner = headerList();
        } catch {
          throw new TypeError("cannot read headers: request closed");
        }
      }
      return this.headerListInner;
    },
    set headerList(value) {
      this.headerListInner = value;
    },
    body,
    redirectMode: "follow",
    redirectCount: 0,
    urlList: [typeof url === "string" ? () => url : url],
    urlListProcessed: [],
    clientRid: null,
    blobUrlEntry,
    url() {
      if (this.urlListProcessed[0] === undefined) {
        try {
          this.urlListProcessed[0] = this.urlList[0]();
        } catch {
          throw new TypeError("cannot read url: request closed");
        }
      }
      return this.urlListProcessed[0];
    },
    currentUrl() {
      const currentIndex = this.urlList.length - 1;
      if (this.urlListProcessed[currentIndex] === undefined) {
        try {
          this.urlListProcessed[currentIndex] = this.urlList[currentIndex]();
        } catch {
          throw new TypeError("cannot read url: request closed");
        }
      }
      return this.urlListProcessed[currentIndex];
    },
  };
}
function cloneInnerRequest(request, skipBody = false) {
  const headerList = ArrayPrototypeMap(
    request.headerList,
    (x) => [x[0], x[1]],
  );
  let body = null;
  if (request.body !== null && !skipBody) {
    body = request.body.clone();
  }
  return {
    method: request.method,
    headerList,
    body,
    redirectMode: request.redirectMode,
    redirectCount: request.redirectCount,
    urlList: [() => request.url()],
    urlListProcessed: [request.url()],
    clientRid: request.clientRid,
    blobUrlEntry: request.blobUrlEntry,
    url() {
      if (this.urlListProcessed[0] === undefined) {
        try {
          this.urlListProcessed[0] = this.urlList[0]();
        } catch {
          throw new TypeError("cannot read url: request closed");
        }
      }
      return this.urlListProcessed[0];
    },
    currentUrl() {
      const currentIndex = this.urlList.length - 1;
      if (this.urlListProcessed[currentIndex] === undefined) {
        try {
          this.urlListProcessed[currentIndex] = this.urlList[currentIndex]();
        } catch {
          throw new TypeError("cannot read url: request closed");
        }
      }
      return this.urlListProcessed[currentIndex];
    },
  };
}
const KNOWN_METHODS = {
  "DELETE": "DELETE",
  "delete": "DELETE",
  "GET": "GET",
  "get": "GET",
  "HEAD": "HEAD",
  "head": "HEAD",
  "OPTIONS": "OPTIONS",
  "options": "OPTIONS",
  "PATCH": "PATCH",
  "patch": "PATCH",
  "POST": "POST",
  "post": "POST",
  "PUT": "PUT",
  "put": "PUT",
};
function validateAndNormalizeMethod(m) {
  if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, m) === null) {
    throw new TypeError("Method is not valid.");
  }
  const upperCase = byteUpperCase(m);
  if (
    upperCase === "CONNECT" || upperCase === "TRACE" || upperCase === "TRACK"
  ) {
    throw new TypeError("Method is forbidden.");
  }
  return upperCase;
}
class Request {
  
  [_request];
  
  [_headersCache];
  [_getHeaders];
  
  get [_headers]() {
    if (this[_headersCache] === undefined) {
      this[_headersCache] = this[_getHeaders]();
    }
    return this[_headersCache];
  }
  set [_headers](value) {
    this[_headersCache] = value;
  }
  
  [_signal];
  get [_mimeType]() {
    const values = getDecodeSplitHeader(
      headerListFromHeaders(this[_headers]),
      "Content-Type",
    );
    return extractMimeType(values);
  }
  get [_body]() {
    return this[_request].body;
  }
  
  constructor(input, init = {}) {
    if (input === _brand) {
      this[_brand] = _brand;
      return;
    }
    const prefix = "Failed to construct 'Request'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    input = webidl.converters["RequestInfo_DOMString"](
      input,
      prefix,
      "Argument 1",
    );
    init = webidl.converters["RequestInit"](init, prefix, "Argument 2");
    this[_brand] = _brand;
    
    let request;
    const baseURL = getLocationHref();
        let signal = null;
        if (typeof input === "string") {
      const parsedURL = new URL(input, baseURL);
      request = newInnerRequest(
        "GET",
        parsedURL.href,
        () => [],
        null,
        true,
      );
    } else {       if (!ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) {
        throw new TypeError("Unreachable");
      }
      const originalReq = input[_request];
            request = cloneInnerRequest(originalReq, true);
      request.redirectCount = 0;       signal = input[_signal];
    }
    
        if (init.redirect !== undefined) {
      request.redirectMode = init.redirect;
    }
        if (init.method !== undefined) {
      const method = init.method;
            request.method = KNOWN_METHODS[method] ??
        validateAndNormalizeMethod(method);
    }
        if (init.signal !== undefined) {
      signal = init.signal;
    }
        if (init.client !== undefined) {
      if (
        init.client !== null &&
        !ObjectPrototypeIsPrototypeOf(HttpClientPrototype, init.client)
      ) {
        throw webidl.makeException(
          TypeError,
          "`client` must be a Deno.HttpClient",
          prefix,
          "Argument 2",
        );
      }
      request.clientRid = init.client?.[internalRidSymbol] ?? null;
    }
        this[_request] = request;
        const signals = signal !== null ? [signal] : [];
        this[_signal] = abortSignal.createDependentAbortSignal(signals, prefix);
        this[_headers] = headersFromHeaderList(request.headerList, "request");
        if (init.headers || ObjectKeys(init).length > 0) {
      const headerList = headerListFromHeaders(this[_headers]);
      const headers = init.headers ?? ArrayPrototypeSlice(
        headerList,
        0,
        headerList.length,
      );
      if (headerList.length !== 0) {
        ArrayPrototypeSplice(headerList, 0, headerList.length);
      }
      fillHeaders(this[_headers], headers);
    }
        let inputBody = null;
    if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) {
      inputBody = input[_body];
    }
        if (
      (request.method === "GET" || request.method === "HEAD") &&
      ((init.body !== undefined && init.body !== null) ||
        inputBody !== null)
    ) {
      throw new TypeError("Request with GET/HEAD method cannot have body.");
    }
        let initBody = null;
        if (init.body !== undefined && init.body !== null) {
      const res = extractBody(init.body);
      initBody = res.body;
      if (res.contentType !== null && !this[_headers].has("content-type")) {
        this[_headers].append("Content-Type", res.contentType);
      }
    }
        const inputOrInitBody = initBody ?? inputBody;
        let finalBody = inputOrInitBody;
        if (initBody === null && inputBody !== null) {
      if (input[_body] && input[_body].unusable()) {
        throw new TypeError("Input request's body is unusable.");
      }
      finalBody = inputBody.createProxy();
    }
        request.body = finalBody;
  }
  get method() {
    webidl.assertBranded(this, RequestPrototype);
    if (this[_method]) {
      return this[_method];
    }
    this[_method] = this[_request].method;
    return this[_method];
  }
  get url() {
    webidl.assertBranded(this, RequestPrototype);
    if (this[_url]) {
      return this[_url];
    }
    this[_url] = this[_request].url();
    return this[_url];
  }
  get headers() {
    webidl.assertBranded(this, RequestPrototype);
    return this[_headers];
  }
  get redirect() {
    webidl.assertBranded(this, RequestPrototype);
    return this[_request].redirectMode;
  }
  get signal() {
    webidl.assertBranded(this, RequestPrototype);
    return this[_signal];
  }
  clone() {
    const prefix = "Failed to execute 'Request.clone'";
    webidl.assertBranded(this, RequestPrototype);
    if (this[_body] && this[_body].unusable()) {
      throw new TypeError("Body is unusable.");
    }
    const clonedReq = cloneInnerRequest(this[_request]);
    assert(this[_signal] !== null);
    const clonedSignal = abortSignal.createDependentAbortSignal(
      [this[_signal]],
      prefix,
    );
    return fromInnerRequest(
      clonedReq,
      clonedSignal,
      guardFromHeaders(this[_headers]),
    );
  }
  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    return inspect(
      createFilteredInspectProxy({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(RequestPrototype, this),
        keys: [
          "bodyUsed",
          "headers",
          "method",
          "redirect",
          "url",
        ],
      }),
      inspectOptions,
    );
  }
}
webidl.configureInterface(Request);
const RequestPrototype = Request.prototype;
mixinBody(RequestPrototype, _body, _mimeType);
webidl.converters["Request"] = webidl.createInterfaceConverter(
  "Request",
  RequestPrototype,
);
webidl.converters["RequestInfo_DOMString"] = (V, prefix, context, opts) => {
    if (typeof V == "object") {
    if (ObjectPrototypeIsPrototypeOf(RequestPrototype, V)) {
      return webidl.converters["Request"](V, prefix, context, opts);
    }
  }
    return webidl.converters["DOMString"](V, prefix, context, opts);
};
webidl.converters["RequestRedirect"] = webidl.createEnumConverter(
  "RequestRedirect",
  [
    "follow",
    "error",
    "manual",
  ],
);
webidl.converters["RequestInit"] = webidl.createDictionaryConverter(
  "RequestInit",
  [
    { key: "method", converter: webidl.converters["ByteString"] },
    { key: "headers", converter: webidl.converters["HeadersInit"] },
    {
      key: "body",
      converter: webidl.createNullableConverter(
        webidl.converters["BodyInit_DOMString"],
      ),
    },
    { key: "redirect", converter: webidl.converters["RequestRedirect"] },
    {
      key: "signal",
      converter: webidl.createNullableConverter(
        webidl.converters["AbortSignal"],
      ),
    },
    { key: "client", converter: webidl.converters.any },
  ],
);
function toInnerRequest(request) {
  return request[_request];
}
function fromInnerRequest(inner, signal, guard) {
  const request = new Request(_brand);
  request[_request] = inner;
  request[_signal] = signal;
  request[_getHeaders] = () => headersFromHeaderList(inner.headerList, guard);
  return request;
}
export {
  fromInnerRequest,
  newInnerRequest,
  processUrlList,
  Request,
  RequestPrototype,
  toInnerRequest,
};