var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Time } from "#time/Time.js";
import { Timestamp } from "#time/Timestamp.js";
import { Seconds } from "#time/TimeUnit.js";
import { Lifetime } from "#util/Lifetime.js";
import { Observable, ObserverGroup } from "#util/Observable.js";
import { Scheduler } from "#util/Scheduler.js";
import { DnssdName } from "./DnssdName.js";
import { QueryMulticaster } from "./DnssdSolicitor.js";
class DnssdNames {
  #socket;
  #lifetime;
  #entropy;
  #filter;
  #solicitor;
  #observers = new ObserverGroup();
  #names = /* @__PURE__ */ new Map();
  #expiration;
  #discovered = new Observable();
  #goodbyeProtectionWindow;
  #minTtl;
  constructor({
    socket,
    lifetime = Lifetime.process,
    entropy,
    filter,
    goodbyeProtectionWindow,
    minTtl
  }) {
    this.#socket = socket;
    this.#lifetime = lifetime.join("mdns names");
    this.#entropy = entropy;
    this.#filter = filter;
    this.#solicitor = new QueryMulticaster(this);
    this.#goodbyeProtectionWindow = goodbyeProtectionWindow ?? DnssdNames.defaults.goodbyeProtectionWindow;
    this.#minTtl = minTtl ?? DnssdNames.defaults.minTtl;
    this.#observers.on(this.#socket.receipt, this.#handleMessage.bind(this));
    this.#expiration = new Scheduler({
      name: "expiration scheduler",
      lifetime: this.#lifetime,
      timeOf: (a) => {
        return a.expiresAt;
      },
      run: (record) => {
        const discoveryName = this.maybeGet(record.name);
        if (discoveryName) {
          discoveryName.deleteRecord(record);
        }
      }
    });
  }
  #handleMessage(message) {
    const records = [...message.answers, ...message.additionalRecords];
    const filtered = new Set(records);
    let goodbyesBefore;
    const handleRecord = (record) => {
      filtered.delete(record);
      const name = this.get(record.name);
      if (record.ttl) {
        if (record.ttl < this.#minTtl) {
          record = { ...record, ttl: this.#minTtl };
        }
        const wasDiscovered = name.isDiscovered;
        name.installRecord(record);
        if (!wasDiscovered && name.isDiscovered) {
          this.#discovered.emit(name);
        }
      } else {
        if (goodbyesBefore === void 0) {
          goodbyesBefore = Timestamp(Time.nowMs - this.#goodbyeProtectionWindow);
        }
        name.deleteRecord(record, goodbyesBefore);
      }
    };
    for (const record of records) {
      if (this.#filter && !this.#filter(record)) {
        continue;
      }
      handleRecord(record);
    }
    let filteredBeforePass = records.length;
    while (filteredBeforePass > filtered.size) {
      filteredBeforePass = filtered.size;
      for (const record of filtered) {
        if (!this.has(record.name)) {
          continue;
        }
        handleRecord(record);
      }
    }
  }
  /**
   * Test for existence of name.
   */
  has(name) {
    return this.#names.has(name.toLowerCase());
  }
  /**
   * Retrieve the {@link DnssdName} for {@link name}.
   *
   * This will create the name if it does not exist, and if you do not add an observer then it will not automatically
   * delete if there are no records.  So if you may not use the record test for existence with {@link has} first.
   */
  get(qname) {
    let name = this.maybeGet(qname);
    if (name === void 0) {
      name = new DnssdName(qname, this.#nameContext);
      this.#names.set(qname.toLowerCase(), name);
    }
    return name;
  }
  /**
   * Retrieve the {@link DnssdName} if known.
   */
  maybeGet(name) {
    return this.#names.get(name.toLowerCase());
  }
  #delete(name) {
    this.#names.delete(name.qname.toLowerCase());
  }
  /**
   * Wait for all workers and close all names.
   */
  async close() {
    var _stack = [];
    try {
      const _closing = __using(_stack, this.#lifetime.closing());
      this.#observers.close();
      await this.#expiration.close();
      for (const name of this.#names.values()) {
        await name.close();
        this.#delete(name);
      }
      await this.#solicitor.close();
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  get socket() {
    return this.#socket;
  }
  /**
   * Emits when a {@link DnssdName} is first discovered.
   */
  get discovered() {
    return this.#discovered;
  }
  /**
   * Shared solicitor.
   *
   * We offer solicitation in this object so there is not redundant solicitation across interested parties.
   */
  get solicitor() {
    return this.#solicitor;
  }
  get entropy() {
    return this.#entropy;
  }
  #nameContext = {
    delete: (name) => {
      const known = this.maybeGet(name.qname);
      if (known === name) {
        this.#delete(name);
      }
    },
    registerForExpiration: (record) => {
      this.#expiration.add(record);
    },
    unregisterForExpiration: (record) => {
      this.#expiration.delete(record);
    },
    get: (qname) => {
      return this.get(qname);
    }
  };
}
((DnssdNames2) => {
  DnssdNames2.defaults = {
    goodbyeProtectionWindow: Seconds(1),
    minTtl: Seconds(15)
    // This is the value that Apple uses
  };
})(DnssdNames || (DnssdNames = {}));
export {
  DnssdNames
};
//# sourceMappingURL=DnssdNames.js.map
