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 { showAlertDialog, showPromptDialog } from "../../dialog-box/show-dialog-box.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;
      let aclCleanedUp = false;
      try {
        await this.removeNodeAtACLEntry(this.node.node_id, endpoint, targetNodeId);
        aclCleanedUp = true;
      } catch (aclError) {
        const errorMessage = aclError instanceof Error ? aclError.message : String(aclError);
        const proceed = await showPromptDialog({
          title: "ACL cleanup failed",
          text: `Could not clean up ACL on target node ${targetNodeId}: ${errorMessage}. The target node may no longer exist or be unreachable. Do you want to remove the binding anyway? Note: The target device may retain an outdated ACL entry.`,
          confirmText: "Remove binding"
        });
        if (!proceed) return;
      }
      try {
        const updatedBindings = this.removeBindingAtIndex(rawBindings, index);
        await this.syncBindingUpdates(updatedBindings, index);
      } catch (bindingError) {
        const errorMessage = bindingError instanceof Error ? bindingError.message : String(bindingError);
        await showAlertDialog({
          title: "Binding removal failed",
          text: `Failed to remove the binding: ${errorMessage}. ` + (aclCleanedUp ? "The ACL on the target device was already updated. The binding and ACL may now be out of sync." : "No changes were made.")
        });
        return;
      }
    } 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)) {
        showAlertDialog({ title: "Validation error", text: "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) {
      showAlertDialog({ title: "Validation error", text: "Please enter a valid target node ID" });
      return;
    }
    if (targetEndpoint === void 0 || targetEndpoint <= 0 || targetEndpoint > 65534) {
      showAlertDialog({ title: "Validation error", text: "Please enter a valid target endpoint" });
      return;
    }
    if (targetCluster !== void 0) {
      if (targetCluster < 0 || targetCluster > 32767) {
        showAlertDialog({ title: "Validation error", text: "Please enter a valid target cluster" });
        return;
      }
    }
    const targets = {
      endpoint: targetEndpoint,
      cluster: targetCluster,
      deviceType: void 0
    };
    const acl_entry = {
      privilege: 3,
      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") {
      showAlertDialog({ title: "Failed to add ACL entry", text: aclResult.message });
      return;
    }
    if (aclResult.outcome === "partial") {
      showAlertDialog({ title: "ACL entry partially failed", text: 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") {
      showAlertDialog({ title: "Binding partially failed", text: bindingResult.message });
      this.requestUpdate();
    } else {
      showAlertDialog({ title: "Failed to add binding", text: 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:16px;">
                            ${Object.values(bindings).map(
      (entry, index) => html`
                  <md-list-item class="binding-item">
                    <div style="display:flex;gap:8px;">
                        <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;">
                            <span style="font-size: 0.75rem;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.
                            </span>
                        </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: 16px 8px 8px 8px;
            border-radius: 4px;
            vertical-align: middle;
            min-width: 80px;
            text-align: center;
            width: -webkit-fill-available;
        }

        .group-label {
            position: absolute;
            left: 16px;
            top: -12px;
            background: var(--md-sys-color-primary);
            color: var(--md-sys-color-on-primary);
            padding: 4px 16px;
            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
