/**
 * Layer Panel manager for dwg-dxf-viewer-sample.
 * Renders layer items, color swatches, entity counts, search filters, and visibility toggles.
 */

import type { Viewer2D } from './viewer2d/Viewer2D';

export interface LayerItem {
  name: string;
  colorHex: string;
  count: number;
  visible: boolean;
}

export class LayerPanelManager {
  private viewer: Viewer2D;
  private listEl: HTMLElement;
  private countEl: HTMLElement | null;
  private searchInputEl: HTMLInputElement | null;
  private hiddenLayers: Set<string> = new Set();
  private searchQuery: string = '';

  constructor(
    viewer: Viewer2D,
    listEl: HTMLElement,
    options?: {
      countEl?: HTMLElement | null;
      searchInputEl?: HTMLInputElement | null;
      showAllBtnEl?: HTMLElement | null;
      hideAllBtnEl?: HTMLElement | null;
    }
  ) {
    this.viewer = viewer;
    this.listEl = listEl;
    this.countEl = options?.countEl || null;
    this.searchInputEl = options?.searchInputEl || null;

    if (this.searchInputEl) {
      this.searchInputEl.addEventListener('input', (e) => {
        this.searchQuery = (e.target as HTMLInputElement).value.trim().toLowerCase();
        this.render();
      });
    }

    if (options?.showAllBtnEl) {
      options.showAllBtnEl.addEventListener('click', () => this.showAllLayers());
    }

    if (options?.hideAllBtnEl) {
      options.hideAllBtnEl.addEventListener('click', () => this.hideAllLayers());
    }

    // Delegated click handling: bound once, so render() never has to re-attach
    // a listener per row (that was O(layers) DOM work on every single toggle).
    this.listEl.addEventListener('click', (e) => {
      const row = (e.target as HTMLElement)?.closest?.('.layer-row') as HTMLElement | null;
      if (!row) return;
      const layerName = row.dataset.name;
      if (layerName) this.toggleLayer(layerName);
    });
  }

  /** Refresh and re-render layer list from Viewer2D state */
  public update(): void {
    this.hiddenLayers.clear();
    const layers = this.viewer.getLayerInfo() || [];
    for (const l of layers) {
      if (!l.visible) {
        this.hiddenLayers.add(l.name);
      }
    }
    this.render();
  }

  public showAllLayers(): void {
    this.hiddenLayers.clear();
    this.viewer.setHiddenLayers(this.hiddenLayers);
    this.syncRows();
  }

  public hideAllLayers(): void {
    const layers = this.viewer.getLayerInfo() || [];
    for (const l of layers) {
      this.hiddenLayers.add(l.name);
    }
    this.viewer.setHiddenLayers(this.hiddenLayers);
    this.syncRows();
  }

  public toggleLayer(name: string): void {
    if (this.hiddenLayers.has(name)) {
      this.hiddenLayers.delete(name);
    } else {
      this.hiddenLayers.add(name);
    }
    this.viewer.setHiddenLayers(this.hiddenLayers);
    this.syncRow(name);
  }

  /** Patch one row in place — a full render() would rebuild the whole list. */
  private syncRow(name: string): void {
    const row = this.listEl?.querySelector<HTMLElement>(
      `.layer-row[data-name="${cssEscape(name)}"]`
    );
    if (!row) return;
    this.paintRow(row, !this.hiddenLayers.has(name));
  }

  /** Patch every row (show-all / hide-all) without touching the DOM structure. */
  private syncRows(): void {
    if (!this.listEl) return;
    this.listEl.querySelectorAll<HTMLElement>('.layer-row').forEach((row) => {
      const name = row.dataset.name;
      if (name != null) this.paintRow(row, !this.hiddenLayers.has(name));
    });
  }

  private paintRow(row: HTMLElement, visible: boolean): void {
    row.style.opacity = visible ? '1' : '0.4';
    const btn = row.querySelector<HTMLElement>('.eye-btn');
    if (btn) {
      btn.textContent = visible ? '👁️' : '🕶️';
      btn.title = visible ? '숨기기' : '표시하기';
    }
  }

  public render(): void {
    if (!this.listEl) return;

    const layers: LayerItem[] = this.viewer.getLayerInfo() || [];

    if (this.countEl) {
      this.countEl.textContent = `${layers.length}개`;
    }

    if (!layers.length) {
      this.listEl.innerHTML = `
        <div style="padding:30px 16px;text-align:center;color:var(--muted);font-size:12px">
          도면에 레이어 정보가 없습니다
        </div>`;
      return;
    }

    const filtered = layers.filter((l) =>
      l.name.toLowerCase().includes(this.searchQuery)
    );

    if (!filtered.length) {
      this.listEl.innerHTML = `
        <div style="padding:24px 16px;text-align:center;color:var(--muted);font-size:12px">
          검색 결과가 없습니다 ("${escapeHtml(this.searchQuery)}")
        </div>`;
      return;
    }

    this.listEl.innerHTML = filtered
      .map((l) => {
        const isVisible = !this.hiddenLayers.has(l.name);
        const opacity = isVisible ? '1' : '0.4';
        const eyeIcon = isVisible ? '👁️' : '🕶️';
        const eyeTitle = isVisible ? '숨기기' : '표시하기';

        return `
        <div class="layer-row" data-name="${escapeHtml(l.name)}"
             style="display:flex;align-items:center;gap:10px;padding:7px 10px;border-bottom:1px solid var(--line);opacity:${opacity};transition:opacity .15s;user-select:none">
          <span style="width:10px;height:10px;border-radius:3px;background:${l.colorHex || '#888'};flex-shrink:0;box-shadow:0 0 0 1px rgba(255,255,255,.15)"></span>
          <span class="mono" style="flex:1;font-size:12px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${escapeHtml(l.name)}">
            ${escapeHtml(l.name)}
          </span>
          <span class="mono" style="font-size:11px;color:var(--muted);background:rgba(255,255,255,.06);padding:2px 6px;border-radius:4px;flex-shrink:0">
            ${l.count}
          </span>
          <button type="button" class="eye-btn" data-toggle="${escapeHtml(l.name)}" title="${eyeTitle}"
                  style="background:none;border:none;cursor:pointer;font-size:13px;padding:2px 4px;border-radius:4px;color:var(--ink);transition:transform .1s">
            ${eyeIcon}
          </button>
        </div>`;
      })
      .join('');
    // Clicks are handled by the delegated listener bound in the constructor.
  }
}

/** Escape a layer name for use inside a double-quoted CSS attribute selector. */
function cssEscape(str: string): string {
  return str.replace(/["\\]/g, '\\$&');
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}
