// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
/*!
 * Adapted directly from negotiator at https://github.com/jshttp/negotiator/
 * which is licensed as follows:
 *
 * (The MIT License)
 *
 * Copyright (c) 2012-2014 Federico Romero
 * Copyright (c) 2012-2014 Isaac Z. Schlueter
 * Copyright (c) 2014-2015 Douglas Christopher Wilson
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

import { compareSpecs, isQuality, type Specificity } from "./common.ts";

interface MediaTypeSpecificity extends Specificity {
  type: string;
  subtype: string;
  params: { [param: string]: string | undefined };
}

const simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;

function splitKeyValuePair(str: string): [string, string | undefined] {
  const [key, value] = str.split("=");
  return [key!.toLowerCase(), value];
}

function parseMediaType(
  str: string,
  i: number,
): MediaTypeSpecificity | undefined {
  const match = simpleMediaTypeRegExp.exec(str);

  if (!match) {
    return;
  }

  const [, type, subtype, parameters] = match;
  if (!type || !subtype) {
    return;
  }

  const params: { [param: string]: string | undefined } = Object.create(null);
  let q = 1;
  if (parameters) {
    const kvps = parameters.split(";").map((p) => p.trim()).map(
      splitKeyValuePair,
    );

    for (const [key, val] of kvps) {
      const value = val && val[0] === `"` && val[val.length - 1] === `"`
        ? val.slice(1, val.length - 1)
        : val;

      if (key === "q" && value) {
        q = parseFloat(value);
        break;
      }

      params[key] = value;
    }
  }

  return { type, subtype, params, q, i };
}

function parseAccept(accept: string): MediaTypeSpecificity[] {
  const accepts = accept.split(",").map((p) => p.trim());

  const mediaTypes: MediaTypeSpecificity[] = [];
  for (const [index, accept] of accepts.entries()) {
    const mediaType = parseMediaType(accept.trim(), index);

    if (mediaType) {
      mediaTypes.push(mediaType);
    }
  }

  return mediaTypes;
}

function getFullType(spec: MediaTypeSpecificity) {
  return `${spec.type}/${spec.subtype}`;
}

function specify(
  type: string,
  spec: MediaTypeSpecificity,
  index: number,
): Specificity | undefined {
  const p = parseMediaType(type, index);

  if (!p) {
    return;
  }

  let s = 0;

  if (spec.type.toLowerCase() === p.type.toLowerCase()) {
    s |= 4;
  } else if (spec.type !== "*") {
    return;
  }

  if (spec.subtype.toLowerCase() === p.subtype.toLowerCase()) {
    s |= 2;
  } else if (spec.subtype !== "*") {
    return;
  }

  const keys = Object.keys(spec.params);
  if (keys.length) {
    if (
      keys.every((key) =>
        (spec.params[key] || "").toLowerCase() ===
          (p.params[key] || "").toLowerCase()
      )
    ) {
      s |= 1;
    } else {
      return;
    }
  }

  return {
    i: index,
    o: spec.o,
    q: spec.q,
    s,
  };
}

function getMediaTypePriority(
  type: string,
  accepted: MediaTypeSpecificity[],
  index: number,
) {
  let priority: Specificity = { o: -1, q: 0, s: 0, i: index };

  for (const accepts of accepted) {
    const spec = specify(type, accepts, index);

    if (
      spec &&
      ((priority.s || 0) - (spec.s || 0) ||
          (priority.q || 0) - (spec.q || 0) ||
          (priority.o || 0) - (spec.o || 0)) < 0
    ) {
      priority = spec;
    }
  }

  return priority;
}

export function preferredMediaTypes(
  accept?: string | null,
  provided?: string[],
): string[] {
  const accepts = parseAccept(accept === undefined ? "*/*" : accept || "");

  if (!provided) {
    return accepts
      .filter(isQuality)
      .sort(compareSpecs)
      .map(getFullType);
  }

  const priorities = provided.map((type, index) => {
    return getMediaTypePriority(type, accepts, index);
  });

  return priorities
    .filter(isQuality)
    .sort(compareSpecs)
    .map((priority) => provided[priorities.indexOf(priority)]!);
}

// denoCacheMetadata={"headers":{"vary":"Origin","set-cookie":"__cf_bm=N0EwQ_DZL9PSvecFBFPU4EfzoiQKy9C0nSEg1UYEmSg-1785241875.2256448-1.0.1.1-pGRYoROtWjuUzDdOKuquzRQdPhWijRHHl0KOtCbAqD.t68bUCalBXzW8Ca7FgUP6g4CJpTGSTS1zUMbow8K0hEgzIW7FWQ41D7m9SUI6ULXJ07ezO6cyN85DXh1NQv_2; HttpOnly; SameSite=None; Secure; Path=/; Domain=jsr.io; Expires=Tue, 28 Jul 2026 13:01:15 GMT","x-jsr-backend":"modules","access-control-allow-origin":"*","server":"cloudflare","content-length":"4970","cross-origin-resource-policy":"cross-origin","x-content-type-options":"nosniff","cf-ray":"a223e6581b817db6-LAX","alt-svc":"h3=\":443\"; ma=86400","nel":"{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}","access-control-expose-headers":"*","x-robots-tag":"noindex","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","etag":"\"a1e87674c2ce74c0d806f4ff7668e451\"","report-to":"{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=Bp9b%2F7Gz1XKI%2F8CiZAQxM3K9IFan%2FkKXvXzahuWuIugm2fLQe%2FU3akirvwNOvWVs2T8EREDIcU4MR7vNsm%2BRCMLw%2Fj4O26zW%2BjP20G7WsHkPqme2z13IRWQ%3D\"}]}","date":"Tue, 28 Jul 2026 12:31:15 GMT","content-type":"text/typescript"},"url":"https://jsr.io/@std/http/0.224.5/_negotiation/media_type.ts","time":1785241875}