var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result) __defProp(target, key, result);
  return result;
};
/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
import "@material/web/button/text-button";
import "@material/web/dialog/dialog";
import { consume } from "@lit/context";
import "@material/web/list/list";
import "@material/web/list/list-item";
import "@material/web/textfield/outlined-text-field";
import "../../../components/ha-svg-icon";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { clientContext } from "../../../client/client-context.js";
import { handleAsync } from "../../../util/async-handler.js";
import { analyzeBatchResults } from "../../../util/matter-status.js";
import { preventDefault } from "../../../util/prevent_default.js";
import {
  AccessControlEntryDataTransformer
} from "../acl/model.js";
import { BindingEntryDataTransformer } from "./model.js";
let NodeBindingDialog = class extends LitElement {
  fetchBindingEntry() {
    const bindings_raw = this.node.attributes[this.endpoint + "/30/0"];
    if (!bindings_raw) return [];
    return Object.values(bindings_raw).map((value) => BindingEntryDataTransformer.transform(value));
  }
  fetchACLEntry(targetNodeId) {
    const acl_cluster_raw = this.client.nodes[String(targetNodeId)]?.attributes["0/31/0"];
    if (!acl_cluster_raw) return [];
    return Object.values(acl_cluster_raw).map(
      (value) => AccessControlEntryDataTransformer.transform(value)
    );
  }
  async deleteBindingHandler(index) {
    const rawBindings = this.fetchBindingEntry();
    try {
      const targetNodeId = rawBindings[index].node;
      const endpoint = rawBindings[index].endpoint;
      if (targetNodeId === void 0 || endpoint === void 0) return;
      await this.removeNodeAtACLEntry(this.node.node_id, endpoint, targetNodeId);
      const updatedBindings = this.removeBindingAtIndex(rawBindings, index);
      await this.syncBindingUpdates(updatedBindings, index);
    } catch (error) {
      this.handleBindingDeletionError(error);
    }
  }
  async removeNodeAtACLEntry(sourceNodeId, sourceEndpoint, targetNodeId) {
    const aclEntries = this.fetchACLEntry(targetNodeId);
    const updatedACLEntries = aclEntries.map((entry) => this.removeEntryAtACL(sourceNodeId, sourceEndpoint, entry)).filter((entry) => entry !== void 0);
    const apiEntries = updatedACLEntries.map((e) => this.toAccessControlEntry(e));
    await this.client.setACLEntry(targetNodeId, apiEntries);
  }
  removeEntryAtACL(nodeId, sourceEndpoint, entry) {
    const hasSubject = entry.subjects.includes(nodeId);
    if (!hasSubject) return entry;
    const hasTarget = entry.targets.filter((item) => item.endpoint === sourceEndpoint);
    return hasTarget.length > 0 ? void 0 : entry;
  }
  removeBindingAtIndex(bindings, index) {
    return [...bindings.slice(0, index), ...bindings.slice(index + 1)];
  }
  async syncBindingUpdates(updatedBindings, index) {
    const apiBindings = updatedBindings.map((b) => this.toBindingTarget(b));
    await this.client.setNodeBinding(this.node.node_id, this.endpoint, apiBindings);
    const attributePath = `${this.endpoint}/30/0`;
    const currentBindings = this.node.attributes[attributePath];
    const updatedAttributes = {
      ...this.node.attributes,
      [attributePath]: currentBindings ? this.removeBindingAtIndex(currentBindings, index) : []
    };
    this.node.attributes = updatedAttributes;
    this.requestUpdate();
  }
  handleBindingDeletionError(error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    console.error(`Binding deletion failed: ${errorMessage}`);
  }
  async add_target_acl(targetNodeId, entry) {
    try {
      const rawEntries = this.client.nodes[String(targetNodeId)]?.attributes["0/31/0"];
      const entries = rawEntries ? Object.values(rawEntries).map((v) => AccessControlEntryDataTransformer.transform(v)) : [];
      entries.push(entry);
      const apiEntries = entries.map((e) => this.toAccessControlEntry(e));
      const results = await this.client.setACLEntry(targetNodeId, apiEntries);
      const batchResult = analyzeBatchResults(results);
      if (batchResult.outcome !== "all_success") {
        console.error(`Set ACL entry: ${batchResult.message}`);
      }
      return batchResult;
    } catch (err) {
      console.error("Add ACL error:", err);
      return {
        outcome: "all_failed",
        successCount: 0,
        failureCount: 1,
        errorCounts: { 1: 1 },
        message: `Exception: ${err instanceof Error ? err.message : String(err)}`
      };
    }
  }
  /** Convert local BindingEntryStruct to API BindingTarget (without fabricIndex) */
  toBindingTarget(entry) {
    return {
      node: entry.node ?? null,
      group: entry.group ?? null,
      endpoint: entry.endpoint ?? null,
      cluster: entry.cluster ?? null
    };
  }
  /** Convert local AccessControlEntryStruct to API AccessControlEntry (without fabricIndex) */
  toAccessControlEntry(entry) {
    return {
      privilege: entry.privilege,
      auth_mode: entry.authMode,
      subjects: entry.subjects ?? null,
      targets: entry.targets?.map((t) => ({
        cluster: t.cluster ?? null,
        endpoint: t.endpoint ?? null,
        device_type: t.deviceType ?? null
      })) ?? null
    };
  }
  async add_bindings(endpoint, bindingEntry) {
    const bindings = this.fetchBindingEntry();
    bindings.push(bindingEntry);
    try {
      const apiBindings = bindings.map((b) => this.toBindingTarget(b));
      const results = await this.client.setNodeBinding(this.node.node_id, endpoint, apiBindings);
      const batchResult = analyzeBatchResults(results);
      if (batchResult.outcome !== "all_success") {
        console.error(`Set binding: ${batchResult.message}`);
      }
      return batchResult;
    } catch (err) {
      console.error("Add bindings error:", err);
      return {
        outcome: "all_failed",
        successCount: 0,
        failureCount: 1,
        errorCounts: { 1: 1 },
        message: `Exception: ${err instanceof Error ? err.message : String(err)}`
      };
    }
  }
  async addBindingHandler() {
    let targetNodeId;
    const rawNodeId = this._targetNodeId.value?.trim();
    if (rawNodeId) {
      if (!/^\d+$/.test(rawNodeId)) {
        alert("Please enter a valid target node ID");
        return;
      }
      targetNodeId = BigInt(rawNodeId);
    }
    const targetEndpoint = this._targetEndpoint.value ? parseInt(this._targetEndpoint.value, 10) : void 0;
    const targetCluster = this._targetCluster.value ? parseInt(this._targetCluster.value, 10) : void 0;
    if (targetNodeId === void 0 || targetNodeId <= 0n) {
      alert("Please enter a valid target node ID");
      return;
    }
    if (targetEndpoint === void 0 || targetEndpoint <= 0 || targetEndpoint > 65534) {
      alert("Please enter a valid target endpoint");
      return;
    }
    if (targetCluster !== void 0) {
      if (targetCluster < 0 || targetCluster > 32767) {
        alert("Please enter a valid target cluster");
        return;
      }
    }
    const targets = {
      endpoint: targetEndpoint,
      cluster: targetCluster,
      deviceType: void 0
    };
    const acl_entry = {
      privilege: 5,
      authMode: 2,
      subjects: [this.node.node_id],
      targets: [targets],
      fabricIndex: 0
      // Placeholder - server will use correct fabric index
    };
    const aclResult = await this.add_target_acl(targetNodeId, acl_entry);
    if (aclResult.outcome === "all_failed") {
      alert(`Failed to add ACL entry:
${aclResult.message}`);
      return;
    }
    if (aclResult.outcome === "partial") {
      alert(`ACL entry partially failed:
${aclResult.message}`);
    }
    const endpoint = this.endpoint;
    const bindingEntry = {
      node: targetNodeId,
      endpoint: targetEndpoint,
      group: void 0,
      cluster: targetCluster,
      fabricIndex: void 0
      // Server will use correct fabric index
    };
    const bindingResult = await this.add_bindings(endpoint, bindingEntry);
    if (bindingResult.outcome === "all_success") {
      this._targetNodeId.value = "";
      this._targetEndpoint.value = "";
      this._targetCluster.value = "";
      this.requestUpdate();
    } else if (bindingResult.outcome === "partial") {
      alert(`Binding partially failed:
${bindingResult.message}`);
      this.requestUpdate();
    } else {
      alert(`Failed to add binding:
${bindingResult.message}`);
    }
  }
  _close() {
    this.shadowRoot.querySelector("md-dialog").close();
  }
  _handleClosed() {
    this.parentNode.removeChild(this);
  }
  onChange(e) {
    const textfield = e.target;
    if (textfield.type === "number" && textfield.max && textfield.min) {
      const value = parseInt(textfield.value, 10);
      if (parseInt(textfield.max, 10) < value || value < parseInt(textfield.min, 10)) {
        textfield.error = true;
        textfield.errorText = "value error";
      } else {
        textfield.error = false;
      }
    } else {
      textfield.error = textfield.value !== "" && !/^[0-9]+$/.test(textfield.value);
      if (textfield.error) {
        textfield.errorText = "must be a numeric value";
      }
    }
  }
  render() {
    const rawBindings = this.node.attributes[this.endpoint + "/30/0"];
    const bindings = rawBindings ? Object.values(rawBindings).map((entry) => BindingEntryDataTransformer.transform(entry)) : [];
    return html`
            <md-dialog open @cancel=${preventDefault} @closed=${this._handleClosed}>
                <div slot="headline">
                    <div>Binding</div>
                </div>
                <div slot="content">
                    <div>
                        <md-list style="padding-bottom:18px;">
                            ${Object.values(bindings).map(
      (entry, index) => html`
                  <md-list-item class="binding-item">
                    <div style="display:flex;gap:10px;">
                        <div>node:${entry["node"]}</div>
                        <div>endpoint:${entry["endpoint"]}</div>
                        ${entry["cluster"] ? html` <div>cluster:${entry["cluster"]}</div> ` : nothing}
                    </div>
                    <div slot="end">
                      <md-text-button
                        @click=${handleAsync(() => this.deleteBindingHandler(index))}
                      >delete</md-text-button
                    </div>
                  </md-list-item>
                `
    )}
                        </md-list>
                        <div class="inline-group">
                            <div class="group-label">target</div>
                            <div class="group-input">
                                <md-outlined-text-field
                                    label="node id"
                                    name="NodeId"
                                    type="text"
                                    pattern="[0-9]+"
                                    class="target-item"
                                    @change=${this.onChange}
                                    supporting-text="required"
                                ></md-outlined-text-field>
                                <md-outlined-text-field
                                    label="endpoint"
                                    name="Endpoint"
                                    type="number"
                                    min="0"
                                    max="65534"
                                    @change=${this.onChange}
                                    class="target-item"
                                    supporting-text="required"
                                ></md-outlined-text-field>
                                <md-outlined-text-field
                                    label="cluster"
                                    name="Cluster"
                                    type="number"
                                    min="0"
                                    max="32767"
                                    @change=${this.onChange}
                                    class="target-item"
                                    supporting-text="optional"
                                ></md-outlined-text-field>
                            </div>
                        </div>
                        <div style="margin:8px;">
                            <Text style="font-size: 10px;font-style: italic;font-weight: bold;">
                                Note: The Cluster ID field is optional according to the Matter specification. If you
                                leave it blank, the binding applies to all eligible clusters on the target endpoint.
                                However, some devices may require a specific cluster to be set in order for the binding
                                to function correctly. If you experience unexpected behavior, try specifying the cluster
                                explicitly.
                            </Text>
                        </div>
                    </div>
                </div>
                <div slot="actions">
                    <md-text-button @click=${handleAsync(() => this.addBindingHandler())}>Add</md-text-button>
                    <md-text-button @click=${this._close}>Cancel</md-text-button>
                </div>
            </md-dialog>
        `;
  }
};
NodeBindingDialog.styles = css`
        .binding-item {
            background: var(--md-sys-color-surface-container-high);
        }

        .inline-group {
            display: flex;
            border: 2px solid var(--md-sys-color-primary);
            padding: 1px;
            border-radius: 8px;
            position: relative;
            margin: 8px;
        }

        .group-input {
            display: flex;
            width: -webkit-fill-available;
        }

        .target-item {
            display: inline-block;
            padding: 20px 10px 10px 10px;
            border-radius: 4px;
            vertical-align: middle;
            min-width: 80px;
            text-align: center;
            width: -webkit-fill-available;
        }

        .group-label {
            position: absolute;
            left: 15px;
            top: -12px;
            background: var(--md-sys-color-primary);
            color: var(--md-sys-color-on-primary);
            padding: 3px 15px;
            border-radius: 4px;
        }
    `;
__decorateClass([
  consume({ context: clientContext, subscribe: true }),
  property({ attribute: false })
], NodeBindingDialog.prototype, "client", 2);
__decorateClass([
  property()
], NodeBindingDialog.prototype, "node", 2);
__decorateClass([
  property({ attribute: false })
], NodeBindingDialog.prototype, "endpoint", 2);
__decorateClass([
  query("md-outlined-text-field[name='NodeId']")
], NodeBindingDialog.prototype, "_targetNodeId", 2);
__decorateClass([
  query("md-outlined-text-field[name='Endpoint']")
], NodeBindingDialog.prototype, "_targetEndpoint", 2);
__decorateClass([
  query("md-outlined-text-field[name='Cluster']")
], NodeBindingDialog.prototype, "_targetCluster", 2);
NodeBindingDialog = __decorateClass([
  customElement("node-binding-dialog")
], NodeBindingDialog);
export {
  NodeBindingDialog
};
//# sourceMappingURL=node-binding-dialog.js.map
