// src/flow/viewers/TreeViewer.ts
//
// TreeViewer - A MatrixActor for displaying hierarchical data as an interactive tree.
// Actually visual - not just JSON!

import { MatrixActor } from '../../core/MatrixActor';
import { MatrixActorHtmlElement } from '../../framework/MatrixActorHtmlElement';
import { ShellRegistry } from '../ShellRegistry';
import { TopicRouter } from '../../engine/messaging/TopicRouter';
import type { IMatrixContext } from '../../engine/core/IMatrixContext';
import type { MxEnvelope } from '../../engine/messaging/MxEnvelope';

/**
 * JSON-compatible value type for tree display.
 * Recursive type representing any valid JSON structure.
 */
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };

/**
 * TreeViewer - Displays data as an expandable tree structure.
 *
 * Accepts:
 *   - setData: { data: any, label?: string }
 *   - expandAll: {}
 *   - collapseAll: {}
 *   - toggleNode: { path: string }
 *
 * Emits:
 *   - nodeSelected: { path: string, value: any }
 *   - nodeToggled: { path: string, expanded: boolean }
 */
export class TreeViewer extends MatrixActor {
  static accepts = {
    setData: { data: 'any', label: 'string?' },
    expandAll: {},
    collapseAll: {},
    toggleNode: { path: 'string' },
  };

  static emits = {
    nodeSelected: { path: 'string', value: 'any' },
    nodeToggled: { path: 'string', expanded: 'boolean' },
    treeChanged: { treeHTML: 'string', label: 'string' },
  };

  static template = `
    <div class="tree-viewer">
      <div class="tree-header">
        <span id="label" class="label">Tree View</span>
        <div class="actions">
          <button id="expandBtn" title="Expand all">▼ All</button>
          <button id="collapseBtn" title="Collapse all">▶ All</button>
        </div>
      </div>
      <div id="treeContent" class="tree-content"></div>
    </div>
    <style>
      :host { display: block; }
      .tree-viewer {
        border: 1px solid var(--mx-color-border-light, #e0e0e0);
        border-radius: 8px;
        background: var(--mx-color-surface-alt, #fafafa);
        overflow: hidden;
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 13px;
      }
      .tree-header {
        padding: 10px 12px;
        background: linear-gradient(135deg, var(--mx-color-primary, #667eea) 0%, var(--mx-color-primary-hover, #764ba2) 100%);
        color: white;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .label {
        font-weight: 600;
        font-size: 14px;
      }
      .actions {
        display: flex;
        gap: 6px;
      }
      .actions button {
        padding: 4px 10px;
        border: 1px solid rgba(255,255,255,0.3);
        border-radius: 4px;
        background: rgba(255,255,255,0.1);
        color: white;
        cursor: pointer;
        font-size: 11px;
      }
      .actions button:hover {
        background: rgba(255,255,255,0.25);
      }
      .tree-content {
        padding: 12px;
        max-height: 400px;
        overflow: auto;
      }
      .tree-node {
        margin-left: 0;
      }
      .tree-node .tree-node {
        margin-left: 20px;
        border-left: 1px dashed var(--mx-color-border, #ccc);
        padding-left: 10px;
      }
      .node-row {
        display: flex;
        align-items: center;
        padding: 3px 6px;
        border-radius: 4px;
        cursor: pointer;
        gap: 6px;
      }
      .node-row:hover {
        background: var(--mx-color-surface-hover, #e3f2fd);
      }
      .node-row.selected {
        background: var(--mx-color-surface-hover, #bbdefb);
      }
      .toggle {
        width: 16px;
        height: 16px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 10px;
        color: var(--mx-color-text-secondary, #666);
        flex-shrink: 0;
      }
      .toggle.expandable {
        cursor: pointer;
        background: var(--mx-color-surface-inset, #f0f0f0);
        border-radius: 3px;
      }
      .toggle.expandable:hover {
        background: var(--mx-color-border, #ddd);
      }
      .node-key {
        color: var(--mx-color-primary, #1565c0);
        font-weight: 500;
      }
      .node-colon {
        color: var(--mx-color-text-secondary, #666);
      }
      .node-value {
        color: var(--mx-color-text, #333);
      }
      .node-value.string { color: var(--mx-color-success, #2e7d32); }
      .node-value.number { color: var(--mx-color-warning, #f57c00); }
      .node-value.boolean { color: var(--mx-color-primary-hover, #7b1fa2); }
      .node-value.null { color: var(--mx-color-text-muted, #999); font-style: italic; }
      .node-type {
        font-size: 10px;
        color: var(--mx-color-text-muted, #999);
        background: var(--mx-color-surface-inset, #f5f5f5);
        padding: 1px 5px;
        border-radius: 3px;
        margin-left: 4px;
      }
      .children {
        overflow: hidden;
      }
      .children.collapsed {
        display: none;
      }
    </style>
  `;

  private _data: JsonValue = null;
  private _label: string = 'Tree View';
  private _expandedPaths: Set<string> = new Set();
  private _selectedPath: string | null = null;

  get data(): JsonValue {
    return this._data;
  }

  get label(): string {
    return this._label;
  }

  get expandedPaths(): Set<string> {
    return this._expandedPaths;
  }

  get selectedPath(): string | null {
    return this._selectedPath;
  }

  onSetData(params: { data: JsonValue; label?: string }): void {
    this._data = params.data;
    if (params.label) {
      this._label = params.label;
    }
    // Auto-expand first level
    this._expandedPaths.add('$');
    this._emitTreeChanged();
  }

  onExpandAll(): void {
    this._expandAllPaths(this._data, '$');
    this._emitTreeChanged();
  }

  onCollapseAll(): void {
    this._expandedPaths.clear();
    this._expandedPaths.add('$'); // Keep root expanded
    this._emitTreeChanged();
  }

  onToggleNode(params: { path: string }): void {
    const { path } = params;
    const wasExpanded = this._expandedPaths.has(path);
    if (wasExpanded) {
      this._expandedPaths.delete(path);
    } else {
      this._expandedPaths.add(path);
    }
    this.emit('nodeToggled', { path, expanded: !wasExpanded });
    this._emitTreeChanged();
  }

  private _emitTreeChanged(): void {
    this.emit('treeChanged', { treeHTML: this.buildTreeHTML(), label: this._label });
  }

  selectNode(path: string, value: JsonValue): void {
    this._selectedPath = path;
    this.emit('nodeSelected', { path, value });
  }

  isExpanded(path: string): boolean {
    return this._expandedPaths.has(path);
  }

  private _expandAllPaths(data: JsonValue, path: string): void {
    this._expandedPaths.add(path);
    if (data && typeof data === 'object') {
      if (Array.isArray(data)) {
        data.forEach((item, i) => {
          this._expandAllPaths(item, `${path}[${i}]`);
        });
      } else {
        Object.keys(data).forEach((key) => {
          this._expandAllPaths((data as Record<string, JsonValue>)[key], `${path}.${key}`);
        });
      }
    }
  }

  /**
   * Build tree HTML - called by shell for rendering
   */
  buildTreeHTML(): string {
    return this._buildNodeHTML(this._data, '$', '');
  }

  private _buildNodeHTML(value: JsonValue, path: string, key: string): string {
    const isObject = value !== null && typeof value === 'object';
    const isArray = Array.isArray(value);
    const isExpanded = this._expandedPaths.has(path);
    const isSelected = this._selectedPath === path;

    if (!isObject) {
      // Leaf node
      const valueClass = value === null ? 'null' : typeof value;
      const displayValue = value === null ? 'null' :
                          typeof value === 'string' ? `"${value}"` :
                          String(value);
      return `
        <div class="tree-node">
          <div class="node-row${isSelected ? ' selected' : ''}" data-path="${path}">
            <span class="toggle"></span>
            ${key ? `<span class="node-key">${key}</span><span class="node-colon">:</span>` : ''}
            <span class="node-value ${valueClass}">${this._escapeHtml(displayValue)}</span>
          </div>
        </div>
      `;
    }

    // Object or Array
    const entries: [string | number, JsonValue][] = isArray
      ? value.map((v, i) => [i, v])
      : Object.entries(value as Record<string, JsonValue>);
    const count = entries.length;
    const typeLabel = isArray ? `Array[${count}]` : `Object{${count}}`;

    const childrenHtml = entries.map((entry) => {
      const k = entry[0];
      const v = entry[1];
      const childPath = isArray ? `${path}[${k}]` : `${path}.${k}`;
      return this._buildNodeHTML(v, childPath, String(k));
    }).join('');

    return `
      <div class="tree-node">
        <div class="node-row${isSelected ? ' selected' : ''}" data-path="${path}">
          <span class="toggle expandable" data-toggle="${path}">${isExpanded ? '▼' : '▶'}</span>
          ${key ? `<span class="node-key">${key}</span><span class="node-colon">:</span>` : ''}
          <span class="node-type">${typeLabel}</span>
        </div>
        <div class="children${isExpanded ? '' : ' collapsed'}">
          ${childrenHtml}
        </div>
      </div>
    `;
  }

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

/**
 * TreeViewerShell - DOM wrapper for TreeViewer (Pattern B: separate actor)
 */
export class TreeViewerShell extends MatrixActorHtmlElement {
  static getActorClass() { return TreeViewer; }
  static moduleUrl = import.meta.url;

  private _domWired = false;

  async onBootstrap(): Promise<void> {
    const labelEl = this.shadowRoot?.getElementById('label');
    const treeContent = this.shadowRoot?.getElementById('treeContent');
    const expandBtn = this.shadowRoot?.getElementById('expandBtn');
    const collapseBtn = this.shadowRoot?.getElementById('collapseBtn');

    // Render tree content from transport payload
    const renderTree = (payload: { treeHTML: string; label: string }) => {
      if (treeContent) {
        treeContent.innerHTML = payload.treeHTML;
      }
      if (labelEl) {
        labelEl.textContent = payload.label || 'Tree View';
      }
    };

    // Subscribe to $events BEFORE sendToSelf to avoid missing the initial emit
    if (this._context) {
      const eventsTopic = TopicRouter.events(this._context.mount);
      this._context.transport.subscribe(eventsTopic, (payload: unknown) => {
        const envelope = payload as MxEnvelope;
        if (envelope?.op === 'treeChanged') {
          renderTree(envelope.payload as { treeHTML: string; label: string });
        }
      });
    }

    // Initialize from props (passed via lifecycle.childAdded event)
    const props = (this as unknown as { _initialProps?: { data?: JsonValue; label?: string } })._initialProps;
    if (props?.data !== undefined) {
      this.sendToSelf('setData', { data: props.data, label: props.label });
    }

    if (!this._domWired) {
      this._domWired = true;

      // Wire up toggle handlers via event delegation
      treeContent?.addEventListener('click', (e: Event) => {
        const target = e.target as HTMLElement;
        const toggle = target.closest('[data-toggle]') as HTMLElement;
        if (toggle) {
          const path = toggle.dataset.toggle;
          if (path) {
            this.sendToSelf('toggleNode', { path });
          }
        }
      });

      // Wire expand/collapse buttons
      expandBtn?.addEventListener('click', () => {
        this.sendToSelf('expandAll', {});
      });

      collapseBtn?.addEventListener('click', () => {
        this.sendToSelf('collapseAll', {});
      });
    }
  }
}

// Register with ShellRegistry
ShellRegistry.register('TreeViewer', {
  shellTag: 'tree-viewer',
  moduleUrl: import.meta.url,
});

// Register custom element
if (typeof customElements !== 'undefined' && !customElements.get('tree-viewer')) {
  customElements.define('tree-viewer', TreeViewerShell);
}
