/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
const BIGINT_MARKER = "__BIGINT__";
function toBigIntAwareJson(value, spaces) {
  const replacements = new Array();
  let result = JSON.stringify(value, (_key, val) => {
    if (typeof val === "bigint") {
      if (val > Number.MAX_SAFE_INTEGER) {
        replacements.push({
          from: `"0x${val.toString(16)}"`,
          to: val.toString()
        });
        return `0x${val.toString(16)}`;
      } else {
        return Number(val);
      }
    }
    return val;
  }, spaces);
  if (replacements.length > 0) {
    replacements.forEach(({
      from,
      to
    }) => {
      result = result.replaceAll(from, to);
    });
  }
  return result;
}
function parseBigIntAwareJson(json) {
  const result = [];
  let i = 0;
  let inString = false;
  while (i < json.length) {
    const char = json[i];
    if (inString) {
      if (char === "\\") {
        result.push(char);
        i++;
        if (i < json.length) {
          result.push(json[i]);
          i++;
        }
      } else if (char === '"') {
        result.push(char);
        inString = false;
        i++;
      } else {
        result.push(char);
        i++;
      }
    } else {
      if (char === '"') {
        result.push(char);
        inString = true;
        i++;
      } else if (char >= "0" && char <= "9") {
        const hasMinus = result.length > 0 && result[result.length - 1] === "-";
        if (hasMinus) {
          result.pop();
        }
        const start = i;
        while (i < json.length && json[i] >= "0" && json[i] <= "9") {
          i++;
        }
        let isFloat = false;
        if (i < json.length && json[i] === ".") {
          isFloat = true;
          i++;
          while (i < json.length && json[i] >= "0" && json[i] <= "9") {
            i++;
          }
        }
        if (i < json.length && (json[i] === "e" || json[i] === "E")) {
          isFloat = true;
          i++;
          if (i < json.length && (json[i] === "+" || json[i] === "-")) {
            i++;
          }
          while (i < json.length && json[i] >= "0" && json[i] <= "9") {
            i++;
          }
        }
        const numberStr = (hasMinus ? "-" : "") + json.slice(start, i);
        if (!isFloat && numberStr.length - (hasMinus ? 1 : 0) >= 15) {
          const num = BigInt(numberStr);
          if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
            result.push(`"${BIGINT_MARKER}${numberStr}"`);
          } else {
            result.push(numberStr);
          }
        } else {
          result.push(numberStr);
        }
      } else {
        result.push(char);
        i++;
      }
    }
  }
  const processed = result.join("");
  return JSON.parse(processed, (_key, value) => {
    if (typeof value === "string" && value.startsWith(BIGINT_MARKER)) {
      return BigInt(value.slice(BIGINT_MARKER.length));
    }
    return value;
  });
}

/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
class Connection {
  /**
   * Create a new connection.
   * @param ws_server_url WebSocket server URL
   * @param wsFactory Optional factory function to create WebSocket instances.
   *                  If not provided, uses the global WebSocket constructor.
   */
  constructor(ws_server_url, wsFactory) {
    this.ws_server_url = ws_server_url;
    this.ws_server_url = ws_server_url;
    this.wsFactory = wsFactory ?? (url => new WebSocket(url));
  }
  serverInfo = void 0;
  socket;
  wsFactory;
  get connected() {
    var _this$socket;
    return ((_this$socket = this.socket) === null || _this$socket === void 0 ? void 0 : _this$socket.readyState) === 1;
  }
  async connect(onMessage, onConnectionLost) {
    if (this.socket) {
      throw new Error("Already connected");
    }
    console.debug("Trying to connect");
    return new Promise((resolve, reject) => {
      this.socket = this.wsFactory(this.ws_server_url);
      this.socket.onopen = () => {
        console.log("WebSocket Connected");
      };
      this.socket.onclose = () => {
        console.log("WebSocket Closed");
        this.socket = void 0;
        this.serverInfo = void 0;
        onConnectionLost();
      };
      this.socket.onerror = error => {
        console.error("WebSocket Error: ", error);
        reject(new Error("WebSocket Error"));
      };
      this.socket.onmessage = event => {
        const dataStr = typeof event.data === "string" ? event.data : String(event.data);
        const data = parseBigIntAwareJson(dataStr);
        console.log("WebSocket OnMessage", data);
        if (!this.serverInfo) {
          this.serverInfo = data;
          resolve();
          return;
        }
        onMessage(data);
      };
    });
  }
  disconnect() {
    if (this.socket) {
      this.socket.close();
      this.socket = void 0;
    }
    this.serverInfo = void 0;
  }
  sendMessage(message) {
    if (!this.socket) {
      throw new Error("Not connected");
    }
    console.log("WebSocket send message", message);
    this.socket.send(toBigIntAwareJson(message));
  }
}

/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
class MatterError extends Error {}
class InvalidServerVersion extends MatterError {}
class CommandTimeoutError extends MatterError {
  constructor(command, timeoutMs) {
    super(`Command '${command}' timed out after ${timeoutMs}ms`);
    this.command = command;
    this.timeoutMs = timeoutMs;
    this.name = "CommandTimeoutError";
  }
}
class ConnectionClosedError extends MatterError {
  constructor(message = "Connection closed while command was pending") {
    super(message);
    this.name = "ConnectionClosedError";
  }
}

/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
class MatterNode {
  constructor(data) {
    this.data = data;
    this.node_id = data.node_id;
    this.date_commissioned = data.date_commissioned;
    this.last_interview = data.last_interview;
    this.interview_version = data.interview_version;
    this.available = data.available;
    this.is_bridge = data.is_bridge;
    this.attributes = data.attributes;
    this.attribute_subscriptions = data.attribute_subscriptions;
    this.matter_version = data.matter_version;
  }
  node_id;
  date_commissioned;
  last_interview;
  interview_version;
  available;
  is_bridge;
  attributes;
  /** Attribute subscriptions (always empty array in current protocol, matches Python Matter Server) */
  attribute_subscriptions;
  /**
   * Matter specification version of the node (e.g., "1.2.0", "1.3.0", "1.4.0").
   * Optional - not available in Python Matter Server.
   */
  matter_version;
  get nodeLabel() {
    const label = this.attributes["0/40/5"];
    if (typeof label !== "string") return "";
    if (label.includes("\0\0")) return "";
    return label.trim();
  }
  get vendorName() {
    const value = this.attributes["0/40/1"];
    return typeof value === "string" ? value : "";
  }
  get productName() {
    const value = this.attributes["0/40/3"];
    return typeof value === "string" ? value : "";
  }
  get serialNumber() {
    const value = this.attributes["0/40/15"];
    return typeof value === "string" ? value : "";
  }
  get updateState() {
    const value = this.attributes["0/42/2"];
    return typeof value === "number" ? value : void 0;
  }
  get updateStateProgress() {
    const value = this.attributes["0/42/3"];
    return typeof value === "number" ? value : void 0;
  }
  update(data) {
    return new MatterNode({
      ...this.data,
      ...data
    });
  }
}

/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
function toNodeKey(nodeId) {
  return String(nodeId);
}
const DEFAULT_COMMAND_TIMEOUT = 5 * 60 * 1e3;
class MatterClient {
  /**
   * Create a new MatterClient.
   * @param url WebSocket URL to connect to
   * @param wsFactory Optional factory function to create WebSocket instances.
   *                  For Node.js, pass: (url) => new WebSocket(url) from the 'ws' package.
   *                  For browser, leave undefined to use native WebSocket.
   */
  constructor(url, wsFactory) {
    this.url = url;
    this.url = url;
    this.connection = new Connection(this.url, wsFactory);
    this.serverBaseAddress = this.url.split("://")[1].split(":")[0] ?? "";
  }
  connection;
  nodes = {};
  serverBaseAddress;
  /** Whether this client is connected to a production server (optional, for UI purposes) */
  isProduction = false;
  /** Default timeout for commands in milliseconds. Set to 0 to disable timeouts. */
  commandTimeout = DEFAULT_COMMAND_TIMEOUT;
  // Using 'unknown' for resolve since the actual types vary by command
  result_futures = {};
  // Start with random offset for defense-in-depth and easier debugging across sessions
  msgId = Math.floor(Math.random() * 2147483647);
  eventListeners = {};
  get serverInfo() {
    return this.connection.serverInfo;
  }
  addEventListener(event, listener) {
    if (!this.eventListeners[event]) {
      this.eventListeners[event] = [];
    }
    this.eventListeners[event].push(listener);
    return () => {
      this.eventListeners[event] = this.eventListeners[event].filter(l => l !== listener);
    };
  }
  async commissionWithCode(code, networkOnly = true, timeout) {
    const data = await this.sendCommand("commission_with_code", 0, {
      code,
      network_only: networkOnly
    }, timeout);
    return new MatterNode(data);
  }
  async setWifiCredentials(ssid, credentials, timeout) {
    await this.sendCommand("set_wifi_credentials", 0, {
      ssid,
      credentials
    }, timeout);
  }
  async setThreadOperationalDataset(dataset, timeout) {
    await this.sendCommand("set_thread_dataset", 0, {
      dataset
    }, timeout);
  }
  async openCommissioningWindow(nodeId, windowTimeout, iteration, option, discriminator, timeout) {
    return await this.sendCommand("open_commissioning_window", 0, {
      node_id: nodeId,
      timeout: windowTimeout,
      iteration,
      option,
      discriminator
    }, timeout);
  }
  async discoverCommissionableNodes(timeout) {
    return await this.sendCommand("discover_commissionable_nodes", 0, {}, timeout);
  }
  async getMatterFabrics(nodeId, timeout) {
    return await this.sendCommand("get_matter_fabrics", 3, {
      node_id: nodeId
    }, timeout);
  }
  async removeMatterFabric(nodeId, fabricIndex, timeout) {
    await this.sendCommand("remove_matter_fabric", 3, {
      node_id: nodeId,
      fabric_index: fabricIndex
    }, timeout);
  }
  async pingNode(nodeId, attempts = 1, timeout) {
    return await this.sendCommand("ping_node", 0, {
      node_id: nodeId,
      attempts
    }, timeout);
  }
  async getNodeIPAddresses(nodeId, preferCache, scoped, timeout) {
    return await this.sendCommand("get_node_ip_addresses", 8, {
      node_id: nodeId,
      prefer_cache: preferCache,
      scoped
    }, timeout);
  }
  async removeNode(nodeId, timeout) {
    await this.sendCommand("remove_node", 0, {
      node_id: nodeId
    }, timeout);
  }
  async interviewNode(nodeId, timeout) {
    await this.sendCommand("interview_node", 0, {
      node_id: nodeId
    }, timeout);
  }
  async importTestNode(dump, timeout) {
    await this.sendCommand("import_test_node", 0, {
      dump
    }, timeout);
  }
  async readAttribute(nodeId, attributePath, timeout) {
    return await this.sendCommand("read_attribute", 0, {
      node_id: nodeId,
      attribute_path: attributePath
    }, timeout);
  }
  async writeAttribute(nodeId, attributePath, value, timeout) {
    return await this.sendCommand("write_attribute", 0, {
      node_id: nodeId,
      attribute_path: attributePath,
      value
    }, timeout);
  }
  async checkNodeUpdate(nodeId, timeout) {
    return await this.sendCommand("check_node_update", 10, {
      node_id: nodeId
    }, timeout);
  }
  async updateNode(nodeId, softwareVersion, timeout) {
    await this.sendCommand("update_node", 10, {
      node_id: nodeId,
      software_version: softwareVersion
    }, timeout);
  }
  async setACLEntry(nodeId, entry, timeout) {
    return await this.sendCommand("set_acl_entry", 0, {
      node_id: nodeId,
      entry
    }, timeout);
  }
  async setNodeBinding(nodeId, endpoint, bindings, timeout) {
    return await this.sendCommand("set_node_binding", 0, {
      node_id: nodeId,
      endpoint,
      bindings
    }, timeout);
  }
  async deviceCommand(nodeId, endpointId, clusterId, commandName, payload = {}, timeout) {
    return await this.sendCommand("device_command", 0, {
      node_id: nodeId,
      endpoint_id: endpointId,
      cluster_id: clusterId,
      command_name: commandName,
      payload,
      response_type: null
    }, timeout);
  }
  async getNodes(onlyAvailable = false, timeout) {
    const data = await this.sendCommand("get_nodes", 0, {
      only_available: onlyAvailable
    }, timeout);
    return data.map(n => new MatterNode(n));
  }
  async getNode(nodeId, timeout) {
    const data = await this.sendCommand("get_node", 0, {
      node_id: nodeId
    }, timeout);
    return new MatterNode(data);
  }
  async getVendorNames(filterVendors, timeout) {
    return await this.sendCommand("get_vendor_names", 0, {
      filter_vendors: filterVendors
    }, timeout);
  }
  async fetchServerInfo(timeout) {
    return await this.sendCommand("server_info", 0, {}, timeout);
  }
  async setDefaultFabricLabel(label, timeout) {
    await this.sendCommand("set_default_fabric_label", 0, {
      label
    }, timeout);
  }
  /**
   * Get the current log levels for console and file logging.
   * @param timeout Optional command timeout in milliseconds
   * @returns The current log level configuration
   */
  async getLogLevel(timeout) {
    return await this.sendCommand("get_loglevel", 0, {}, timeout);
  }
  /**
   * Set the log level for console and/or file logging.
   * Changes are temporary and will be reset when the server restarts.
   * @param consoleLoglevel Console log level to set (optional)
   * @param fileLoglevel File log level to set, only applied if file logging is enabled (optional)
   * @param timeout Optional command timeout in milliseconds
   * @returns The log level configuration after the change
   */
  async setLogLevel(consoleLoglevel, fileLoglevel, timeout) {
    return await this.sendCommand("set_loglevel", 0, {
      console_loglevel: consoleLoglevel,
      file_loglevel: fileLoglevel
    }, timeout);
  }
  /**
   * Send a command to the Matter server.
   * @param command The command name
   * @param require_schema Minimum schema version required (0 for any version)
   * @param args Command arguments
   * @param timeout Optional timeout in milliseconds. Defaults to `commandTimeout`. Set to 0 to disable.
   * @returns Promise that resolves with the command result
   * @throws Error if the command times out or fails
   */
  sendCommand(command, require_schema = void 0, args, timeout = this.commandTimeout) {
    if (require_schema && this.serverInfo.schema_version < require_schema) {
      throw new InvalidServerVersion(`Command not available due to incompatible server version. Update the Matter Server to a version that supports at least api schema ${require_schema}.`);
    }
    if (this.msgId >= Number.MAX_SAFE_INTEGER) {
      this.msgId = 0;
    }
    const messageId = String(++this.msgId);
    const message = {
      message_id: messageId,
      command,
      args
    };
    return new Promise((resolve, reject) => {
      let timeoutId;
      if (timeout > 0) {
        timeoutId = setTimeout(() => {
          const pending = this.result_futures[messageId];
          if (pending) {
            if (pending.timeoutId) {
              clearTimeout(pending.timeoutId);
            }
            delete this.result_futures[messageId];
            reject(new CommandTimeoutError(command, timeout));
          }
        }, timeout);
      }
      this.result_futures[messageId] = {
        resolve,
        reject,
        timeoutId
      };
      this.connection.sendMessage(message);
    });
  }
  /**
   * Safely resolve a pending command, ensuring it's only resolved once.
   * Clears timeout and removes from pending futures before resolving.
   */
  _resolvePendingCommand(messageId, result) {
    const pending = this.result_futures[messageId];
    if (pending) {
      if (pending.timeoutId) {
        clearTimeout(pending.timeoutId);
      }
      delete this.result_futures[messageId];
      pending.resolve(result);
    }
  }
  /**
   * Safely reject a pending command, ensuring it's only rejected once.
   * Clears timeout and removes from pending futures before rejecting.
   */
  _rejectPendingCommand(messageId, error) {
    const pending = this.result_futures[messageId];
    if (pending) {
      if (pending.timeoutId) {
        clearTimeout(pending.timeoutId);
      }
      delete this.result_futures[messageId];
      pending.reject(error);
    }
  }
  /**
   * Reject all pending commands with a ConnectionClosedError.
   * Called when the connection is closed or lost.
   */
  _rejectAllPendingCommands() {
    const error = new ConnectionClosedError();
    const pendingIds = Object.keys(this.result_futures);
    for (const messageId of pendingIds) {
      this._rejectPendingCommand(messageId, error);
    }
  }
  async connect() {
    if (this.connection.connected) {
      return;
    }
    await this.connection.connect(msg => this._handleIncomingMessage(msg), () => {
      this._rejectAllPendingCommands();
      this.fireEvent("connection_lost");
    });
  }
  disconnect(clearStorage = false) {
    this._rejectAllPendingCommands();
    if (this.connection && this.connection.connected) {
      this.connection.disconnect();
    }
    if (clearStorage && typeof localStorage !== "undefined") {
      localStorage.removeItem("matterURL");
      location.reload();
    }
  }
  async startListening() {
    await this.connect();
    const nodesArray = await this.sendCommand("start_listening", 0, {});
    const nodes = {};
    for (const node of nodesArray) {
      nodes[toNodeKey(node.node_id)] = new MatterNode(node);
    }
    this.nodes = nodes;
  }
  _handleIncomingMessage(msg) {
    if ("event" in msg) {
      this._handleEventMessage(msg);
      return;
    }
    if ("error_code" in msg) {
      this._rejectPendingCommand(msg.message_id, new Error(msg.details));
      return;
    }
    if ("result" in msg) {
      this._resolvePendingCommand(msg.message_id, msg.result);
      return;
    }
    console.warn("Received message with unknown format", msg);
  }
  _handleEventMessage(event) {
    console.log("Incoming event", event);
    this.onRawEvent(event);
    if (event.event === "node_added") {
      const node = new MatterNode(event.data);
      this.nodes = {
        ...this.nodes,
        [toNodeKey(node.node_id)]: node
      };
      this.fireEvent("nodes_changed");
      return;
    }
    if (event.event === "node_removed") {
      delete this.nodes[toNodeKey(event.data)];
      this.nodes = {
        ...this.nodes
      };
      this.fireEvent("nodes_changed");
      return;
    }
    if (event.event === "node_updated") {
      const node = new MatterNode(event.data);
      this.nodes = {
        ...this.nodes,
        [toNodeKey(node.node_id)]: node
      };
      this.fireEvent("nodes_changed");
      return;
    }
    if (event.event === "attribute_updated") {
      const [nodeId, attributeKey, attributeValue] = event.data;
      const nodeKey = toNodeKey(nodeId);
      const existingNode = this.nodes[nodeKey];
      if (existingNode) {
        const node = new MatterNode(existingNode.data);
        node.attributes[attributeKey] = attributeValue;
        this.nodes = {
          ...this.nodes,
          [nodeKey]: node
        };
        this.fireEvent("nodes_changed");
      }
      return;
    }
    if (event.event === "server_info_updated") {
      this.connection.serverInfo = event.data;
      this.fireEvent("server_info_updated");
      return;
    }
    if (event.event === "server_shutdown") {
      this.fireEvent("server_shutdown");
      this.disconnect();
      return;
    }
  }
  fireEvent(event) {
    const listeners = this.eventListeners[event];
    if (listeners) {
      for (const listener of listeners) {
        listener();
      }
    }
  }
  /**
   * Hook for subclasses to receive raw events.
   * Override this method to intercept all incoming events.
   * @param event The raw event message
   */
  onRawEvent(_event) {}
}

/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
const STORAGE_KEY = "matterTheme";
class ThemeServiceImpl {
  constructor() {
    this._preference = "system";
    this._listeners = /* @__PURE__ */new Set();
    this._mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    this._mediaQuery.addEventListener("change", () => this._applyTheme());
    this._loadPreference();
    this._applyTheme();
  }
  get preference() {
    return this._preference;
  }
  get effectiveTheme() {
    if (this._preference === "system") {
      return this._mediaQuery.matches ? "dark" : "light";
    }
    return this._preference;
  }
  setPreference(pref) {
    this._preference = pref;
    localStorage.setItem(STORAGE_KEY, pref);
    this._applyTheme();
  }
  cycleTheme() {
    const cycle = ["light", "dark", "system"];
    const currentIndex = cycle.indexOf(this._preference);
    const nextIndex = (currentIndex + 1) % cycle.length;
    this.setPreference(cycle[nextIndex]);
    return this._preference;
  }
  subscribe(callback) {
    this._listeners.add(callback);
    return () => this._listeners.delete(callback);
  }
  _loadPreference() {
    const urlParams = new URLSearchParams(window.location.search);
    const themeParam = urlParams.get("theme");
    if (themeParam && ["light", "dark", "system"].includes(themeParam)) {
      this._preference = themeParam;
      localStorage.setItem(STORAGE_KEY, themeParam);
      urlParams.delete("theme");
      const newUrl = urlParams.toString() ? `${window.location.pathname}?${urlParams.toString()}${window.location.hash}` : `${window.location.pathname}${window.location.hash}`;
      history.replaceState({}, "", newUrl);
      return;
    }
    const stored = localStorage.getItem(STORAGE_KEY);
    if (stored && ["light", "dark", "system"].includes(stored)) {
      this._preference = stored;
    }
  }
  _applyTheme() {
    const effective = this.effectiveTheme;
    document.documentElement.classList.toggle("dark-theme", effective === "dark");
    const metaThemeColor = document.querySelector('meta[name="theme-color"]');
    if (metaThemeColor) {
      metaThemeColor.setAttribute("content", effective === "dark" ? "#1e1e1e" : "#03a9f4");
    }
    this._listeners.forEach(cb => cb(effective));
  }
}
const ThemeService = new ThemeServiceImpl();

/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
async function main() {
  import('./matter-dashboard-app-BiNu4m8W.js').then(function (n) { return n.I; });
  let url = "";
  const isProductionServer = window.__MATTERJS_PRODUCTION_MODE__ === true || location.origin.includes(":5580") || location.href.includes("hassio_ingress") || location.href.includes("/api/ingress/");
  if (!isProductionServer) {
    let storageUrl = localStorage.getItem("matterURL");
    if (!storageUrl) {
      const urlParams = new URLSearchParams(window.location.search);
      const suggestedUrl = urlParams.get("url");
      storageUrl = prompt("Enter Websocket URL to a running Matter Server", suggestedUrl ?? "ws://localhost:5580/ws");
      if (!storageUrl) {
        alert("Unable to connect without URL");
        return;
      }
      if (suggestedUrl) {
        history.pushState({}, "", window.location.pathname);
      }
      localStorage.setItem("matterURL", storageUrl);
    }
    url = storageUrl;
  } else {
    let baseUrl = window.location.origin + window.location.pathname;
    if (baseUrl.endsWith("/")) {
      baseUrl = baseUrl.slice(0, -1);
    }
    url = baseUrl.replace("http", "ws") + "/ws";
    console.log(`Connecting to Matter Server API using url: ${url}`);
  }
  const client = new MatterClient(url);
  client.isProduction = isProductionServer;
  const dashboard = document.createElement("matter-dashboard-app");
  dashboard.client = client;
  document.body.append(dashboard);
}
main();

export { ThemeService as T, toBigIntAwareJson as t };
