// Copyright 2018-2026 the Deno authors. MIT license.

// @ts-check
/// <reference path="../../core/internal.d.ts" />
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../web/internal.d.ts" />
/// <reference path="../../cli/tsc/dts/lib.deno_web.d.ts" />

(function () {
const { core, internals, primordials } = __bootstrap;
const {
  op_base64_decode,
  op_base64_decode_into,
  op_base64_encode,
  op_base64_encode_from_buffer,
} = core.ops;
const {
  ArrayPrototypeJoin,
  ArrayPrototypeMap,
  decodeURIComponent,
  Error,
  JSONStringify,
  NumberPrototypeToString,
  ObjectPrototypeIsPrototypeOf,
  SafeArrayIterator,
  SafeRegExp,
  String,
  StringPrototypeCharAt,
  StringPrototypeCharCodeAt,
  StringPrototypeMatch,
  StringPrototypePadStart,
  StringPrototypeReplace,
  StringPrototypeSlice,
  StringPrototypeSubstring,
  StringPrototypeToLowerCase,
  StringPrototypeToUpperCase,
  Symbol,
  TypeError,
} = primordials;

const { URLPrototype } = core.loadExtScript("ext:deno_web/00_url.js");

const ASCII_DIGIT = ["\u0030-\u0039"];
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
const ASCII_ALPHA = [
  ...new SafeArrayIterator(ASCII_UPPER_ALPHA),
  ...new SafeArrayIterator(ASCII_LOWER_ALPHA),
];
const ASCII_ALPHANUMERIC = [
  ...new SafeArrayIterator(ASCII_DIGIT),
  ...new SafeArrayIterator(ASCII_ALPHA),
];

const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"];
const HTTP_WHITESPACE = [
  "\u000A",
  "\u000D",
  ...new SafeArrayIterator(HTTP_TAB_OR_SPACE),
];

const HTTP_TOKEN_CODE_POINT = [
  "\u0021",
  "\u0023",
  "\u0024",
  "\u0025",
  "\u0026",
  "\u0027",
  "\u002A",
  "\u002B",
  "\u002D",
  "\u002E",
  "\u005E",
  "\u005F",
  "\u0060",
  "\u007C",
  "\u007E",
  ...new SafeArrayIterator(ASCII_ALPHANUMERIC),
];
const HTTP_TOKEN_CODE_POINT_RE = new SafeRegExp(
  `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
);
const HTTP_QUOTED_STRING_TOKEN_POINT = [
  "\u0009",
  "\u0020-\u007E",
  "\u0080-\u00FF",
];
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new SafeRegExp(
  `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
);
const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE);
const HTTP_TAB_OR_SPACE_PREFIX_RE = new SafeRegExp(
  `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`,
  "g",
);
const HTTP_TAB_OR_SPACE_SUFFIX_RE = new SafeRegExp(
  `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`,
  "g",
);
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
const HTTP_BETWEEN_WHITESPACE = new SafeRegExp(
  `^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`,
);
const HTTP_WHITESPACE_PREFIX_RE = new SafeRegExp(
  `^[${HTTP_WHITESPACE_MATCHER}]+`,
  "g",
);
const HTTP_WHITESPACE_SUFFIX_RE = new SafeRegExp(
  `[${HTTP_WHITESPACE_MATCHER}]+$`,
  "g",
);

/**
 * Turn a string of chars into a regex safe matcher.
 * @param {string[]} chars
 * @returns {string}
 */
function regexMatcher(chars) {
  const matchers = ArrayPrototypeMap(chars, (char) => {
    if (char.length === 1) {
      const a = StringPrototypePadStart(
        NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16),
        4,
        "0",
      );
      return `\\u${a}`;
    } else if (char.length === 3 && char[1] === "-") {
      const a = StringPrototypePadStart(
        NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16),
        4,
        "0",
      );
      const b = StringPrototypePadStart(
        NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16),
        4,
        "0",
      );
      return `\\u${a}-\\u${b}`;
    } else {
      throw new TypeError("unreachable");
    }
  });
  return ArrayPrototypeJoin(matchers, "");
}

/**
 * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
 * @param {string} input
 * @param {number} position
 * @param {(char: string) => boolean} condition
 * @returns {{result: string, position: number}}
 */
function collectSequenceOfCodepoints(input, position, condition) {
  const start = position;
  for (
    let c = StringPrototypeCharAt(input, position);
    position < input.length && condition(c);
    c = StringPrototypeCharAt(input, ++position)
  );
  return { result: StringPrototypeSlice(input, start, position), position };
}

const LOWERCASE_PATTERN = new SafeRegExp(/[a-z]/g);

/**
 * @param {string} s
 * @returns {string}
 */
function byteUpperCase(s) {
  return StringPrototypeReplace(
    String(s),
    LOWERCASE_PATTERN,
    function byteUpperCaseReplace(c) {
      return StringPrototypeToUpperCase(c);
    },
  );
}

/**
 * @param {string} s
 * @returns {string}
 */
function byteLowerCase(s) {
  // NOTE: correct since all callers convert to ByteString first
  // TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter
  return StringPrototypeToLowerCase(s);
}

/**
 * https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
 * @param {string} input
 * @param {number} position
 * @param {boolean} extractValue
 * @returns {{result: string, position: number}}
 */
function collectHttpQuotedString(input, position, extractValue) {
  // 1.
  const positionStart = position;
  // 2.
  let value = "";
  // 3.
  if (input[position] !== "\u0022") throw new TypeError('must be "');
  // 4.
  position++;
  // 5.
  while (true) {
    // 5.1.
    const res = collectSequenceOfCodepoints(
      input,
      position,
      (c) => c !== "\u0022" && c !== "\u005C",
    );
    value += res.result;
    position = res.position;
    // 5.2.
    if (position >= input.length) break;
    // 5.3.
    const quoteOrBackslash = input[position];
    // 5.4.
    position++;
    // 5.5.
    if (quoteOrBackslash === "\u005C") {
      // 5.5.1.
      if (position >= input.length) {
        value += "\u005C";
        break;
      }
      // 5.5.2.
      value += input[position];
      // 5.5.3.
      position++;
    } else {
      // 5.6.
      // 5.6.1
      if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "');
      // 5.6.2
      break;
    }
  }
  // 6.
  if (extractValue) return { result: value, position };
  // 7.
  return {
    result: StringPrototypeSubstring(input, positionStart, position + 1),
    position,
  };
}

/**
 * @param {Uint8Array} data
 * @returns {string}
 */
function forgivingBase64Encode(data) {
  return op_base64_encode(data);
}

/**
 * @param {Uint8Array} data
 * @param {number} offset
 * @param {number} length
 * @returns {string}
 */
function forgivingBase64EncodeFromBuffer(data, offset, length) {
  return op_base64_encode_from_buffer(data, offset, length);
}

/**
 * @param {string} data
 * @returns {Uint8Array}
 */
function forgivingBase64Decode(data) {
  return op_base64_decode(data);
}

/**
 * @param {string} data
 * @param {Uint8Array} target
 * @param {number} offset
 * @returns {number}
 */
function forgivingBase64DecodeInto(data, target, offset) {
  return op_base64_decode_into(data, target, offset);
}

/**
 * @param {string} char
 * @returns {boolean}
 */
function isHttpWhitespace(char) {
  switch (char) {
    case "\u0009":
    case "\u000A":
    case "\u000D":
    case "\u0020":
      return true;
    default:
      return false;
  }
}

/**
 * @param {string} s
 * @returns {string}
 */
function httpTrim(s) {
  if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) {
    return s;
  }
  return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? "";
}

class AssertionError extends Error {
  constructor(msg) {
    super(msg);
    this.name = "AssertionError";
  }
}

/**
 * @param {unknown} cond
 * @param {string=} msg
 * @returns {asserts cond}
 */
function assert(cond, msg = "Assertion failed.") {
  if (!cond) {
    throw new AssertionError(msg);
  }
}

/**
 * @param {unknown} value
 * @returns {string}
 */
function serializeJSValueToJSONString(value) {
  const result = JSONStringify(value);
  if (result === undefined) {
    throw new TypeError("Value is not JSON serializable");
  }
  return result;
}

const PATHNAME_WIN_RE = new SafeRegExp(/^\/*([A-Za-z]:)(\/|$)/);
const SLASH_WIN_RE = new SafeRegExp(/\//g);
const PERCENT_RE = new SafeRegExp(/%(?![0-9A-Fa-f]{2})/g);

// Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.
/**
 * @param {URL} url
 * @returns {string}
 */
function pathFromURLWin32(url) {
  let p = StringPrototypeReplace(url.pathname, PATHNAME_WIN_RE, "$1/");
  p = StringPrototypeReplace(p, SLASH_WIN_RE, "\\");
  p = StringPrototypeReplace(p, PERCENT_RE, "%25");
  let path = decodeURIComponent(p);
  if (url.hostname != "") {
    // Note: The `URL` implementation guarantees that the drive letter and
    // hostname are mutually exclusive. Otherwise it would not have been valid
    // to append the hostname and path like this.
    path = `\\\\${url.hostname}${path}`;
  }
  return path;
}

// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.
/**
 * @param {URL} url
 * @returns {string}
 */
function pathFromURLPosix(url) {
  if (url.hostname !== "") {
    throw new TypeError("Host must be empty");
  }

  return decodeURIComponent(
    StringPrototypeReplace(url.pathname, PERCENT_RE, "%25"),
  );
}

function pathFromURL(pathOrUrl) {
  if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) {
    if (pathOrUrl.protocol != "file:") {
      throw new TypeError("Must be a file URL");
    }

    return core.build.os == "windows"
      ? pathFromURLWin32(pathOrUrl)
      : pathFromURLPosix(pathOrUrl);
  }
  return pathOrUrl;
}

// NOTE(bartlomieju): this is exposed on `internals` so we can test
// it in unit tests
internals.pathFromURL = pathFromURL;

// deno-lint-ignore deno-internal/prefer-primordials
const SymbolMetadata = Symbol.metadata ?? Symbol("Symbol.metadata");

return {
  ASCII_ALPHA,
  ASCII_ALPHANUMERIC,
  ASCII_DIGIT,
  ASCII_LOWER_ALPHA,
  ASCII_UPPER_ALPHA,
  assert,
  AssertionError,
  byteLowerCase,
  byteUpperCase,
  collectHttpQuotedString,
  collectSequenceOfCodepoints,
  forgivingBase64Decode,
  forgivingBase64DecodeInto,
  forgivingBase64Encode,
  forgivingBase64EncodeFromBuffer,
  HTTP_QUOTED_STRING_TOKEN_POINT,
  HTTP_QUOTED_STRING_TOKEN_POINT_RE,
  HTTP_TAB_OR_SPACE,
  HTTP_TAB_OR_SPACE_PREFIX_RE,
  HTTP_TAB_OR_SPACE_SUFFIX_RE,
  HTTP_TOKEN_CODE_POINT,
  HTTP_TOKEN_CODE_POINT_RE,
  HTTP_WHITESPACE,
  HTTP_WHITESPACE_PREFIX_RE,
  HTTP_WHITESPACE_SUFFIX_RE,
  httpTrim,
  pathFromURL,
  regexMatcher,
  serializeJSValueToJSONString,
  SymbolMetadata,
};
})();
