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/outlined-button";
import { mdiTrashCan } from "@mdi/js";
import { css, html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js";
import "../../../components/ha-svg-icon.js";
import { clusters } from "../../../client/models/descriptions.js";
import { showAlertDialog, showPromptDialog } from "../../../components/dialog-box/show-dialog-box.js";
import {
  deleteBindingAtIndex,
  ensureBindingAcl,
  fixOverPrivilegedBindingAcl
} from "../../../components/dialogs/binding/binding-actions.js";
import { showNodeBindingDialog } from "../../../components/dialogs/binding/show-node-binding-dialog.js";
import { nodeIdKey } from "../../../util/access-control.js";
import { handleAsync } from "../../../util/async-handler.js";
import { readBindings, reverseAclState } from "../../../util/binding.js";
import { getEndpointDeviceTypes } from "../../../util/endpoints.js";
import { getDeviceName } from "../../../util/node-name.js";
import { BaseClusterCommands } from "../base-cluster-commands.js";
import { registerClusterCommands } from "../registry.js";
const CLUSTER_ID = 30;
let BindingClusterCommands = class extends BaseClusterCommands {
  constructor() {
    super(...arguments);
    this._loadedKey = "";
    this._busy = false;
  }
  updated(changed) {
    super.updated(changed);
    if (changed.has("client") && this.client && !this._unsubscribe) {
      this._unsubscribe = this.client.addEventListener("nodes_changed", () => this.requestUpdate());
    }
    void this._ensureLoaded();
  }
  disconnectedCallback() {
    super.disconnectedCallback();
    this._unsubscribe?.();
  }
  /** Read the (fabric-scoped) binding attribute and each target's ACL into the cache on open. */
  async _ensureLoaded() {
    if (!this.client || !this.node || !this.node.available || this.endpoint === void 0) return;
    const key = `${nodeIdKey(this.node.node_id)}/${this.endpoint}`;
    if (this._loadedKey === key) return;
    this._loadedKey = key;
    try {
      await this._readInto(this.node.node_id, [`${this.endpoint}/30/0`, "0/62/5"]);
      const targets = new Set(
        readBindings(this.node, this.endpoint).map((b) => b.node != null ? nodeIdKey(b.node) : void 0).filter((k) => k !== void 0)
      );
      await Promise.all(
        [...targets].map((k) => {
          const target = this.client.nodes[k];
          return target?.available ? this._readInto(target.node_id, ["0/31/0", "0/62/5"]) : void 0;
        })
      );
      this.requestUpdate();
    } catch (err) {
      this._loadedKey = "";
      console.error("Failed to load binding/ACL data", err);
    }
  }
  async _readInto(nodeId, path) {
    const res = await this.client.readAttribute(nodeId, path);
    const node = this.client.nodes[nodeIdKey(nodeId)];
    if (node) for (const [k, v] of Object.entries(res)) node.attributes[k] = v;
  }
  _clusterName(id) {
    if (id == null) return "All clusters";
    return `${clusters[id]?.label ?? "Cluster"} (0x${id.toString(16).padStart(2, "0").toUpperCase()})`;
  }
  _targetNode(nodeId) {
    if (nodeId == null) return void 0;
    return this.client.nodes[nodeIdKey(nodeId)];
  }
  async _openAdd() {
    await showNodeBindingDialog(this.node, this.endpoint);
  }
  async _run(action, failTitle) {
    this._busy = true;
    try {
      await action();
    } catch (err) {
      await showAlertDialog({ title: failTitle, text: err instanceof Error ? err.message : String(err) });
    } finally {
      this._busy = false;
    }
  }
  async _delete(index) {
    const confirmed = await showPromptDialog({
      title: "Remove binding",
      text: "Remove this binding and clean up the matching access control entry on the target node?",
      confirmText: "Remove"
    });
    if (!confirmed) return;
    await this._run(() => deleteBindingAtIndex(this.client, this.node, this.endpoint, index), "Delete failed");
  }
  async _fixAcl(b, mode) {
    if (b.node == null || b.endpoint == null) return;
    await this._run(
      () => mode === "missing" ? ensureBindingAcl(this.client, this.node.node_id, b.node, b.endpoint, b.cluster) : fixOverPrivilegedBindingAcl(this.client, this.node.node_id, b.node, b.endpoint, b.cluster),
      "Fix failed"
    );
  }
  render() {
    if (!this.node || this.cluster !== CLUSTER_ID) return nothing;
    const bindings = readBindings(this.node, this.endpoint);
    return html`
            <details class="command-panel">
                <summary>Bindings (${bindings.length})</summary>
                <div class="command-content">
                    ${bindings.length === 0 ? html`<div class="empty">No bindings on this endpoint.</div>` : html`<table class="bt">
                              <thead>
                                  <tr>
                                      <th>Target node</th>
                                      <th>Endpoint</th>
                                      <th>Cluster</th>
                                      <th>ACL on target</th>
                                      <th></th>
                                  </tr>
                              </thead>
                              <tbody>
                                  ${bindings.map((b, i) => this._row(b, i))}
                              </tbody>
                          </table>`}
                    <md-outlined-button
                        ?disabled=${this._busy || !this.node.available}
                        @click=${handleAsync(() => this._openAdd())}
                        >Add binding</md-outlined-button
                    >
                </div>
            </details>
        `;
  }
  _row(b, index) {
    const target = this._targetNode(b.node);
    const aclState = b.node == null ? "cannotVerify" : reverseAclState(this.node.node_id, b, target).state;
    const name = b.group != null ? `Group ${b.group}` : target ? getDeviceName(target) : "Unknown node";
    const deviceType = b.endpoint != null && target ? getEndpointDeviceTypes(target, b.endpoint)[0] : void 0;
    const endpointText = b.endpoint == null ? "\u2014" : deviceType ? `EP ${b.endpoint} \xB7 ${deviceType.label}` : `EP ${b.endpoint}`;
    return html`
            <tr>
                <td>
                    <span class="ident"
                        ><b>${name}</b>${b.node != null ? html` · <span class="nid">${b.node.toString()}</span>` : nothing}</span
                    >
                </td>
                <td>${endpointText}</td>
                <td>${this._clusterName(b.cluster)}</td>
                <td>${this._aclCell(b, aclState)}</td>
                <td class="actions">
                    <md-outlined-button
                        class="danger"
                        ?disabled=${this._busy || !this.node.available}
                        @click=${handleAsync(() => this._delete(index))}
                    >
                        <ha-svg-icon .path=${mdiTrashCan} slot="icon"></ha-svg-icon>delete
                    </md-outlined-button>
                </td>
            </tr>
        `;
  }
  _aclCell(b, state2) {
    if (state2 === "self") return html`<span class="status mut">self — no ACL needed</span>`;
    if (state2 === "present") return html`<span class="status ok">ACL present</span>`;
    if (state2 === "cannotVerify") return html`<span class="status mut">can't verify</span>`;
    const label = state2 === "missing" ? "ACL missing" : "ACL > Operate";
    const fixLabel = state2 === "missing" ? "Fix ACL" : "Fix \u2192 Operate";
    return html`
            <span class="status warn">${label}</span>
            <md-outlined-button
                class="fix"
                ?disabled=${this._busy || !this.node.available}
                @click=${handleAsync(() => this._fixAcl(b, state2))}
                >${fixLabel}</md-outlined-button
            >
        `;
  }
};
BindingClusterCommands.styles = [
  BaseClusterCommands.styles,
  css`
            .bt {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 12px;
            }
            .bt th {
                text-align: left;
                font-size: 11px;
                text-transform: uppercase;
                opacity: 0.6;
                padding: 8px 10px;
                border-bottom: 1px solid var(--md-sys-color-outline-variant);
            }
            .bt td {
                padding: 10px;
                border-bottom: 1px solid var(--md-sys-color-outline-variant);
                vertical-align: middle;
            }
            .empty {
                opacity: 0.6;
                padding: 8px 0 12px;
            }
            .ident .nid {
                font-weight: 600;
            }
            .status {
                font-size: inherit;
            }
            .status.ok {
                color: var(--md-sys-color-primary);
            }
            .status.warn {
                color: var(--md-sys-color-error);
                margin-right: 8px;
            }
            .status.mut {
                opacity: 0.6;
            }
            .actions {
                text-align: right;
                white-space: nowrap;
            }
            md-outlined-button.fix {
                --md-outlined-button-container-height: 28px;
            }
            md-outlined-button.danger {
                --md-outlined-button-label-text-color: var(--md-sys-color-error);
                --md-outlined-button-outline-color: var(--md-sys-color-error);
            }
        `
];
__decorateClass([
  state()
], BindingClusterCommands.prototype, "_busy", 2);
BindingClusterCommands = __decorateClass([
  customElement("binding-cluster-commands")
], BindingClusterCommands);
registerClusterCommands(CLUSTER_ID, "binding-cluster-commands");
//# sourceMappingURL=binding-commands.js.map
