const b = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class I {
  /** 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 { ...b, ...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 T(h, e, t, n) {
  if (n.processCell)
    return n.processCell(h, e, t);
  if (h == null) return "";
  if (h instanceof Date) return h.toISOString();
  if (typeof h == "object") return JSON.stringify(h);
  const i = String(h), l = n.delimiter ?? "	", o = n.newline ?? `
`;
  return n.quoteStrings || i.includes(l) || i.includes(o) || i.includes('"') ? `"${i.replace(/"/g, '""')}"` : i;
}
function p(h) {
  const { rows: e, columns: t, selectedIndices: n, config: i } = h, l = i.delimiter ?? "	", o = i.newline ?? `
`, r = t.filter((s) => !s.hidden && !s.field.startsWith("__")), c = [];
  if (i.includeHeaders) {
    const s = r.map((d) => {
      const f = d.header || d.field;
      return f.includes(l) || f.includes(o) || f.includes('"') ? `"${f.replace(/"/g, '""')}"` : f;
    });
    c.push(s.join(l));
  }
  const u = [...n instanceof Set ? [...n] : n].sort((s, d) => s - d);
  for (const s of u) {
    const d = e[s];
    if (!d) continue;
    const f = r.map(
      (g) => T(d[g.field], g.field, d, i)
    );
    c.push(f.join(l));
  }
  return c.join(o);
}
async function C(h) {
  try {
    return await navigator.clipboard.writeText(h), !0;
  } catch {
    const e = document.createElement("textarea");
    e.value = h, e.style.position = "fixed", e.style.opacity = "0", e.style.pointerEvents = "none", document.body.appendChild(e), e.select();
    const t = document.execCommand("copy");
    return document.body.removeChild(e), t;
  }
}
function y(h, e) {
  const t = e.delimiter ?? "	", n = e.newline ?? `
`, i = h.replace(/\r\n/g, `
`).replace(/\r/g, `
`), l = [];
  let o = [], r = "", c = !1;
  for (let a = 0; a < i.length; a++) {
    const u = i[a];
    u === '"' && !c ? c = !0 : u === '"' && c ? i[a + 1] === '"' ? (r += '"', a++) : c = !1 : u === t && !c ? (o.push(r), r = "") : u === n && !c ? (o.push(r), r = "", (o.length > 1 || o.some((s) => s.trim() !== "")) && l.push(o), o = []) : r += u;
  }
  return o.push(r), (o.length > 1 || o.some((a) => a.trim() !== "")) && l.push(o), l;
}
async function x() {
  try {
    return await navigator.clipboard.readText();
  } catch {
    return "";
  }
}
class D extends I {
  name = "clipboard";
  version = "1.0.0";
  get defaultConfig() {
    return {
      includeHeaders: !1,
      delimiter: "	",
      newline: `
`,
      quoteStrings: !1
    };
  }
  // #region Internal State
  /** The last copied text (for reference/debugging) */
  lastCopied = null;
  // #endregion
  // #region Lifecycle
  detach() {
    this.lastCopied = null;
  }
  // #endregion
  // #region Event Handlers
  onKeyDown(e) {
    const t = (e.ctrlKey || e.metaKey) && e.key === "c", n = (e.ctrlKey || e.metaKey) && e.key === "v";
    return t ? (this.#t(e.target), !0) : n ? (this.#n(), !0) : !1;
  }
  // #endregion
  // #region Private Methods
  /**
   * Handle copy operation
   */
  #t(e) {
    const t = this.#e(), n = t?.getSelectedRows() ?? [], i = n.length > 0, l = t?.getRanges() ?? [], o = l.length > 0, r = t?.getSelectedCell() != null;
    let c, a, u;
    if (i && t)
      c = p({
        rows: this.rows,
        columns: [...this.columns],
        selectedIndices: n,
        config: this.config
      }), a = n.length, u = this.columns.filter((s) => !s.hidden && !s.field.startsWith("__")).length;
    else if (o && t) {
      const s = l[l.length - 1], d = this.#i({
        startRow: s.from.row,
        startCol: s.from.col,
        endRow: s.to.row,
        endCol: s.to.col
      });
      c = d.text, a = d.rowCount, u = d.columnCount;
    } else if (r && t) {
      const s = t.getSelectedCell(), d = this.#s(s.row, s.col);
      if (!d) return;
      c = d.text, a = 1, u = 1;
    } else {
      const s = this.#o(e);
      if (!s) return;
      c = s.text, a = 1, u = 1;
    }
    C(c).then(() => {
      this.lastCopied = { text: c, timestamp: Date.now() }, this.emit("copy", { text: c, rowCount: a, columnCount: u });
    });
  }
  /**
   * Handle paste operation
   */
  #n() {
    x().then((e) => {
      if (!e) return;
      const t = y(e, this.config);
      this.emit("paste", { rows: t, text: e });
    });
  }
  /**
   * Get the selection plugin instance if available.
   */
  #e() {
    try {
      const e = this.grid;
      if (e?._plugins) {
        for (const t of e._plugins)
          if (t.name === "selection")
            return t;
      }
    } catch {
    }
  }
  /**
   * Build text for a single cell by row/col index.
   */
  #s(e, t) {
    const n = this.rows[e];
    if (!n) return null;
    const i = this.columns[t];
    if (!i) return null;
    const l = n[i.field], o = i.header || i.field;
    let r;
    if (this.config.includeHeaders) {
      const c = l == null ? "" : String(l);
      r = `${o}: ${c}`;
    } else
      r = l == null ? "" : String(l);
    return { text: r };
  }
  /**
   * Build text for a rectangular range of cells.
   */
  #i(e) {
    const { startRow: t, startCol: n, endRow: i, endCol: l } = e, o = Math.min(t, i), r = Math.max(t, i), c = Math.min(n, l), a = Math.max(n, l), u = this.config.delimiter ?? "	", s = this.config.newline ?? `
`, d = [], f = this.columns.slice(c, a + 1);
    if (this.config.includeHeaders) {
      const g = f.map((m) => m.header || m.field);
      d.push(g.join(u));
    }
    for (let g = o; g <= r; g++) {
      const m = this.rows[g];
      if (!m) continue;
      const S = f.map((R) => {
        const w = m[R.field];
        return w == null ? "" : w instanceof Date ? w.toISOString() : String(w);
      });
      d.push(S.join(u));
    }
    return {
      text: d.join(s),
      rowCount: r - o + 1,
      columnCount: a - c + 1
    };
  }
  /**
   * Build text for a single focused cell from DOM.
   * Used when selection plugin is not available or no rows are selected.
   */
  #o(e) {
    const t = e.closest("[data-field-cache]");
    if (!t) return null;
    const n = t.dataset.fieldCache;
    if (!n) return null;
    const i = t.dataset.row;
    if (!i) return null;
    const l = parseInt(i, 10);
    if (isNaN(l)) return null;
    const o = this.rows[l];
    if (!o) return null;
    const r = o[n], a = this.columns.find((s) => s.field === n)?.header || n;
    let u;
    if (this.config.includeHeaders) {
      const s = r == null ? "" : String(r);
      u = `${a}: ${s}`;
    } else
      u = r == null ? "" : String(r);
    return { text: u, field: n, value: r };
  }
  // #endregion
  // #region Public API
  /**
   * Copy currently selected rows to clipboard.
   * @returns The copied text
   */
  async copy() {
    const t = this.#e()?.getSelectedRows() ?? [], n = p({
      rows: this.rows,
      columns: [...this.columns],
      selectedIndices: t,
      config: this.config
    });
    return await C(n), this.lastCopied = { text: n, timestamp: Date.now() }, n;
  }
  /**
   * Copy specific rows by index to clipboard.
   * @param indices - Array of row indices to copy
   * @returns The copied text
   */
  async copyRows(e) {
    const t = p({
      rows: this.rows,
      columns: [...this.columns],
      selectedIndices: e,
      config: this.config
    });
    return await C(t), this.lastCopied = { text: t, timestamp: Date.now() }, t;
  }
  /**
   * Read and parse clipboard content.
   * @returns Parsed 2D array of cell values, or null if clipboard is empty
   */
  async paste() {
    const e = await x();
    return e ? y(e, this.config) : null;
  }
  /**
   * Get the last copied text and timestamp.
   * @returns The last copied info or null
   */
  getLastCopied() {
    return this.lastCopied;
  }
  // #endregion
}
export {
  D as ClipboardPlugin
};
//# sourceMappingURL=index.js.map
