// src/framework/components/MxDock.ts
//
// MxDock — Network-controllable tabbed dock panel.
//
// Manages a tab bar + content area. Tabs are added/removed via the actor
// protocol (pin/unpin). Content children are created dynamically using
// Matrix's addChild/removeChild with shellTag, which the framework's
// _handleChildAdded/_handleChildRemoved wires into [data-slot="content"].
//
// NOT layout-only: this component parents its dynamic children.
// Mount paths: dock.transportlog, dock.vlm-debug, etc.

import { MatrixActorHtmlElement } from '../MatrixActorHtmlElement';
import { MatrixActor } from '../../core/MatrixActor';

interface TabInfo {
  name: string;
  label: string;
  tag: string;
}

type SessionInfoLookup = {
  getService?: (key: string | symbol) => { root?: string; realm?: string } | undefined;
};

export class MxDock extends MatrixActorHtmlElement {
  static accepts = {
    pin:      { tag: 'string', label: 'string', name: 'string', data: 'any?' },
    unpin:    { name: 'string' },
    activate: { name: 'string' },
    collapse: {},
    expand:   {},
  };

  static emits = {
    tabActivated: { name: 'string' },
    tabClosed:    { name: 'string' },
    collapsed:    {},
    expanded:     {},
  };

  static moduleUrl = import.meta.url;

  static template = `
    <div class="dock-root">
      <div class="dock-tabs" id="tabBar">
        <button class="dock-mount-tag" id="mountTag" type="button"
          title="Copy dock address"></button>
      </div>
      <div class="dock-content" data-slot="content"></div>
    </div>
    <style>
      :host {
        display: flex;
        flex-direction: column;
        flex: 1 1 0px;
        min-height: 0;
        overflow: hidden;
      }
      :host([collapsed]) .dock-content {
        display: none;
      }
      .dock-root {
        display: flex;
        flex-direction: column;
        flex: 1 1 0px;
        min-height: 0;
        overflow: hidden;
      }
      .dock-tabs {
        display: flex;
        flex-shrink: 0;
        border-bottom: 1px solid var(--mx-color-border, #d7e2ef);
        background: var(--mx-color-surface-alt, #f4f8ff);
        padding: 0 4px;
        min-height: 28px;
        align-items: flex-end;
      }
      .dock-mount-tag {
        /* Visibility is opt-out via --mx-dock-mount-tag-display so host apps
           can hide the address chip without forking MxDock. Default keeps
           the chip visible for callers that don't set the variable. */
        display: var(--mx-dock-mount-tag-display, inline-block);
        margin-left: auto;
        padding: 2px 8px;
        font-size: var(--mx-font-size-sm, 10px);
        font-family: var(--mx-font-mono, monospace);
        color: var(--mx-color-text-muted, #94a3b8);
        background: transparent;
        border: 1px solid var(--mx-color-border-light, #e2e8f0);
        border-radius: var(--mx-radius-sm, 3px);
        cursor: pointer;
        white-space: nowrap;
        align-self: center;
        transition: color 0.15s, border-color 0.15s;
      }
      .dock-mount-tag:hover {
        color: var(--mx-color-primary-hover, #1d4ed8);
        border-color: var(--mx-color-focus-ring, #93c5fd);
      }
      .dock-mount-tag.copied {
        color: var(--mx-color-success, #16a34a);
        border-color: #86efac;
      }
      .dock-tab {
        padding: 4px 12px;
        font-size: var(--mx-font-size-sm, 11px);
        font-weight: 600;
        color: var(--mx-color-text-secondary, #64748b);
        background: transparent;
        border: none;
        border-bottom: 2px solid transparent;
        cursor: pointer;
        display: flex;
        align-items: center;
        gap: 6px;
        white-space: nowrap;
      }
      .dock-tab:hover {
        color: var(--mx-color-text, #334155);
        background: var(--mx-color-surface-hover, #e9f1ff);
      }
      .dock-tab.active {
        color: var(--mx-color-primary, #1d4ed8);
        border-bottom-color: var(--mx-color-primary, #2563eb);
        background: var(--mx-color-surface, #fff);
      }
      .dock-tab .close-btn {
        font-size: 10px;
        opacity: 0.5;
        cursor: pointer;
        background: none;
        border: none;
        padding: 0 2px;
        color: inherit;
        line-height: 1;
      }
      .dock-tab .close-btn:hover {
        opacity: 1;
        color: var(--mx-color-error, #dc2626);
      }
      [data-slot="content"] {
        flex: 1 1 0px;
        min-height: 0;
        overflow: hidden;
        display: flex;
        flex-direction: column;
      }
      [data-slot="content"] > * {
        display: none;
        flex: 1 1 0px;
        min-height: 0;
        overflow: hidden;
      }
      [data-slot="content"] > .dock-active {
        display: flex;
        flex-direction: column;
      }
    </style>
  `;

  private _tabs: TabInfo[] = [];
  private _activeTab: string | null = null;
  private _mountTagWired = false;
  private _pendingChildData = new Map<string, { data: unknown; label: string }>();

  // ── Lifecycle ──────────────────────────────────────────────────────────

  protected async _onContextUpdated(context: unknown): Promise<void> {
    const mountTag = this.shadowRoot?.getElementById('mountTag') as HTMLButtonElement | null;
    if (mountTag) {
      const mount = this.mountPath || this.getAttribute('name') || '';
      const fullMount = this._resolveFullMount(context, mount);
      mountTag.textContent = fullMount;
      mountTag.title = `Dock address: ${fullMount} (click to copy)`;

      if (!this._mountTagWired) {
        this._mountTagWired = true;
        mountTag.addEventListener('click', () => {
          const text = mountTag.textContent || '';
          navigator.clipboard?.writeText(text).then(() => {
            mountTag.classList.add('copied');
            setTimeout(() => mountTag.classList.remove('copied'), 1200);
          });
        });
      }
    }
  }

  // ── Child lifecycle override ─────────────────────────────────────────

  /**
   * Override _handleChildAdded to send pending data after child is fully wired.
   * The base class awaits _receiveContext() → child's inbox is subscribed and
   * ready to receive messages. This replaces the unreliable setTimeout(100) hack.
   */
  protected async _handleChildAdded(payload: {
    id: string; slot: string; mount: string;
    componentClass: string; props?: Record<string, unknown>; shellTag?: string;
  }): Promise<void> {
    await super._handleChildAdded(payload);

    const pending = this._pendingChildData.get(payload.id);
    if (pending) {
      this._pendingChildData.delete(payload.id);
      this.sendToChild(payload.id, 'setData', pending);
    }
  }

  // ── Accepts handlers ─────────────────────────────────────────────────

  async onPin(p: { tag: string; label: string; name: string; data?: unknown }): Promise<void> {
    // Guard: already pinned
    if (this._tabs.some(t => t.name === p.name)) return;

    // Create tab button (insert before mount tag so tabs are left, tag is right)
    const tabBar = this.shadowRoot?.getElementById('tabBar');
    const mountTag = this.shadowRoot?.getElementById('mountTag');
    if (tabBar) {
      const tab = document.createElement('button');
      tab.className = 'dock-tab';
      tab.dataset.tabName = p.name;
      tab.innerHTML = `<span class="tab-label">${this._escapeHtml(p.label)}</span><button class="close-btn" title="Close">\u00d7</button>`;
      tab.addEventListener('click', (e) => {
        // Don't activate if clicking the close button
        if ((e.target as HTMLElement).classList.contains('close-btn')) return;
        this.sendToSelf('activate', { name: p.name });
      });
      const closeBtn = tab.querySelector('.close-btn');
      closeBtn?.addEventListener('click', () => {
        this.sendToSelf('unpin', { name: p.name });
      });
      tabBar.insertBefore(tab, mountTag ?? null);
    }

    // Track tab info
    this._tabs.push({ name: p.name, label: p.label, tag: p.tag });

    // Create child actor + DOM element via framework's addChild
    try {
      await this._actor!.addChild(p.name, MatrixActor as unknown as new () => MatrixActor, {
        slot: 'content',
        shellTag: p.tag,
      });
    } catch (err) {
      this.errorKernel(`[MxDock] Failed to add child '${p.name}' (tag: ${p.tag}):`, err);
      // Remove the tab button we just created
      const tabEl = this.shadowRoot?.querySelector(`[data-tab-name="${CSS.escape(p.name)}"]`);
      tabEl?.remove();
      this._tabs = this._tabs.filter(t => t.name !== p.name);
      return;
    }

    // Store pending data — _handleChildAdded will send it after child is fully wired
    if (p.data !== undefined) {
      this._pendingChildData.set(p.name, { data: p.data, label: p.label });
    }

    // Auto-expand if collapsed
    if (this.hasAttribute('collapsed')) {
      this.removeAttribute('collapsed');
      this.emit('expanded', {});
    }

    // Activate the newly pinned tab
    this._activateTab(p.name);
  }

  async onUnpin(p: { name: string }): Promise<void> {
    const tabIndex = this._tabs.findIndex(t => t.name === p.name);
    if (tabIndex === -1) return;

    // Remove tab button
    const tabEl = this.shadowRoot?.querySelector(`[data-tab-name="${CSS.escape(p.name)}"]`);
    tabEl?.remove();

    // Remove from list
    this._tabs.splice(tabIndex, 1);

    // Remove child actor + DOM element via framework's removeChild
    try {
      await this._actor!.removeChild(p.name);
    } catch (err) {
      this.warnKernel(`[MxDock] Error removing child '${p.name}':`, err);
    }

    this.emit('tabClosed', { name: p.name });

    // If active tab was removed, activate next
    if (this._activeTab === p.name) {
      if (this._tabs.length > 0) {
        const nextIndex = Math.min(tabIndex, this._tabs.length - 1);
        this._activateTab(this._tabs[nextIndex].name);
      } else {
        this._activeTab = null;
        this.setAttribute('collapsed', '');
        this.emit('collapsed', {});
      }
    }
  }

  onActivate(p: { name: string }): void {
    if (!this._tabs.some(t => t.name === p.name)) return;
    this._activateTab(p.name);
  }

  onCollapse(): void {
    this.setAttribute('collapsed', '');
    this.emit('collapsed', {});
  }

  onExpand(): void {
    this.removeAttribute('collapsed');
    this.emit('expanded', {});
  }

  // ── Internal ─────────────────────────────────────────────────────────

  private _activateTab(name: string): void {
    this._activeTab = name;

    // Update tab button classes
    const tabBar = this.shadowRoot?.getElementById('tabBar');
    if (tabBar) {
      for (const tab of Array.from(tabBar.querySelectorAll('.dock-tab'))) {
        tab.classList.toggle('active', (tab as HTMLElement).dataset.tabName === name);
      }
    }

    // Update content visibility
    const contentSlot = this.shadowRoot?.querySelector('[data-slot="content"]');
    if (contentSlot) {
      for (const child of Array.from(contentSlot.children)) {
        child.classList.toggle('dock-active', child.id === name);
      }
    }

    this.emit('tabActivated', { name });
  }

  private _resolveFullMount(context: unknown, mount: string): string {
    if (!mount) return '(unmounted)';
    // Try to read root from session info (standard Matrix context service)
    const SESSION_INFO_KEY = Symbol.for('matrix.session.info');
    const sessionInfo = (context as SessionInfoLookup | null | undefined)?.getService?.(SESSION_INFO_KEY);
    const root = typeof sessionInfo?.root === 'string' ? sessionInfo.root.trim()
      : typeof sessionInfo?.realm === 'string' ? sessionInfo.realm.trim()
      : '';
    if (root && !mount.startsWith(`${root}/`)) {
      return `${root}/${mount}`;
    }
    return mount;
  }

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

// Alias for naming consistency with Pattern B conventions
export { MxDock as MxDockShell };
