// 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="../webidl/internal.d.ts" />
/// <reference path="../web/internal.d.ts" />
/// <reference path="../../cli/tsc/dts/lib.deno_web.d.ts" />

(function () {
const { core, primordials } = __bootstrap;
const {
  Error,
  ErrorPrototype,
  ObjectDefineProperty,
  ObjectCreate,
  ObjectEntries,
  ObjectHasOwn,
  ObjectPrototypeIsPrototypeOf,
  ObjectSetPrototypeOf,
  ReflectConstruct,
  ReflectHas,
  RangeError,
  Symbol,
  SymbolFor,
} = primordials;

const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");

// Lazy-load createFilteredInspectProxy from console to avoid
// circular dependency at load time. Only needed for custom inspect.
let _createFilteredInspectProxy;
function getCreateFilteredInspectProxy() {
  if (!_createFilteredInspectProxy) {
    _createFilteredInspectProxy = core.loadExtScript(
      "ext:deno_web/01_console.js",
    ).createFilteredInspectProxy;
  }
  return _createFilteredInspectProxy;
}

const _name = Symbol("name");
const _message = Symbol("message");
const _code = Symbol("code");

// Defined in WebIDL 4.3.
// https://webidl.spec.whatwg.org/#idl-DOMException
const INDEX_SIZE_ERR = 1;
const DOMSTRING_SIZE_ERR = 2;
const HIERARCHY_REQUEST_ERR = 3;
const WRONG_DOCUMENT_ERR = 4;
const INVALID_CHARACTER_ERR = 5;
const NO_DATA_ALLOWED_ERR = 6;
const NO_MODIFICATION_ALLOWED_ERR = 7;
const NOT_FOUND_ERR = 8;
const NOT_SUPPORTED_ERR = 9;
const INUSE_ATTRIBUTE_ERR = 10;
const INVALID_STATE_ERR = 11;
const SYNTAX_ERR = 12;
const INVALID_MODIFICATION_ERR = 13;
const NAMESPACE_ERR = 14;
const INVALID_ACCESS_ERR = 15;
const VALIDATION_ERR = 16;
const TYPE_MISMATCH_ERR = 17;
const SECURITY_ERR = 18;
const NETWORK_ERR = 19;
const ABORT_ERR = 20;
const URL_MISMATCH_ERR = 21;
const QUOTA_EXCEEDED_ERR = 22;
const TIMEOUT_ERR = 23;
const INVALID_NODE_TYPE_ERR = 24;
const DATA_CLONE_ERR = 25;

// Defined in WebIDL 2.8.1.
// https://webidl.spec.whatwg.org/#dfn-error-names-table
/** @type {Record<string, number>} */
// the prototype should be null, to prevent user code from looking
// up Object.prototype properties, such as "toString"
const nameToCodeMapping = ObjectCreate(null, {
  IndexSizeError: { value: INDEX_SIZE_ERR },
  HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR },
  WrongDocumentError: { value: WRONG_DOCUMENT_ERR },
  InvalidCharacterError: { value: INVALID_CHARACTER_ERR },
  NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR },
  NotFoundError: { value: NOT_FOUND_ERR },
  NotSupportedError: { value: NOT_SUPPORTED_ERR },
  InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR },
  InvalidStateError: { value: INVALID_STATE_ERR },
  SyntaxError: { value: SYNTAX_ERR },
  InvalidModificationError: { value: INVALID_MODIFICATION_ERR },
  NamespaceError: { value: NAMESPACE_ERR },
  InvalidAccessError: { value: INVALID_ACCESS_ERR },
  TypeMismatchError: { value: TYPE_MISMATCH_ERR },
  SecurityError: { value: SECURITY_ERR },
  NetworkError: { value: NETWORK_ERR },
  AbortError: { value: ABORT_ERR },
  URLMismatchError: { value: URL_MISMATCH_ERR },
  QuotaExceededError: { value: QUOTA_EXCEEDED_ERR },
  TimeoutError: { value: TIMEOUT_ERR },
  InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR },
  DataCloneError: { value: DATA_CLONE_ERR },
});

// Defined in WebIDL 4.3.
// https://webidl.spec.whatwg.org/#idl-DOMException
class DOMException {
  [_message];
  [_name];
  [_code];

  // https://webidl.spec.whatwg.org/#dom-domexception-domexception
  // Since Node.js allows the second argument to accept an options object, we need to support that
  // https://github.com/nodejs/node/blob/9e201e61fd8e4b8bfb74409151cbcbbc7377ca67/lib/internal/per_context/domexception.js#L82-L96
  constructor(message = "", options = "Error") {
    message = webidl.converters.DOMString(
      message,
      "Failed to construct 'DOMException'",
      "Argument 1",
    );

    // execute Error constructor to have stack property and [[ErrorData]] internal slot
    const error = ReflectConstruct(Error, [], new.target);

    let name;
    if (options !== null && typeof options === "object") {
      name = webidl.converters.DOMString(
        options.name,
        "Failed to construct 'DOMException'",
        "Argument 2",
      );
      if (ReflectHas(options, "cause")) {
        ObjectDefineProperty(error, "cause", {
          __proto__: null,
          value: options.cause,
          configurable: true,
          writable: true,
          enumerable: false,
        });
      }
    } else {
      name = webidl.converters.DOMString(
        options,
        "Failed to construct 'DOMException'",
        "Argument 2",
      );
    }
    const code = nameToCodeMapping[name] ?? 0;

    error[_message] = message;
    error[_name] = name;
    error[_code] = code;
    error[webidl.brand] = webidl.brand;
    ObjectDefineProperty(error, core.hostObjectBrand, {
      __proto__: null,
      value: () => ({
        type: "DOMException",
        message,
        name,
        stack: error.stack,
      }),
      enumerable: false,
      configurable: false,
      writable: false,
    });

    return error;
  }

  get message() {
    webidl.assertBranded(this, DOMExceptionPrototype);
    return this[_message];
  }

  get name() {
    webidl.assertBranded(this, DOMExceptionPrototype);
    return this[_name];
  }

  get code() {
    webidl.assertBranded(this, DOMExceptionPrototype);
    return this[_code];
  }

  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    if (ObjectHasOwn(this, "stack")) {
      const stack = this.stack;
      if (typeof stack === "string") {
        return stack;
      }
    }
    return inspect(
      getCreateFilteredInspectProxy()({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this),
        keys: [
          "message",
          "name",
          "code",
        ],
      }),
      inspectOptions,
    );
  }
}

ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype);

webidl.configureInterface(DOMException);
const DOMExceptionPrototype = DOMException.prototype;

const entries = ObjectEntries({
  INDEX_SIZE_ERR,
  DOMSTRING_SIZE_ERR,
  HIERARCHY_REQUEST_ERR,
  WRONG_DOCUMENT_ERR,
  INVALID_CHARACTER_ERR,
  NO_DATA_ALLOWED_ERR,
  NO_MODIFICATION_ALLOWED_ERR,
  NOT_FOUND_ERR,
  NOT_SUPPORTED_ERR,
  INUSE_ATTRIBUTE_ERR,
  INVALID_STATE_ERR,
  SYNTAX_ERR,
  INVALID_MODIFICATION_ERR,
  NAMESPACE_ERR,
  INVALID_ACCESS_ERR,
  VALIDATION_ERR,
  TYPE_MISMATCH_ERR,
  SECURITY_ERR,
  NETWORK_ERR,
  ABORT_ERR,
  URL_MISMATCH_ERR,
  QUOTA_EXCEEDED_ERR,
  TIMEOUT_ERR,
  INVALID_NODE_TYPE_ERR,
  DATA_CLONE_ERR,
});
for (let i = 0; i < entries.length; ++i) {
  const { 0: key, 1: value } = entries[i];
  const desc = { __proto__: null, value, enumerable: true };
  ObjectDefineProperty(DOMException, key, desc);
  ObjectDefineProperty(DOMException.prototype, key, desc);
}

core.registerCloneableResource("DOMException", (data) => {
  const ex = new DOMException(data.message, data.name);
  if (data.stack !== undefined) {
    ObjectDefineProperty(ex, "stack", {
      __proto__: null,
      value: data.stack,
      configurable: true,
      writable: true,
      enumerable: false,
    });
  }
  return ex;
});

const _quota = Symbol("quota");
const _requested = Symbol("requested");

// Defined in WebIDL 4.3.1.
// https://webidl.spec.whatwg.org/#quotaexceedederror
class QuotaExceededError extends DOMException {
  [_quota];
  [_requested];

  constructor(message = "", options = { __proto__: null }) {
    super(message, "QuotaExceededError");

    if (options !== null && typeof options === "object") {
      if (ObjectHasOwn(options, "quota")) {
        const quota = webidl.converters["unrestricted double"](
          options.quota,
          "Failed to construct 'QuotaExceededError'",
          "'quota' member of QuotaExceededErrorOptions",
        );
        if (quota < 0) {
          throw new RangeError(
            "Failed to construct 'QuotaExceededError': quota must not be negative",
          );
        }
        this[_quota] = quota;
      } else {
        this[_quota] = null;
      }
      if (ObjectHasOwn(options, "requested")) {
        const requested = webidl.converters["unrestricted double"](
          options.requested,
          "Failed to construct 'QuotaExceededError'",
          "'requested' member of QuotaExceededErrorOptions",
        );
        if (requested < 0) {
          throw new RangeError(
            "Failed to construct 'QuotaExceededError': requested must not be negative",
          );
        }
        this[_requested] = requested;
      } else {
        this[_requested] = null;
      }
    } else {
      this[_quota] = null;
      this[_requested] = null;
    }
  }

  get quota() {
    webidl.assertBranded(this, QuotaExceededErrorPrototype);
    return this[_quota];
  }

  get requested() {
    webidl.assertBranded(this, QuotaExceededErrorPrototype);
    return this[_requested];
  }
}

webidl.configureInterface(QuotaExceededError);
const QuotaExceededErrorPrototype = QuotaExceededError.prototype;

return {
  DOMException,
  DOMExceptionPrototype,
  QuotaExceededError,
  QuotaExceededErrorPrototype,
};
})();
