// Copyright 2018-2026 the Deno authors. MIT license.
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.

(function () {
const { core, primordials } = __bootstrap;
const { nextTick } = core.loadExtScript("ext:deno_node/_next_tick.ts");
const { customPromisifyArgs } = core.loadExtScript(
  "ext:deno_node/internal/util.mjs",
);
const {
  validateBoolean,
  validateFunction,
  validateNumber,
  validateOneOf,
  validatePort,
  validateString,
} = core.loadExtScript("ext:deno_node/internal/validators.mjs");
const { isIP } = core.loadExtScript("ext:deno_node/internal/net.ts");
const dnsUtilsNs = core.loadExtScript(
  "ext:deno_node/internal/dns/utils.ts",
);
const {
  dnsOrderToNumber,
  getDefaultDnsOrder,
  getDefaultResolver,
  isFamily,
  isLookupCallback,
  isLookupOptions,
  isResolveCallback,
  setDefaultResolver,
  setDefaultResultOrder,
  validateHints,
  validDnsOrders,
} = dnsUtilsNs;
const CallbackResolver = dnsUtilsNs.Resolver;
const promisesBase =
  core.loadExtScript("ext:deno_node/internal/dns/promises.ts").default;
const {
  dnsException,
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_ARG_VALUE,
  ERR_MISSING_ARGS,
  handleDnsError,
} = core.loadExtScript("ext:deno_node/internal/errors.ts");
const {
  AI_ADDRCONFIG: ADDRCONFIG,
  AI_ALL: ALL,
  AI_V4MAPPED: V4MAPPED,
} = core.loadExtScript("ext:deno_node/internal_binding/ares.ts");
const {
  default: cares,
  GetAddrInfoReqWrap,
  GetNameInfoReqWrap,
  QueryReqWrap,
  kPermTokenSink,
} = core.loadExtScript("ext:deno_node/internal_binding/cares_wrap.ts");
const { domainToASCII } = core.loadExtScript(
  "ext:deno_node/internal/idna.ts",
);

const {
  ArrayPrototypeMap,
  ObjectCreate,
  ObjectDefineProperty,
  ReflectApply,
} = primordials;

function onlookup(
  this: GetAddrInfoReqWrap,
  err: number | null,
  addresses: string[],
  netPermToken: object | undefined,
) {
  if (err) {
    return this.callback(dnsException(err, "getaddrinfo", this.hostname));
  }

  // The NetPermToken is only handed to net.connect's built-in lookup, which
  // tags its callback with `kPermTokenSink`. User-supplied callbacks never
  // bear the marker and so never observe the token (GHSA-fhjh-jqv7-m238).
  if (this.callback[kPermTokenSink]) {
    this.callback(
      null,
      addresses[0],
      this.family || isIP(addresses[0]),
      netPermToken,
    );
  } else {
    this.callback(null, addresses[0], this.family || isIP(addresses[0]));
  }
}

function onlookupall(
  this: GetAddrInfoReqWrap,
  err: number | null,
  addresses: string[],
  netPermToken: object | undefined,
) {
  if (err) {
    return this.callback(dnsException(err, "getaddrinfo", this.hostname));
  }

  const family = this.family;
  const parsedAddresses = [];

  for (let i = 0; i < addresses.length; i++) {
    const addr = addresses[i];
    parsedAddresses[i] = {
      address: addr,
      family: family || isIP(addr),
    };
  }

  // Only net.connect's built-in lookup (tagged with `kPermTokenSink`) is given
  // the NetPermToken; user-supplied callbacks never observe it.
  if (this.callback[kPermTokenSink]) {
    this.callback(null, parsedAddresses, undefined, netPermToken);
  } else {
    this.callback(null, parsedAddresses);
  }
}

type LookupCallback = (
  err: ErrnoException | null,
  addressOrAddresses?: string | LookupAddress[] | null,
  family?: number,
) => void;

const validFamilies = [0, 4, 6];

// Easy DNS A/AAAA look up
// lookup(hostname, [options,] callback)
function lookup(
  hostname: string,
  family: number,
  callback: (
    err: ErrnoException | null,
    address: string,
    family: number,
  ) => void,
): GetAddrInfoReqWrap | Record<string, never>;
function lookup(
  hostname: string,
  options: LookupOneOptions,
  callback: (
    err: ErrnoException | null,
    address: string,
    family: number,
  ) => void,
): GetAddrInfoReqWrap | Record<string, never>;
function lookup(
  hostname: string,
  options: LookupAllOptions,
  callback: (err: ErrnoException | null, addresses: LookupAddress[]) => void,
): GetAddrInfoReqWrap | Record<string, never>;
function lookup(
  hostname: string,
  options: LookupOptions,
  callback: (
    err: ErrnoException | null,
    address: string | LookupAddress[],
    family: number,
  ) => void,
): GetAddrInfoReqWrap | Record<string, never>;
function lookup(
  hostname: string,
  callback: (
    err: ErrnoException | null,
    address: string,
    family: number,
  ) => void,
): GetAddrInfoReqWrap | Record<string, never>;
function lookup(
  hostname: string,
  options: unknown,
  callback?: unknown,
): GetAddrInfoReqWrap | Record<string, never> {
  let hints = 0;
  let family = 0;
  let all = false;
  let dnsOrder = getDefaultDnsOrder();
  let port = undefined;

  // Parse arguments
  if (hostname) {
    validateString(hostname, "hostname");
  }

  if (isLookupCallback(options)) {
    callback = options;
    family = 0;
  } else if (isFamily(options)) {
    validateFunction(callback, "callback");

    validateOneOf(options, "family", validFamilies);
    family = options;
  } else if (!isLookupOptions(options)) {
    validateFunction(arguments.length === 2 ? options : callback, "callback");

    throw new ERR_INVALID_ARG_TYPE("options", ["integer", "object"], options);
  } else {
    validateFunction(callback, "callback");

    if (options?.hints != null) {
      validateNumber(options.hints, "options.hints");
      hints = options.hints >>> 0;
      validateHints(hints);
    }

    if (options?.family != null) {
      // Accept both numeric (0, 4, 6) and string ('IPv4', 'IPv6') family values
      // to match Node.js behavior
      switch (options.family) {
        case "IPv4":
          family = 4;
          break;
        case "IPv6":
          family = 6;
          break;
        default:
          validateOneOf(options.family, "options.family", validFamilies);
          family = options.family;
      }
    }

    if (options?.all != null) {
      validateBoolean(options.all, "options.all");
      all = options.all;
    }

    if (options?.verbatim != null) {
      validateBoolean(options.verbatim, "options.verbatim");
      dnsOrder = options.verbatim ? "verbatim" : "ipv4first";
    }

    if ((options as Record<string, unknown>)?.order != null) {
      validateOneOf(
        (options as Record<string, unknown>).order,
        "options.order",
        validDnsOrders,
      );
      dnsOrder = (options as Record<string, unknown>).order as string;
    }

    if (options?.port != null) {
      validateNumber(options.port, "options.port");
      port = options.port;
    }
  }

  if (!hostname) {
    if (all) {
      nextTick(callback as LookupCallback, null, []);
    } else {
      nextTick(
        callback as LookupCallback,
        null,
        null,
        family === 6 ? 6 : 4,
      );
    }
    return {};
  }

  const matchedFamily = isIP(hostname);

  if (matchedFamily) {
    if (all) {
      nextTick(callback as LookupCallback, null, [
        { address: hostname, family: matchedFamily },
      ]);
    } else {
      nextTick(callback as LookupCallback, null, hostname, matchedFamily);
    }

    return {};
  }

  const req = new GetAddrInfoReqWrap();
  req.callback = callback as LookupCallback;
  req.family = family;
  req.hostname = hostname;
  req.oncomplete = all ? onlookupall : onlookup;
  req.port = port;

  const err = cares.getaddrinfo(
    req,
    domainToASCII(hostname),
    family,
    hints,
    dnsOrderToNumber(dnsOrder),
  );

  if (err) {
    nextTick(
      callback as LookupCallback,
      dnsException(err, "getaddrinfo", hostname),
    );

    return {};
  }

  return req;
}

ObjectDefineProperty(lookup, customPromisifyArgs, {
  __proto__: null,
  value: ["address", "family"],
  enumerable: false,
});

function onlookupservice(
  this: GetNameInfoReqWrap,
  err: Error | null,
  hostname?: string,
  service?: string,
) {
  if (err) {
    return this.callback!(handleDnsError(err, "getnameinfo", this.address));
  }

  this.callback!(err, hostname, service);
}

function lookupService(
  address: string,
  port: number,
  callback: (
    err: ErrnoException | null,
    hostname?: string,
    service?: string,
  ) => void,
): GetNameInfoReqWrap {
  if (arguments.length !== 3) {
    throw new ERR_MISSING_ARGS("address", "port", "callback");
  }

  if (isIP(address) === 0) {
    throw new ERR_INVALID_ARG_VALUE("address", address);
  }

  port = validatePort(port);

  validateFunction(callback, "callback");

  const req = new GetNameInfoReqWrap();
  req.callback = callback;
  req.address = address;
  req.port = port;
  req.oncomplete = onlookupservice;

  const errCode = cares.getnameinfo(req, address, port);
  if (errCode) {
    throw dnsException(errCode, "getnameinfo", address);
  }

  return req;
}

ObjectDefineProperty(lookupService, customPromisifyArgs, {
  __proto__: null,
  value: ["hostname", "service"],
  enumerable: false,
});

function onresolve(
  this: QueryReqWrap,
  err: number,
  records: Records,
  ttls?: number[],
) {
  if (err) {
    this.callback(dnsException(err, this.bindingName, this.hostname));

    return;
  }

  const parsedRecords = ttls && this.ttl
    ? ArrayPrototypeMap(
      records as string[],
      (address: string, index: number) => ({
        address,
        ttl: ttls[index],
      }),
    )
    : records;

  this.callback(null, parsedRecords);
}

function resolver(bindingName: string) {
  function query(
    this: Resolver,
    name: string,
    options: unknown,
    callback?: unknown,
  ): QueryReqWrap {
    if (isResolveCallback(options)) {
      callback = options;
      options = {};
    }

    validateString(name, "name");
    validateFunction(callback, "callback");

    const req = new QueryReqWrap();
    req.bindingName = bindingName;
    req.callback = callback as ResolveCallback;
    req.hostname = name;
    req.oncomplete = onresolve;
    req.ttl = !!(options && (options as ResolveOptions).ttl);

    const err = this._handle[bindingName](req, domainToASCII(name));

    if (err) {
      throw dnsException(err, bindingName, name);
    }

    return req;
  }

  ObjectDefineProperty(query, "name", {
    __proto__: null,
    value: bindingName,
  });

  return query;
}

const resolveMap = ObjectCreate(null);

class Resolver extends CallbackResolver {
  constructor(options?: ResolverOptions) {
    super(options);
  }
}

Resolver.prototype.resolveAny = resolveMap.ANY = resolver("queryAny");
Resolver.prototype.resolve4 = resolveMap.A = resolver("queryA");
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver("queryAaaa");
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver("queryCaa");
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver("queryCname");
Resolver.prototype.resolveMx = resolveMap.MX = resolver("queryMx");
Resolver.prototype.resolveNs = resolveMap.NS = resolver("queryNs");
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver("queryTxt");
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver("querySrv");
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver("queryPtr");
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver("queryNaptr");
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver("querySoa");
Resolver.prototype.reverse = resolver("getHostByAddr");
Resolver.prototype.resolve = _resolve;

function _resolve(
  this: Resolver,
  hostname: string,
  rrtype: unknown,
  callback?: unknown,
): QueryReqWrap {
  let resolver: Resolver;

  if (typeof hostname !== "string") {
    throw new ERR_INVALID_ARG_TYPE("name", "string", hostname);
  }

  if (typeof rrtype === "string") {
    resolver = resolveMap[rrtype];
  } else if (typeof rrtype === "function") {
    resolver = resolveMap.A;
    callback = rrtype;
  } else {
    throw new ERR_INVALID_ARG_TYPE("rrtype", "string", rrtype);
  }

  if (typeof resolver === "function") {
    return ReflectApply(resolver, this, [hostname, callback]);
  }

  throw new ERR_INVALID_ARG_VALUE("rrtype", rrtype);
}

/**
 * Sets the IP address and port of servers to be used when performing DNS
 * resolution. The `servers` argument is an array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted
 * addresses. If the port is the IANA default DNS port (53) it can be omitted.
 *
 * ```js
 * dns.setServers([
 *   '4.4.4.4',
 *   '[2001:4860:4860::8888]',
 *   '4.4.4.4:1053',
 *   '[2001:4860:4860::8888]:1053',
 * ]);
 * ```
 *
 * An error will be thrown if an invalid address is provided.
 *
 * The `dns.setServers()` method must not be called while a DNS query is in
 * progress.
 *
 * The `setServers` method affects only `resolve`,`dns.resolve*()` and `reverse` (and specifically _not_ `lookup`).
 *
 * This method works much like [resolve.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html).
 * That is, if attempting to resolve with the first server provided results in a
 * `NOTFOUND` error, the `resolve()` method will _not_ attempt to resolve with
 * subsequent servers provided. Fallback DNS servers will only be used if the
 * earlier ones time out or result in some other error.
 *
 * @param servers array of `RFC 5952` formatted addresses
 */
function setServers(servers: ReadonlyArray<string>) {
  const resolver = new Resolver();

  resolver.setServers(servers);
  setDefaultResolver(resolver);
}

// The Node implementation uses `bindDefaultResolver` to set the follow methods
// on `module.exports` bound to the current `defaultResolver`. We don't have
// the same ability in ESM but can simulate this (at some cost) by explicitly
// exporting these methods which dynamically bind to the default resolver when
// called.

/**
 * Returns an array of IP address strings, formatted according to [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6),
 * that are currently configured for DNS resolution. A string will include a port
 * section if a custom port is used.
 *
 * ```js
 * [
 *   '4.4.4.4',
 *   '2001:4860:4860::8888',
 *   '4.4.4.4:1053',
 *   '[2001:4860:4860::8888]:1053',
 * ]
 * ```
 */
function getServers(): string[] {
  return ReflectApply(
    Resolver.prototype.getServers,
    getDefaultResolver(),
    [],
  );
}

/**
 * Uses the DNS protocol to resolve all records (also known as `ANY` or `*` query).
 * The `ret` argument passed to the `callback` function will be an array containing
 * various types of records. Each object has a property `type` that indicates the
 * type of the current record. And depending on the `type`, additional properties
 * will be present on the object.
 *
 * Here is an example of the `ret` object passed to the callback:
 *
 * ```js
 * [ { type: 'A', address: '127.0.0.1', ttl: 299 },
 *   { type: 'CNAME', value: 'example.com' },
 *   { type: 'MX', exchange: 'alt4.aspmx.l.example.com', priority: 50 },
 *   { type: 'NS', value: 'ns1.example.com' },
 *   { type: 'TXT', entries: [ 'v=spf1 include:_spf.example.com ~all' ] },
 *   { type: 'SOA',
 *     nsname: 'ns1.example.com',
 *     hostmaster: 'admin.example.com',
 *     serial: 156696742,
 *     refresh: 900,
 *     retry: 900,
 *     expire: 1800,
 *     minttl: 60 } ]
 * ```
 *
 * DNS server operators may choose not to respond to `ANY` queries. It may be
 * better to call individual methods like `resolve4`, `resolveMx`, and so on.
 * For more details, see [RFC 8482](https://tools.ietf.org/html/rfc8482).
 */
function resolveAny(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: AnyRecord[]) => void,
): QueryReqWrap;
function resolveAny(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveAny,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve a IPv4 addresses (`A` records) for the
 * `hostname`. The `addresses` argument passed to the `callback` function will
 * contain an array of IPv4 addresses (e.g. `['74.125.79.104', '74.125.79.105','74.125.79.106']`).
 *
 * @param hostname Host name to resolve.
 */
function resolve4(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): void;
function resolve4(
  hostname: string,
  options: ResolveWithTtlOptions,
  callback: (err: ErrnoException | null, addresses: RecordWithTtl[]) => void,
): void;
function resolve4(
  hostname: string,
  options: ResolveOptions,
  callback: (
    err: ErrnoException | null,
    addresses: string[] | RecordWithTtl[],
  ) => void,
): void;
function resolve4(
  hostname: string,
  options: unknown,
  callback?: unknown,
) {
  return ReflectApply(
    Resolver.prototype.resolve4,
    getDefaultResolver() as Resolver,
    [hostname, options, callback],
  );
}

/**
 * Uses the DNS protocol to resolve a IPv6 addresses (`AAAA` records) for the
 * `hostname`. The `addresses` argument passed to the `callback` function
 * will contain an array of IPv6 addresses.
 *
 * @param hostname Host name to resolve.
 */
function resolve6(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): void;
function resolve6(
  hostname: string,
  options: ResolveWithTtlOptions,
  callback: (err: ErrnoException | null, addresses: RecordWithTtl[]) => void,
): void;
function resolve6(
  hostname: string,
  options: ResolveOptions,
  callback: (
    err: ErrnoException | null,
    addresses: string[] | RecordWithTtl[],
  ) => void,
): void;
function resolve6(
  hostname: string,
  options: unknown,
  callback?: unknown,
) {
  return ReflectApply(
    Resolver.prototype.resolve6,
    getDefaultResolver() as Resolver,
    [hostname, options, callback],
  );
}

/**
 * Uses the DNS protocol to resolve `CAA` records for the `hostname`. The
 * `addresses` argument passed to the `callback` function will contain an array
 * of certification authority authorization records available for the
 * `hostname` (e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`).
 */
function resolveCaa(
  hostname: string,
  callback: (err: ErrnoException | null, records: CaaRecord[]) => void,
): QueryReqWrap;
function resolveCaa(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveCaa,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve `CNAME` records for the `hostname`. The
 * `addresses` argument passed to the `callback` function will contain an array
 * of canonical name records available for the `hostname`(e.g. `['bar.example.com']`).
 */
function resolveCname(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolveCname(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveCname,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve mail exchange records (`MX` records) for the
 * `hostname`. The `addresses` argument passed to the `callback` function will
 * contain an array of objects containing both a `priority` and `exchange`
 * property (e.g. `[{priority: 10, exchange: 'mx.example.com'}, ...]`).
 */
function resolveMx(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: MxRecord[]) => void,
): QueryReqWrap;
function resolveMx(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveMx,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve name server records (`NS` records) for the
 * `hostname`. The `addresses` argument passed to the `callback` function will
 * contain an array of name server records available for `hostname`
 * (e.g. `['ns1.example.com', 'ns2.example.com']`).
 */
function resolveNs(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolveNs(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveNs,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve text queries (`TXT` records) for the
 * `hostname`. The `records` argument passed to the `callback` function is a
 * two-dimensional array of the text records available for `hostname`
 * (e.g.`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT
 * chunks of one record. Depending on the use case, these could be either
 * joined together or treated separately.
 */
function resolveTxt(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[][]) => void,
): QueryReqWrap;
function resolveTxt(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveTxt,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve service records (`SRV` records) for the
 * `hostname`. The `addresses` argument passed to the `callback` function will
 * be an array of objects with the following properties:
 *
 * - `priority`
 * - `weight`
 * - `port`
 * - `name`
 *
 * ```js
 * {
 *   priority: 10,
 *   weight: 5,
 *   port: 21223,
 *   name: 'service.example.com'
 * }
 * ```
 */
function resolveSrv(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: SrvRecord[]) => void,
): QueryReqWrap;
function resolveSrv(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveSrv,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve pointer records (`PTR` records) for the
 * `hostname`. The `addresses` argument passed to the `callback` function will
 * be an array of strings containing the reply records.
 */
function resolvePtr(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolvePtr(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolvePtr,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve regular expression based records (`NAPTR`
 * records) for the `hostname`. The `addresses` argument passed to the
 * `callback` function will contain an array of objects with the following
 * properties:
 *
 * - `flags`
 * - `service`
 * - `regexp`
 * - `replacement`
 * - `order`
 * - `preference`
 *
 * ```js
 * {
 *   flags: 's',
 *   service: 'SIP+D2U',
 *   regexp: '',
 *   replacement: '_sip._udp.example.com',
 *   order: 30,
 *   preference: 100
 * }
 * ```
 */
function resolveNaptr(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: NaptrRecord[]) => void,
): QueryReqWrap;
function resolveNaptr(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveNaptr,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve a start of authority record (`SOA` record) for
 * the `hostname`. The `address` argument passed to the `callback` function will
 * be an object with the following properties:
 *
 * - `nsname`
 * - `hostmaster`
 * - `serial`
 * - `refresh`
 * - `retry`
 * - `expire`
 * - `minttl`
 *
 * ```js
 * {
 *   nsname: 'ns.example.com',
 *   hostmaster: 'root.example.com',
 *   serial: 2013101809,
 *   refresh: 10000,
 *   retry: 2400,
 *   expire: 604800,
 *   minttl: 3600
 * }
 * ```
 */
function resolveSoa(
  hostname: string,
  callback: (err: ErrnoException | null, address: SoaRecord) => void,
): QueryReqWrap;
function resolveSoa(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.resolveSoa,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Performs a reverse DNS query that resolves an IPv4 or IPv6 address to an
 * array of host names.
 *
 * On error, `err` is an `Error` object, where `err.code` is
 * one of the `DNS error codes`.
 */
function reverse(
  ip: string,
  callback: (err: ErrnoException | null, hostnames: string[]) => void,
): QueryReqWrap;
function reverse(...args: unknown[]): QueryReqWrap {
  return ReflectApply(
    Resolver.prototype.reverse,
    getDefaultResolver() as Resolver,
    args,
  );
}

/**
 * Uses the DNS protocol to resolve a host name (e.g. `'nodejs.org'`) into an array
 * of the resource records. The `callback` function has arguments`(err, records)`.]
 * When successful, `records` will be an array of resource
 * records. The type and structure of individual results varies based on `rrtype`.
 *
 * On error, `err` is an `Error` object, where `err.code` is one of the DNS error codes.
 *
 * @param hostname Host name to resolve.
 * @param [rrtype='A'] Resource record type.
 */
function resolve(
  hostname: string,
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "A",
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "AAAA",
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "ANY",
  callback: (err: ErrnoException | null, addresses: AnyRecord[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "CNAME",
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "MX",
  callback: (err: ErrnoException | null, addresses: MxRecord[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "NAPTR",
  callback: (err: ErrnoException | null, addresses: NaptrRecord[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "NS",
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "PTR",
  callback: (err: ErrnoException | null, addresses: string[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "SOA",
  callback: (err: ErrnoException | null, addresses: SoaRecord) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "SRV",
  callback: (err: ErrnoException | null, addresses: SrvRecord[]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: "TXT",
  callback: (err: ErrnoException | null, addresses: string[][]) => void,
): QueryReqWrap;
function resolve(
  hostname: string,
  rrtype: string,
  callback: (
    err: ErrnoException | null,
    addresses:
      | string[]
      | MxRecord[]
      | NaptrRecord[]
      | SoaRecord
      | SrvRecord[]
      | string[][]
      | AnyRecord[],
  ) => void,
): QueryReqWrap;
function resolve(hostname: string, rrtype: unknown, callback?: unknown) {
  return ReflectApply(
    Resolver.prototype.resolve,
    getDefaultResolver() as Resolver,
    [hostname, rrtype, callback],
  );
}

// ERROR CODES
const NODATA = "ENODATA";
const FORMERR = "EFORMERR";
const SERVFAIL = "ESERVFAIL";
const NOTFOUND = "ENOTFOUND";
const NOTIMP = "ENOTIMP";
const REFUSED = "EREFUSED";
const BADQUERY = "EBADQUERY";
const BADNAME = "EBADNAME";
const BADFAMILY = "EBADFAMILY";
const BADRESP = "EBADRESP";
const CONNREFUSED = "ECONNREFUSED";
const TIMEOUT = "ETIMEOUT";
const EOF = "EOF";
const FILE = "EFILE";
const NOMEM = "ENOMEM";
const DESTRUCTION = "EDESTRUCTION";
const BADSTR = "EBADSTR";
const BADFLAGS = "EBADFLAGS";
const NONAME = "ENONAME";
const BADHINTS = "EBADHINTS";
const NOTINITIALIZED = "ENOTINITIALIZED";
const LOADIPHLPAPI = "ELOADIPHLPAPI";
const ADDRGETNETWORKPARAMS = "EADDRGETNETWORKPARAMS";
const CANCELLED = "ECANCELLED";

const promises = {
  ...promisesBase,
  getDefaultResultOrder: getDefaultDnsOrder,
  setDefaultResultOrder,
  setServers,

  // ERROR CODES
  NODATA,
  FORMERR,
  SERVFAIL,
  NOTFOUND,
  NOTIMP,
  REFUSED,
  BADQUERY,
  BADNAME,
  BADFAMILY,
  BADRESP,
  CONNREFUSED,
  TIMEOUT,
  EOF,
  FILE,
  NOMEM,
  DESTRUCTION,
  BADSTR,
  BADFLAGS,
  NONAME,
  BADHINTS,
  NOTINITIALIZED,
  LOADIPHLPAPI,
  ADDRGETNETWORKPARAMS,
  CANCELLED,
};

const defaultExport = {
  ADDRCONFIG,
  ALL,
  V4MAPPED,
  lookup,
  lookupService,
  getServers,
  resolveAny,
  resolve4,
  resolve6,
  resolveCaa,
  resolveCname,
  resolveMx,
  resolveNs,
  resolveTxt,
  resolveSrv,
  resolvePtr,
  resolveNaptr,
  resolveSoa,
  resolve,
  Resolver,
  reverse,
  setServers,
  getDefaultResultOrder: getDefaultDnsOrder,
  setDefaultResultOrder,
  promises,
  NODATA,
  FORMERR,
  SERVFAIL,
  NOTFOUND,
  NOTIMP,
  REFUSED,
  BADQUERY,
  BADNAME,
  BADFAMILY,
  BADRESP,
  CONNREFUSED,
  TIMEOUT,
  EOF,
  FILE,
  NOMEM,
  DESTRUCTION,
  BADSTR,
  BADFLAGS,
  NONAME,
  BADHINTS,
  NOTINITIALIZED,
  LOADIPHLPAPI,
  ADDRGETNETWORKPARAMS,
  CANCELLED,
};

return {
  default: defaultExport,
  ADDRCONFIG,
  ALL,
  V4MAPPED,
  lookup,
  lookupService,
  getServers,
  resolveAny,
  resolve4,
  resolve6,
  resolveCaa,
  resolveCname,
  resolveMx,
  resolveNs,
  resolveTxt,
  resolveSrv,
  resolvePtr,
  resolveNaptr,
  resolveSoa,
  resolve,
  Resolver,
  reverse,
  setServers,
  getDefaultResultOrder: getDefaultDnsOrder,
  setDefaultResultOrder,
  promises,
  NODATA,
  FORMERR,
  SERVFAIL,
  NOTFOUND,
  NOTIMP,
  REFUSED,
  BADQUERY,
  BADNAME,
  BADFAMILY,
  BADRESP,
  CONNREFUSED,
  TIMEOUT,
  EOF,
  FILE,
  NOMEM,
  DESTRUCTION,
  BADSTR,
  BADFLAGS,
  NONAME,
  BADHINTS,
  NOTINITIALIZED,
  LOADIPHLPAPI,
  ADDRGETNETWORKPARAMS,
  CANCELLED,
};
})();
