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

import { parseMediaType } from "./parse_media_type.ts";
import { getCharset } from "./get_charset.ts";
import { formatMediaType } from "./format_media_type.ts";
import type { db } from "./_db.ts";
import { typeByExtension } from "./type_by_extension.ts";

/** MIME-types database. */
export type DB = typeof db;
/** Maps content types to their corresponding file extensions. */
export type ContentTypeToExtension = {
  /**
   * Maps each content type key to its corresponding file extension.
   */
  [K in keyof DB]: DB[K] extends { "extensions": readonly string[] }
    ? DB[K]["extensions"][number]
    : never;
};

/** Known extension or type. Used in {@linkcode contentType}. */
export type KnownExtensionOrType =
  | keyof ContentTypeToExtension
  | ContentTypeToExtension[keyof ContentTypeToExtension]
  | `.${ContentTypeToExtension[keyof ContentTypeToExtension]}`;

/**
 * Returns the full `Content-Type` or `Content-Disposition` header value for the
 * given extension or media type.
 *
 * The function will treat the `extensionOrType` as a media type when it
 * contains a `/`, otherwise it will process it as an extension, with or without
 * the leading `.`.
 *
 * Returns `undefined` if unable to resolve the media type.
 *
 * > Note: a side effect of `deno/x/media_types` was that you could pass a file
 * > name (e.g. `file.json`) and it would return the content type. This behavior
 * > is intentionally not supported here. If you want to get an extension for a
 * > file name, use {@linkcode https://jsr.io/@std/path/doc/~/extname | extname}
 * > to determine the extension and pass it here.
 *
 * @template T Type of the extension or media type to resolve.
 *
 * @param extensionOrType The extension or media type to resolve.
 *
 * @returns The full `Content-Type` or `Content-Disposition` header value, or
 * `undefined` if unable to resolve the media type.
 *
 * @example Usage
 * ```ts
 * import { contentType } from "@std/media-types/content-type";
 * import { assertEquals } from "@std/assert/assert-equals";
 *
 * assertEquals(contentType(".json"), "application/json; charset=UTF-8");
 * assertEquals(contentType("text/html"), "text/html; charset=UTF-8");
 * assertEquals(contentType("text/html; charset=UTF-8"), "text/html; charset=UTF-8");
 * assertEquals(contentType("txt"), "text/plain; charset=UTF-8");
 * assertEquals(contentType("foo"), undefined);
 * assertEquals(contentType("file.json"), undefined);
 * ```
 */
export function contentType<
  // Workaround to autocomplete for parameters: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939
  // deno-lint-ignore ban-types
  T extends (string & {}) | KnownExtensionOrType,
>(
  extensionOrType: T,
): Lowercase<T> extends KnownExtensionOrType ? string : string | undefined {
  try {
    const [mediaType, params = {}] = extensionOrType.includes("/")
      ? parseMediaType(extensionOrType)
      : [typeByExtension(extensionOrType), undefined];
    if (!mediaType) {
      return undefined as Lowercase<T> extends KnownExtensionOrType ? string
        : string | undefined;
    }
    if (!("charset" in params)) {
      const charset = getCharset(mediaType);
      if (charset) {
        params.charset = charset;
      }
    }
    return formatMediaType(mediaType, params);
  } catch {
    // just swallow returning undefined
  }
  return undefined as Lowercase<T> extends KnownExtensionOrType ? string
    : string | undefined;
}

// denoCacheMetadata={"headers":{"nel":"{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}","access-control-expose-headers":"*","content-type":"text/typescript","etag":"\"391f31799a436e5ffc95f301eb0ad5f2\"","content-length":"3551","set-cookie":"__cf_bm=K8arArYpRHp7rIzGi9a1yxErqEU72MKyFpPyw3KQQXs-1785241875.1266925-1.0.1.1-NJ2GGPS5H52qz.9xHUjvYsYIAL4imO0ejGEWc6.AVK.LqNT4VvDwO9rM847PPcr5DM9FN3f4DInSdTbAu_2.8gXIHaHVIwSvJYYjB.IJAEIXkopd8LaHzfI9M49KEyW4; HttpOnly; SameSite=None; Secure; Path=/; Domain=jsr.io; Expires=Tue, 28 Jul 2026 13:01:15 GMT","date":"Tue, 28 Jul 2026 12:31:15 GMT","report-to":"{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=6Z9qJc5R7I34Ux%2BpFi%2BgcxTx6VXcPowlyWoJAi%2FU2LLCMSP%2FIFxGV%2Bj7xst7DTD%2F6vYdPusFxC50vX%2Fswf%2FdIvgaH49DXBUzoL1HqCqLMZR%2B6zmBw3Esi3g%3D\"}]}","server":"cloudflare","cross-origin-resource-policy":"cross-origin","x-jsr-backend":"modules","x-content-type-options":"nosniff","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';","cache-control":"public, max-age=31536000, immutable","x-robots-tag":"noindex","cf-ray":"a223e65789d17db6-LAX","access-control-allow-origin":"*","alt-svc":"h3=\":443\"; ma=86400","vary":"Origin"},"url":"https://jsr.io/@std/media-types/0.224.1/content_type.ts","time":1785241875}