// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

/**
 * Contains the functions {@linkcode accepts}, {@linkcode acceptsEncodings}, and
 * {@linkcode acceptsLanguages} to provide content negotiation capabilities.
 *
 * @module
 */

import { preferredEncodings } from "./_negotiation/encoding.ts";
import { preferredLanguages } from "./_negotiation/language.ts";
import { preferredMediaTypes } from "./_negotiation/media_type.ts";

/** Loose copy of {@linkcode Request}. */
export type Request = {
  headers: {
    get(key: string): string | null;
  };
};

/**
 * Returns an array of media types accepted by the request, in order of
 * preference. If there are no media types supplied in the request, then any
 * media type selector will be returned.
 *
 * @example Usage
 * ```ts
 * import { accepts } from "@std/http/negotiation";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * const request = new Request("https://example.com/", {
 *   headers: {
 *     accept:
 *       "text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, *\/*;q=0.8",
 *   },
 * });
 *
 * assertEquals(accepts(request), [
 *   "text/html",
 *   "application/xhtml+xml",
 *   "image/webp",
 *   "application/xml",
 *   "*\/*",
 * ]);
 * ```
 *
 * @param request The request to get the acceptable media types for.
 * @returns An array of acceptable media types.
 */
export function accepts(request: Request): string[];
/**
 * For a given set of media types, return the best match accepted in the
 * request. If no media type matches, then the function returns `undefined`.
 *
 *  @example Usage
 * ```ts
 * import { accepts } from "@std/http/negotiation";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * const request = new Request("https://example.com/", {
 *   headers: {
 *     accept:
 *       "text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, *\/*;q=0.8",
 *   },
 * });
 *
 * assertEquals(accepts(request, "text/html", "image/webp"), "text/html");
 * ```
 *
 * @param request The request to get the acceptable media types for.
 * @param types An array of media types to find the best matching one from.
 * @returns The best matching media type, if any match.
 */
export function accepts(
  request: Request,
  ...types: string[]
): string | undefined;
export function accepts(
  request: Request,
  ...types: string[]
): string | string[] | undefined {
  const accept = request.headers.get("accept");
  return types.length
    ? accept ? preferredMediaTypes(accept, types)[0] : types[0]
    : accept
    ? preferredMediaTypes(accept)
    : ["*/*"];
}

/**
 * Returns an array of content encodings accepted by the request, in order of
 * preference. If there are no encoding supplied in the request, then `["*"]`
 * is returned, implying any encoding is accepted.
 *
 * @example Usage
 * ```ts
 * import { acceptsEncodings } from "@std/http/negotiation";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * const request = new Request("https://example.com/", {
 *   headers: { "accept-encoding": "deflate, gzip;q=1.0, *;q=0.5" },
 * });
 *
 * assertEquals(acceptsEncodings(request), ["deflate", "gzip", "*"]);
 * ```
 *
 * @param request The request to get the acceptable content encodings for.
 * @returns An array of content encodings this request accepts.
 */
export function acceptsEncodings(request: Request): string[];
/**
 * For a given set of content encodings, return the best match accepted in the
 * request. If no content encodings match, then the function returns
 * `undefined`.
 *
 * **NOTE:** You should always supply `identity` as one of the encodings
 * to ensure that there is a match when the `Accept-Encoding` header is part
 * of the request.
 *
 * @example Usage
 * ```ts
 * import { acceptsEncodings } from "@std/http/negotiation";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * const request = new Request("https://example.com/", {
 *   headers: { "accept-encoding": "deflate, gzip;q=1.0, *;q=0.5" },
 * });
 *
 * assertEquals(acceptsEncodings(request, "gzip", "identity"), "gzip");
 * ```
 *
 * @param request The request to get the acceptable content encodings for.
 * @param encodings An array of encodings to find the best matching one from.
 * @returns The best matching encoding, if any match.
 */
export function acceptsEncodings(
  request: Request,
  ...encodings: string[]
): string | undefined;
export function acceptsEncodings(
  request: Request,
  ...encodings: string[]
): string | string[] | undefined {
  const acceptEncoding = request.headers.get("accept-encoding");
  return encodings.length
    ? acceptEncoding
      ? preferredEncodings(acceptEncoding, encodings)[0]
      : encodings[0]
    : acceptEncoding
    ? preferredEncodings(acceptEncoding)
    : ["*"];
}

/**
 * Returns an array of languages accepted by the request, in order of
 * preference. If there are no languages supplied in the request, then `["*"]`
 * is returned, imply any language is accepted.
 *
 * @example Usage
 * ```ts
 * import { acceptsLanguages } from "@std/http/negotiation";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * const request = new Request("https://example.com/", {
 *   headers: {
 *     "accept-language": "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
 *   },
 * });
 *
 * assertEquals(acceptsLanguages(request), ["fr-CH", "fr", "en", "de", "*"]);
 * ```
 *
 * @param request The request to get the acceptable languages for.
 * @returns An array of languages this request accepts.
 */
export function acceptsLanguages(request: Request): string[];
/**
 * For a given set of languages, return the best match accepted in the request.
 * If no languages match, then the function returns `undefined`.
 *
 * @example Usage
 * ```ts
 * import { acceptsLanguages } from "@std/http/negotiation";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * const request = new Request("https://example.com/", {
 *   headers: {
 *     "accept-language": "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
 *   },
 * });
 *
 * assertEquals(acceptsLanguages(request, "en-gb", "en-us", "en"), "en");
 * ```
 *
 * @param request The request to get the acceptable language for.
 * @param langs An array of languages to find the best matching one from.
 * @returns The best matching language, if any match.
 */
export function acceptsLanguages(
  request: Request,
  ...langs: string[]
): string | undefined;
export function acceptsLanguages(
  request: Request,
  ...langs: string[]
): string | string[] | undefined {
  const acceptLanguage = request.headers.get("accept-language");
  return langs.length
    ? acceptLanguage ? preferredLanguages(acceptLanguage, langs)[0] : langs[0]
    : acceptLanguage
    ? preferredLanguages(acceptLanguage)
    : ["*"];
}

// denoCacheMetadata={"headers":{"cf-cache-status":"HIT","x-jsr-backend":"modules","server":"cloudflare","alt-svc":"h3=\":443\"; ma=86400","report-to":"{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=zUwD1L0CIkpiyXG%2BlMQKNlK1f%2F7d8egHU06MgAvFZbLmrr%2Br9DzAjAQGeaY2qUcODT03DtIhhE7%2BkLWcK9uHA14z0j0Beb1a9Ct3yijVjktrg4Z1FXv45R0%3D\"}]}","cf-ray":"a223e656c80b7db6-LAX","etag":"\"bc4b3539850961356527cebe4011fea0\"","access-control-allow-origin":"*","content-type":"text/typescript","access-control-expose-headers":"*","age":"126790","cache-control":"public, max-age=31536000, immutable","content-security-policy":"default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; font-src 'none'; connect-src 'none'; frame-src 'none'; object-src 'none'; frame-ancestors 'none'; sandbox; form-action 'none';","accept-ranges":"bytes","x-robots-tag":"noindex","date":"Tue, 28 Jul 2026 12:31:15 GMT","set-cookie":"__cf_bm=w8zYZVDqVs9MYqafwvO5xTZ798VrPjifDVab3NMO.HA-1785241875.0121539-1.0.1.1-EFqH9ZNpQ3jWkOU6tjw7ohRz13g.LydkYSkv3RDa7c66QnQ.amIf4zBGx2yvnmEQ3DFcsiN6OFtZl8xmidTyEepFRYAtwj6O6UGa6C0R4vKhDYZJM7mtD1y4pYuDxl9a; HttpOnly; SameSite=None; Secure; Path=/; Domain=jsr.io; Expires=Tue, 28 Jul 2026 13:01:15 GMT","cross-origin-resource-policy":"cross-origin","nel":"{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}","x-content-type-options":"nosniff","content-length":"6861","vary":"Origin"},"url":"https://jsr.io/@std/http/0.224.5/negotiation.ts","time":1785241875}