"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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();
};
var IpService_exports = {};
__export(IpService_exports, {
  IpService: () => IpService
});
module.exports = __toCommonJS(IpService_exports);
var import_DnsCodec = require("#codec/DnsCodec.js");
var import_Diagnostic = require("#log/Diagnostic.js");
var import_ServerAddress = require("#net/ServerAddress.js");
var import_ServerAddressSet = require("#net/ServerAddressSet.js");
var import_Time = require("#time/Time.js");
var import_Abort = require("#util/Abort.js");
var import_Observable = require("#util/Observable.js");
var import_IpServiceStatus = require("./IpServiceStatus.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
class IpService {
  #name;
  #via;
  #names;
  #observers = new import_Observable.ObserverGroup(this);
  #services = /* @__PURE__ */ new Map();
  #changed = new import_Observable.AsyncObservable();
  #addresses = (0, import_ServerAddressSet.ServerAddressSet)();
  #status = new import_IpServiceStatus.IpServiceStatus(this);
  #notified;
  constructor(name, via, names) {
    this.#name = names.get(name);
    this.#names = names;
    this.#via = import_Diagnostic.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 = import_ServerAddressSet.ServerAddressSet.compareDesirability
  } = {}) {
    var _stack = [];
    try {
      let knownAddresses = /* @__PURE__ */ new Set();
      const dirty = new import_Observable.AsyncObservableValue();
      const _changed = __using(_stack, this.changed.use(() => dirty.emit(true)));
      loop: while (true) {
        dirty.emit(false);
        const addresses = (0, import_ServerAddressSet.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 (import_Abort.Abort.is(abort)) {
            return;
          }
          if (dirty.value) {
            continue loop;
          }
        }
        await import_Abort.Abort.race(abort, dirty);
        if (import_Abort.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 = import_Time.Time.nowMs;
      service.ttl = ttl;
      service.port = port;
      service.priority = priority;
      service.weight = weight;
      return;
    }
    service = {
      name: this.#names.get(target),
      discoveredAt: import_Time.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 ip = ipOf(record);
        if (ip !== void 0) {
          this.#updateAddress(service, ip);
        }
      }
    }
    if (deleted) {
      for (const record of deleted) {
        const ip = ipOf(record);
        if (ip !== void 0) {
          this.#deleteAddress(service, ip);
        }
      }
    }
  };
  #updateAddress(service, ip) {
    const address = { 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 = { 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 import_Time.Time.sleep("discovery service coalescence", 0);
    this.#notified = void 0;
    await this.changed.emit();
  }
}
function serviceOf(record) {
  if (record.recordType !== import_DnsCodec.DnsRecordType.SRV) {
    return;
  }
  return record.value;
}
function hostKeyOf(name, port) {
  return `${name}:${port}`;
}
function ipOf(record) {
  if (record.recordType === import_DnsCodec.DnsRecordType.A) {
    return record.value;
  }
  if (record.recordType === import_DnsCodec.DnsRecordType.AAAA) {
    if (record.sourceIntf !== void 0 && import_ServerAddress.ServerAddress.isIpv6LinkLocal(record.value) && !record.value.includes("%")) {
      return `${record.value}%${record.sourceIntf}`;
    }
    return record.value;
  }
  return void 0;
}
//# sourceMappingURL=IpService.js.map
