const m = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class w {
  /** 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 { ...m, ...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
}
const b = {
  sum: (n, e) => n.reduce((t, i) => t + (Number(i[e]) || 0), 0),
  avg: (n, e) => {
    const t = n.reduce((i, r) => i + (Number(r[e]) || 0), 0);
    return n.length ? t / n.length : 0;
  },
  count: (n) => n.length,
  min: (n, e) => Math.min(...n.map((t) => Number(t[e]) || 1 / 0)),
  max: (n, e) => Math.max(...n.map((t) => Number(t[e]) || -1 / 0)),
  first: (n, e) => n[0]?.[e],
  last: (n, e) => n[n.length - 1]?.[e]
}, _ = /* @__PURE__ */ new Map(), f = {
  /**
   * Register a custom aggregator function.
   */
  register(n, e) {
    _.set(n, e);
  },
  /**
   * Unregister a custom aggregator function.
   */
  unregister(n) {
    _.delete(n);
  },
  /**
   * Get an aggregator function by reference.
   */
  get(n) {
    if (n !== void 0)
      return typeof n == "function" ? n : _.get(n) ?? b[n];
  },
  /**
   * Run an aggregator on a set of rows.
   */
  run(n, e, t, i) {
    const r = this.get(n);
    return r ? r(e, t, i) : void 0;
  },
  /**
   * Check if an aggregator exists.
   */
  has(n) {
    return _.has(n) || n in b;
  },
  /**
   * List all available aggregator names.
   */
  list() {
    return [...Object.keys(b), ..._.keys()];
  }
};
f.register.bind(f);
f.unregister.bind(f);
f.get.bind(f);
const R = f.run.bind(f);
f.list.bind(f);
function v({ rows: n, config: e, expanded: t }) {
  const i = e.groupOn;
  if (typeof i != "function")
    return [];
  const r = { key: "__root__", value: null, depth: -1, rows: [], children: /* @__PURE__ */ new Map() };
  if (n.forEach((s) => {
    let u = i(s);
    u == null || u === !1 ? u = ["__ungrouped__"] : Array.isArray(u) || (u = [u]);
    let o = r;
    u.forEach((c, x) => {
      const a = c == null ? "∅" : String(c), g = o.key === "__root__" ? a : o.key + "||" + a;
      let p = o.children.get(a);
      p || (p = { key: g, value: c, depth: x, rows: [], children: /* @__PURE__ */ new Map(), parent: o }, o.children.set(a, p)), o = p;
    }), o.rows.push(s);
  }), r.children.size === 1 && r.children.has("__ungrouped__") && r.children.get("__ungrouped__").rows.length === n.length)
    return [];
  const d = [], l = (s) => {
    if (s === r) {
      s.children.forEach((o) => l(o));
      return;
    }
    const u = t.has(s.key);
    d.push({
      kind: "group",
      key: s.key,
      value: s.value,
      depth: s.depth,
      rows: s.rows,
      expanded: u
    }), u && (s.children.size ? s.children.forEach((o) => l(o)) : s.rows.forEach((o) => d.push({ kind: "data", row: o, rowIndex: n.indexOf(o) })));
  };
  return l(r), d;
}
function C(n, e) {
  const t = new Set(n);
  return t.has(e) ? t.delete(e) : t.add(e), t;
}
function A(n) {
  const e = /* @__PURE__ */ new Set();
  for (const t of n)
    t.kind === "group" && e.add(t.key);
  return e;
}
function K() {
  return /* @__PURE__ */ new Set();
}
function E(n) {
  return n.kind !== "group" ? 0 : n.rows.length;
}
const G = '.group-row{background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500}.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:4px;font-size:10px}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:2px}.group-label{display:inline-flex;align-items:center;gap:8px}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:.85em;font-weight:400}[data-group-depth="0"] .group-label{padding-left:0}[data-group-depth="1"] .group-label{padding-left:20px}[data-group-depth="2"] .group-label{padding-left:40px}[data-group-depth="3"] .group-label{padding-left:60px}[data-group-depth="4"] .group-label{padding-left:80px}';
class S extends w {
  name = "groupingRows";
  version = "1.0.0";
  get defaultConfig() {
    return {
      defaultExpanded: !1,
      showRowCount: !0,
      indentWidth: 20,
      aggregators: {}
    };
  }
  // #region Internal State
  expandedKeys = /* @__PURE__ */ new Set();
  flattenedRows = [];
  isActive = !1;
  // #endregion
  // #region Lifecycle
  detach() {
    this.expandedKeys.clear(), this.flattenedRows = [], this.isActive = !1;
  }
  // #endregion
  // #region Hooks
  /**
   * Auto-detect grouping configuration from grid config.
   * Called by plugin system to determine if plugin should activate.
   */
  static detect(e, t) {
    return typeof t?.groupOn == "function" || typeof t?.enableRowGrouping == "boolean";
  }
  processRows(e) {
    const t = this.config;
    if (typeof t.groupOn != "function")
      return this.isActive = !1, this.flattenedRows = [], [...e];
    const i = v({
      rows: e,
      config: t,
      expanded: this.expandedKeys
    });
    return i.length === 0 ? (this.isActive = !1, this.flattenedRows = [], [...e]) : (this.isActive = !0, this.flattenedRows = i, i.map((r) => r.kind === "group" ? {
      __isGroupRow: !0,
      __groupKey: r.key,
      __groupValue: r.value,
      __groupDepth: r.depth,
      __groupRows: r.rows,
      __groupExpanded: r.expanded,
      __groupRowCount: E(r)
    } : r.row));
  }
  onCellClick(e) {
    const t = e.row;
    if (t?.__isGroupRow && e.originalEvent.target?.closest(".group-toggle"))
      return this.toggle(t.__groupKey), !0;
  }
  /**
   * Render a row. Returns true if we handled the row (group row), false otherwise.
   */
  renderRow(e, t, i) {
    if (!e?.__isGroupRow)
      return !1;
    const r = this.config;
    if (r.groupRowRenderer) {
      const s = () => {
        this.toggle(e.__groupKey);
      }, u = r.groupRowRenderer({
        key: e.__groupKey,
        value: e.__groupValue,
        depth: e.__groupDepth,
        rows: e.__groupRows,
        expanded: e.__groupExpanded,
        toggleExpand: s
      });
      if (u)
        return t.className = "group-row", t.__isCustomRow = !0, t.setAttribute("data-group-depth", String(e.__groupDepth)), typeof u == "string" ? t.innerHTML = u : (t.innerHTML = "", t.appendChild(u)), !0;
    }
    const d = () => {
      this.toggle(e.__groupKey);
    };
    return t.className = "group-row", t.__isCustomRow = !0, t.setAttribute("data-group-depth", String(e.__groupDepth)), t.setAttribute("role", "row"), t.setAttribute("aria-expanded", String(e.__groupExpanded)), t.style.paddingLeft = `${(e.__groupDepth || 0) * (r.indentWidth ?? 20)}px`, t.innerHTML = "", r.fullWidth !== !1 ? this.renderFullWidthGroupRow(e, t, d) : this.renderPerColumnGroupRow(e, t, d), !0;
  }
  afterRender() {
  }
  // #endregion
  // #region Private Rendering Helpers
  renderFullWidthGroupRow(e, t, i) {
    const r = this.config, d = document.createElement("div");
    d.className = "cell group-full", d.style.gridColumn = "1 / -1", d.setAttribute("role", "gridcell");
    const l = document.createElement("button");
    l.type = "button", l.className = "group-toggle", l.setAttribute("aria-label", e.__groupExpanded ? "Collapse group" : "Expand group"), this.setIcon(l, this.resolveIcon(e.__groupExpanded ? "collapse" : "expand")), l.addEventListener("click", (o) => {
      o.stopPropagation(), i();
    }), d.appendChild(l);
    const s = document.createElement("span");
    s.className = "group-label";
    const u = r.formatLabel ? r.formatLabel(e.__groupValue, e.__groupDepth || 0, e.__groupKey) : String(e.__groupValue);
    if (s.textContent = u, d.appendChild(s), r.showRowCount !== !1) {
      const o = document.createElement("span");
      o.className = "group-count", o.textContent = `(${e.__groupRowCount ?? e.__groupRows?.length ?? 0})`, d.appendChild(o);
    }
    t.appendChild(d);
  }
  renderPerColumnGroupRow(e, t, i) {
    const r = this.config, d = r.aggregators ?? {}, l = this.columns, s = e.__groupRows ?? [], o = this.shadowRoot?.querySelector(".body")?.style.gridTemplateColumns || "";
    o && (t.style.display = "grid", t.style.gridTemplateColumns = o), l.forEach((c, x) => {
      const a = document.createElement("div");
      if (a.className = "cell group-cell", a.setAttribute("data-col", String(x)), a.setAttribute("role", "gridcell"), x === 0) {
        const g = document.createElement("button");
        g.type = "button", g.className = "group-toggle", g.setAttribute("aria-label", e.__groupExpanded ? "Collapse group" : "Expand group"), this.setIcon(g, this.resolveIcon(e.__groupExpanded ? "collapse" : "expand")), g.addEventListener("click", (h) => {
          h.stopPropagation(), i();
        }), a.appendChild(g);
        const p = document.createElement("span"), y = d[c.field];
        if (y) {
          const h = R(y, s, c.field, c);
          p.textContent = h != null ? String(h) : String(e.__groupValue);
        } else {
          const h = r.formatLabel ? r.formatLabel(e.__groupValue, e.__groupDepth || 0, e.__groupKey) : String(e.__groupValue);
          p.textContent = h;
        }
        if (a.appendChild(p), r.showRowCount !== !1) {
          const h = document.createElement("span");
          h.className = "group-count", h.textContent = ` (${s.length})`, a.appendChild(h);
        }
      } else {
        const g = d[c.field];
        if (g) {
          const p = R(g, s, c.field, c);
          a.textContent = p != null ? String(p) : "";
        } else
          a.textContent = "";
      }
      t.appendChild(a);
    });
  }
  // #endregion
  // #region Public API
  /**
   * Expand all groups.
   */
  expandAll() {
    this.expandedKeys = A(this.flattenedRows), this.requestRender();
  }
  /**
   * Collapse all groups.
   */
  collapseAll() {
    this.expandedKeys = K(), this.requestRender();
  }
  /**
   * Toggle expansion of a specific group.
   * @param key - The group key to toggle
   */
  toggle(e) {
    this.expandedKeys = C(this.expandedKeys, e);
    const t = this.flattenedRows.find((i) => i.kind === "group" && i.key === e);
    this.emit("group-toggle", {
      key: e,
      expanded: this.expandedKeys.has(e),
      value: t?.value,
      depth: t?.depth ?? 0
    }), this.requestRender();
  }
  /**
   * Check if a specific group is expanded.
   * @param key - The group key to check
   * @returns Whether the group is expanded
   */
  isExpanded(e) {
    return this.expandedKeys.has(e);
  }
  /**
   * Expand a specific group.
   * @param key - The group key to expand
   */
  expand(e) {
    this.expandedKeys.has(e) || (this.expandedKeys = /* @__PURE__ */ new Set([...this.expandedKeys, e]), this.requestRender());
  }
  /**
   * Collapse a specific group.
   * @param key - The group key to collapse
   */
  collapse(e) {
    if (this.expandedKeys.has(e)) {
      const t = new Set(this.expandedKeys);
      t.delete(e), this.expandedKeys = t, this.requestRender();
    }
  }
  /**
   * Get the current group state.
   * @returns Group state information
   */
  getGroupState() {
    const e = this.flattenedRows.filter((t) => t.kind === "group");
    return {
      isActive: this.isActive,
      expandedCount: this.expandedKeys.size,
      totalGroups: e.length,
      expandedKeys: [...this.expandedKeys]
    };
  }
  /**
   * Get the total count of visible rows (including group headers).
   * @returns Number of visible rows
   */
  getRowCount() {
    return this.flattenedRows.length;
  }
  /**
   * Refresh the grouped row model.
   * Call this after modifying groupOn or other config options.
   */
  refreshGroups() {
    this.requestRender();
  }
  /**
   * Get current expanded group keys.
   * @returns Array of expanded group keys
   */
  getExpandedGroups() {
    return [...this.expandedKeys];
  }
  /**
   * Get the flattened row model.
   * @returns Array of render rows (groups + data rows)
   */
  getFlattenedRows() {
    return this.flattenedRows;
  }
  /**
   * Check if grouping is currently active.
   * @returns Whether grouping is active
   */
  isGroupingActive() {
    return this.isActive;
  }
  /**
   * Set the groupOn function dynamically.
   * @param fn - The groupOn function or undefined to disable
   */
  setGroupOn(e) {
    this.config.groupOn = e, this.requestRender();
  }
  // #endregion
  // #region Styles
  styles = G;
  // #endregion
}
export {
  S as GroupingRowsPlugin
};
//# sourceMappingURL=index.js.map
