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 { DnsRecordType } from "#codec/DnsCodec.js";
import { Diagnostic } from "#log/Diagnostic.js";
import { ServerAddressSet } from "#net/ServerAddressSet.js";
import { Time } from "#time/Time.js";
import { Abort } from "#util/Abort.js";
import { AsyncObservable, AsyncObservableValue, ObserverGroup } from "#util/Observable.js";
import { IpServiceStatus } from "./IpServiceStatus.js";
class IpService {
  #name;
  #via;
  #names;
  #observers = new ObserverGroup(this);
  #services = /* @__PURE__ */ new Map();
  #changed = new AsyncObservable();
  #addresses = ServerAddressSet();
  #status = new IpServiceStatus(this);
  #notified;
  constructor(name, via, names) {
    this.#name = names.get(name);
    this.#names = names;
    this.#via = Diagnostic.via(via);
    this.#observers.on(this.#name, this.#onServiceChanged);
    for (const record of this.#name.records) {
      const service = serviceOf(record);
      if (!service) {
        continue;
      }
      this.#updateService(record.ttl, service);
    }
  }
  /**
   * The DNS-SD name.
   */
  get name() {
    return this.#name;
  }
  /**
   * Other DNS-SD names.
   */
  get names() {
    return this.#names;
  }
  /**
   * Identifier used for logging.
   */
  get via() {
    return this.#via;
  }
  /**
   * Status details of the service.
   */
  get status() {
    return this.#status;
  }
  /**
   * Release resources.
   */
  async close() {
    this.#observers.close();
    await this.#status.close();
    if (this.#notified) {
      await this.#notified;
    }
  }
  /**
   * Known addresses.
   */
  get addresses() {
    return this.#addresses;
  }
  /**
   * Values from TXT records.
   */
  get parameters() {
    return this.#name.parameters;
  }
  /**
   * Emits when the service changes.
   */
  get changed() {
    return this.#changed;
  }
  map(fn) {
    return [...this.addresses].map(fn);
  }
  /**
   * Stream address updates, starting with initial set of addresses.
   *
   * Outputs addresses in priority order so the stream may be used directly for establishing new connections.
   *
   * If no addresses are present, triggers discovery using standard MDNS backoff schedule.
   */
  async *addressChanges({
    abort,
    order = ServerAddressSet.compareDesirability
  } = {}) {
    var _stack = [];
    try {
      let knownAddresses = /* @__PURE__ */ new Set();
      const dirty = new AsyncObservableValue();
      const _changed = __using(_stack, this.changed.use(() => dirty.emit(true)));
      loop: while (true) {
        dirty.emit(false);
        const addresses = ServerAddressSet(this.addresses, order);
        let changes = new Array();
        const oldKnownAddresses = knownAddresses;
        knownAddresses = /* @__PURE__ */ new Set();
        for (const address of addresses) {
          knownAddresses.add(address);
          if (oldKnownAddresses.has(address)) {
            oldKnownAddresses.delete(address);
            continue;
          }
          changes.push({ kind: "add", address });
        }
        if (oldKnownAddresses.size) {
          const deletedAddresses = [...oldKnownAddresses.values()];
          const deletes = deletedAddresses.map((address) => ({ kind: "delete", address }));
          changes = [...deletes, ...changes];
        }
        for (const change of changes) {
          yield change;
          if (Abort.is(abort)) {
            return;
          }
          if (dirty.value) {
            continue loop;
          }
        }
        await Abort.race(abort, dirty);
        if (Abort.is(abort)) {
          return;
        }
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  #onServiceChanged = async ({ updated, deleted }) => {
    if (updated) {
      for (const record of updated) {
        const service = serviceOf(record);
        if (service) {
          this.#updateService(record.ttl, service);
        }
      }
    }
    if (deleted) {
      for (const record of deleted) {
        const service = serviceOf(record);
        if (service) {
          this.#deleteService(service);
        }
      }
    }
  };
  #updateService(ttl, { target, port, priority, weight }) {
    const key = hostKeyOf(target, port);
    let service = this.#services.get(key);
    if (service) {
      service.discoveredAt = Time.nowMs;
      service.ttl = ttl;
      service.port = port;
      service.priority = priority;
      service.weight = weight;
      return;
    }
    service = {
      name: this.#names.get(target),
      discoveredAt: Time.nowMs,
      ttl,
      port,
      priority,
      weight,
      onChange: (changes) => this.#onAddressChanged(service, changes)
    };
    this.#observers.on(service.name, service.onChange);
    this.#onAddressChanged(service, { name: service.name, updated: [...service.name.records] });
    this.#services.set(key, service);
  }
  #deleteService({ target, port }) {
    const key = hostKeyOf(target, port);
    const service = this.#services.get(key);
    if (!service) {
      return;
    }
    this.#services.delete(key);
    this.#observers.off(service.name, service.onChange);
    this.#onAddressChanged(service, { name: service.name, deleted: [...service.name.records] });
    return;
  }
  #onAddressChanged = (service, { updated, deleted }) => {
    if (updated) {
      for (const record of updated) {
        const addr = addressOf(record);
        if (addr) {
          this.#updateAddress(service, addr);
        }
      }
    }
    if (deleted) {
      for (const record of deleted) {
        const addr = addressOf(record);
        if (addr) {
          this.#deleteAddress(service, addr);
        }
      }
    }
  };
  #updateAddress(service, ip) {
    const address = { type: "udp", ip, port: service.port };
    if (this.#addresses.has(address)) {
      return;
    }
    this.#addresses.add(address);
    this.#status.isReachable = true;
    this.#notify();
  }
  #deleteAddress(service, ip) {
    const address = { type: "udp", ip, port: service.port };
    if (!this.#addresses.has(address)) {
      return;
    }
    this.#addresses.delete(address);
    this.#notify();
  }
  #notify = () => {
    if (this.#notified) {
      return;
    }
    this.#notified = this.#emitNotification();
  };
  async #emitNotification() {
    await Time.sleep("discovery service coalescence", 0);
    this.#notified = void 0;
    await this.changed.emit();
  }
}
function serviceOf(record) {
  if (record.recordType !== DnsRecordType.SRV) {
    return;
  }
  return record.value;
}
function hostKeyOf(name, port) {
  return `${name}:${port}`;
}
function addressOf(record) {
  if (record.recordType !== DnsRecordType.A && record.recordType !== DnsRecordType.AAAA) {
    return;
  }
  return record.value;
}
export {
  IpService
};
//# sourceMappingURL=IpService.js.map
