const p = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class x {
  /** Plugin version - override in subclass if needed */
  version = "1.0.0";
  /** CSS styles to inject into the grid's shadow DOM */
  styles;
  /** Custom cell renderers keyed by type name */
  cellRenderers;
  /** Custom header renderers keyed by type name */
  headerRenderers;
  /** Custom cell editors keyed by type name */
  cellEditors;
  /** The grid instance this plugin is attached to */
  grid;
  /** Plugin configuration - merged with defaults in attach() */
  config;
  /** User-provided configuration from constructor */
  userConfig;
  /**
   * Default configuration - subclasses should override this getter.
   * Note: This must be a getter (not property initializer) for proper inheritance
   * since property initializers run after parent constructor.
   */
  get defaultConfig() {
    return {};
  }
  constructor(e = {}) {
    this.userConfig = e;
  }
  /**
   * Called when the plugin is attached to a grid.
   * Override to set up event listeners, initialize state, etc.
   */
  attach(e) {
    this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
  }
  /**
   * Called when the plugin is detached from a grid.
   * Override to clean up event listeners, timers, etc.
   */
  detach() {
  }
  /**
   * Get another plugin instance from the same grid.
   * Use for inter-plugin communication.
   */
  getPlugin(e) {
    return this.grid?.getPlugin(e);
  }
  /**
   * Emit a custom event from the grid.
   */
  emit(e, t) {
    this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
  }
  /**
   * Request a re-render of the grid.
   */
  requestRender() {
    this.grid?.requestRender?.();
  }
  /**
   * Request a lightweight style update without rebuilding DOM.
   * Use this instead of requestRender() when only CSS classes need updating.
   */
  requestAfterRender() {
    this.grid?.requestAfterRender?.();
  }
  /**
   * Get the current rows from the grid.
   */
  get rows() {
    return this.grid?.rows ?? [];
  }
  /**
   * Get the original unfiltered/unprocessed rows from the grid.
   * Use this when you need all source data regardless of active filters.
   */
  get sourceRows() {
    return this.grid?.sourceRows ?? [];
  }
  /**
   * Get the current columns from the grid.
   */
  get columns() {
    return this.grid?.columns ?? [];
  }
  /**
   * Get only visible columns from the grid (excludes hidden).
   * Use this for rendering that needs to match the grid template.
   */
  get visibleColumns() {
    return this.grid?.visibleColumns ?? [];
  }
  /**
   * Get the shadow root of the grid.
   */
  get shadowRoot() {
    return this.grid?.shadowRoot ?? null;
  }
  /**
   * Get the disconnect signal for event listener cleanup.
   * This signal is aborted when the grid disconnects from the DOM.
   * Use this when adding event listeners that should be cleaned up automatically.
   *
   * Best for:
   * - Document/window-level listeners added in attach()
   * - Listeners on the grid element itself
   * - Any listener that should persist across renders
   *
   * Not needed for:
   * - Listeners on elements created in afterRender() (removed with element)
   *
   * @example
   * element.addEventListener('click', handler, { signal: this.disconnectSignal });
   * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
   */
  get disconnectSignal() {
    return this.grid?.disconnectSignal;
  }
  /**
   * Get the grid-level icons configuration.
   * Returns merged icons (user config + defaults).
   */
  get gridIcons() {
    const e = this.grid?.gridConfig?.icons ?? {};
    return { ...p, ...e };
  }
  /**
   * Resolve an icon value to string or HTMLElement.
   * Checks plugin config first, then grid-level icons, then defaults.
   *
   * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
   * @param pluginOverride - Optional plugin-level override
   * @returns The resolved icon value
   */
  resolveIcon(e, t) {
    return t !== void 0 ? t : this.gridIcons[e];
  }
  /**
   * Set an icon value on an element.
   * Handles both string (text/HTML) and HTMLElement values.
   *
   * @param element - The element to set the icon on
   * @param icon - The icon value (string or HTMLElement)
   */
  setIcon(e, t) {
    typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(e) {
    console.warn(`[tbw-grid:${this.name}] ${e}`);
  }
  // #endregion
}
function g(a, e) {
  const t = new Set(a);
  return t.has(e) ? t.delete(e) : t.add(e), t;
}
function R(a, e) {
  const t = new Set(a);
  return t.add(e), t;
}
function m(a, e) {
  const t = new Set(a);
  return t.delete(e), t;
}
function E(a, e) {
  return a.has(e);
}
function b(a, e, t, r) {
  const o = document.createElement("div");
  o.className = "master-detail-row", o.setAttribute("data-detail-for", String(e)), o.setAttribute("role", "row");
  const d = document.createElement("div");
  d.className = "master-detail-cell", d.setAttribute("role", "cell"), d.style.gridColumn = `1 / ${r + 1}`;
  const n = t(a, e);
  return typeof n == "string" ? d.innerHTML = n : n instanceof HTMLElement && d.appendChild(n), o.appendChild(d), o;
}
const C = ".master-detail-cell-wrapper{display:flex;align-items:center;gap:4px}.master-detail-toggle{cursor:pointer;font-size:10px;opacity:.7;-webkit-user-select:none;user-select:none}.master-detail-toggle:hover{opacity:1}.master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border))}.master-detail-cell{padding:16px;overflow:auto}";
class v extends x {
  name = "masterDetail";
  version = "1.0.0";
  get defaultConfig() {
    return {
      detailHeight: "auto",
      expandOnRowClick: !1,
      collapseOnClickOutside: !1,
      showExpandColumn: !0
    };
  }
  // #region Internal State
  expandedRows = /* @__PURE__ */ new Set();
  detailElements = /* @__PURE__ */ new Map();
  // #endregion
  // #region Lifecycle
  detach() {
    this.expandedRows.clear(), this.detailElements.clear();
  }
  // #endregion
  // #region Hooks
  processColumns(e) {
    if (!this.config.detailRenderer)
      return [...e];
    const t = [...e];
    if (t.length > 0) {
      const r = { ...t[0] }, o = r.viewRenderer;
      if (o?.__masterDetailWrapped)
        return t;
      const d = (n) => {
        const { value: i, row: s } = n, l = this.expandedRows.has(s), c = document.createElement("span");
        c.className = "master-detail-cell-wrapper";
        const h = document.createElement("span");
        h.className = "master-detail-toggle", this.setIcon(h, this.resolveIcon(l ? "collapse" : "expand")), h.setAttribute("role", "button"), h.setAttribute("tabindex", "0"), h.setAttribute("aria-expanded", String(l)), h.setAttribute("aria-label", l ? "Collapse details" : "Expand details"), h.addEventListener("click", (f) => {
          f.stopPropagation();
          const w = this.rows.indexOf(s);
          this.expandedRows = g(this.expandedRows, s), this.emit("detail-expand", {
            rowIndex: w,
            row: s,
            expanded: this.expandedRows.has(s)
          }), this.requestRender();
        }), c.appendChild(h);
        const u = document.createElement("span");
        if (o) {
          const f = o(n);
          f instanceof Node ? u.appendChild(f) : u.textContent = String(f ?? i ?? "");
        } else
          u.textContent = String(i ?? "");
        return c.appendChild(u), c;
      };
      d.__masterDetailWrapped = !0, r.viewRenderer = d, t[0] = r;
    }
    return t;
  }
  onRowClick(e) {
    if (!(!this.config.expandOnRowClick || !this.config.detailRenderer))
      return this.expandedRows = g(this.expandedRows, e.row), this.emit("detail-expand", {
        rowIndex: e.rowIndex,
        row: e.row,
        expanded: this.expandedRows.has(e.row)
      }), this.requestRender(), !1;
  }
  onCellClick() {
    this.expandedRows.size > 0 && queueMicrotask(() => this.#e());
  }
  afterRender() {
    this.#e();
  }
  /**
   * Called on scroll to sync detail elements with visible rows.
   * Removes details for rows that scrolled out of view and reattaches for visible rows.
   */
  onScrollRender() {
    !this.config.detailRenderer || this.expandedRows.size === 0 || this.#e();
  }
  /**
   * Full sync of detail rows - cleans up stale elements and creates new ones.
   * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.
   */
  #e() {
    if (!this.config.detailRenderer) return;
    const e = this.shadowRoot?.querySelector(".rows");
    if (!e) return;
    const t = /* @__PURE__ */ new Map(), r = e.querySelectorAll(".data-grid-row"), o = this.columns.length;
    for (const n of r) {
      const i = n.querySelector(".cell[data-row]"), s = i ? parseInt(i.getAttribute("data-row") ?? "-1", 10) : -1;
      s >= 0 && t.set(s, n);
    }
    const d = e.querySelectorAll(".master-detail-row");
    for (const n of d) {
      const i = parseInt(n.getAttribute("data-detail-for") ?? "-1", 10), s = i >= 0 ? this.rows[i] : void 0, l = s && this.expandedRows.has(s), c = t.has(i);
      (!l || !c) && (n.remove(), s && this.detailElements.delete(s));
    }
    for (const [n, i] of t) {
      const s = this.rows[n];
      if (!s || !this.expandedRows.has(s)) continue;
      const l = this.detailElements.get(s);
      if (l) {
        l.previousElementSibling !== i && i.after(l);
        continue;
      }
      const c = b(s, n, this.config.detailRenderer, o);
      typeof this.config.detailHeight == "number" && (c.style.height = `${this.config.detailHeight}px`), i.after(c), this.detailElements.set(s, c);
    }
  }
  /**
   * Return total extra height from all expanded detail rows.
   * Used by grid virtualization to adjust scrollbar height.
   */
  getExtraHeight() {
    let e = 0;
    for (const t of this.expandedRows) {
      const r = this.detailElements.get(t);
      if (r)
        e += r.offsetHeight;
      else {
        const o = this.config?.detailHeight;
        e += typeof o == "number" ? o : 150;
      }
    }
    return e;
  }
  /**
   * Return extra height that appears before a given row index.
   * This is the sum of heights of all expanded details whose parent row is before the given index.
   */
  getExtraHeightBefore(e) {
    let t = 0;
    for (const r of this.expandedRows) {
      const o = this.rows.indexOf(r);
      if (o >= 0 && o < e) {
        const d = this.detailElements.get(r);
        if (d)
          t += d.offsetHeight;
        else {
          const n = this.config?.detailHeight;
          t += typeof n == "number" ? n : 150;
        }
      }
    }
    return t;
  }
  /**
   * Adjust the virtualization start index to keep expanded row visible while its detail is visible.
   * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.
   */
  adjustVirtualStart(e, t, r) {
    if (this.expandedRows.size === 0) return e;
    const o = [];
    for (const i of this.expandedRows) {
      const s = this.rows.indexOf(i);
      s >= 0 && o.push({ index: s, row: i });
    }
    o.sort((i, s) => i.index - s.index);
    let d = e, n = 0;
    for (const { index: i, row: s } of o) {
      const l = i * r + n, h = this.detailElements.get(s)?.offsetHeight ?? (typeof this.config?.detailHeight == "number" ? this.config.detailHeight : 150), u = l + r + h;
      n += h, !(i >= e) && u > t && i < d && (d = i);
    }
    return d;
  }
  // #endregion
  // #region Public API
  /**
   * Expand the detail row at the given index.
   * @param rowIndex - Index of the row to expand
   */
  expand(e) {
    const t = this.rows[e];
    t && (this.expandedRows = R(this.expandedRows, t), this.requestRender());
  }
  /**
   * Collapse the detail row at the given index.
   * @param rowIndex - Index of the row to collapse
   */
  collapse(e) {
    const t = this.rows[e];
    t && (this.expandedRows = m(this.expandedRows, t), this.requestRender());
  }
  /**
   * Toggle the detail row at the given index.
   * @param rowIndex - Index of the row to toggle
   */
  toggle(e) {
    const t = this.rows[e];
    t && (this.expandedRows = g(this.expandedRows, t), this.requestRender());
  }
  /**
   * Check if the detail row at the given index is expanded.
   * @param rowIndex - Index of the row to check
   * @returns Whether the detail row is expanded
   */
  isExpanded(e) {
    const t = this.rows[e];
    return t ? E(this.expandedRows, t) : !1;
  }
  /**
   * Expand all detail rows.
   */
  expandAll() {
    for (const e of this.rows)
      this.expandedRows.add(e);
    this.requestRender();
  }
  /**
   * Collapse all detail rows.
   */
  collapseAll() {
    this.expandedRows.clear(), this.requestRender();
  }
  /**
   * Get the indices of all expanded rows.
   * @returns Array of row indices that are expanded
   */
  getExpandedRows() {
    const e = [];
    for (const t of this.expandedRows) {
      const r = this.rows.indexOf(t);
      r >= 0 && e.push(r);
    }
    return e;
  }
  /**
   * Get the detail element for a specific row.
   * @param rowIndex - Index of the row
   * @returns The detail HTMLElement or undefined
   */
  getDetailElement(e) {
    const t = this.rows[e];
    return t ? this.detailElements.get(t) : void 0;
  }
  // #endregion
  // #region Styles
  styles = C;
  // #endregion
}
export {
  v as MasterDetailPlugin
};
//# sourceMappingURL=index.js.map
