// src/framework/shadowlisp/primitives/SlReparent.ts
//
// Vision 52: DOM Reparenting Primitive
//
// <sl-reparent child-ref="widget" new-parent-ref="header" slot="main" />
//
// Moves a child element to a different parent, with proper mount path update
// and relocation announcement via Matrix transport.
//
// This is the KEY primitive that enables LISP-controlled DOM manipulation.
// Combined with VLC, you can move components anywhere in the DOM tree.

import type { SExpr } from '../../../lisp/SExpr';
import { SlMatrixElement } from '../SlMatrixElement';
import { SlBootstrap, type SlEnv } from '../SlBootstrap';
import { TopicRouter } from '../../../engine/messaging/TopicRouter';
import type { MxEnvelope } from '../../../engine/messaging/MxEnvelope';

/**
 * SlReparent - Move a DOM element to a different parent.
 *
 * Vision 52 Part 6.3: DOM manipulation via LISP primitives.
 *
 * @example Basic usage:
 * <sl-reparent child-ref="widget" new-parent-ref="header" />
 *
 * @example With named slot:
 * <sl-reparent child-ref="widget" new-parent-ref="container" slot="main" />
 *
 * @example Via VLC command:
 * (reparent "widget" "header" "main")
 */
export class SlReparent extends SlMatrixElement {
  static get observedAttributes(): string[] {
    return ['mount', 'child-ref', 'new-parent-ref', 'slot'];
  }

  /**
   * Get the reference to the child element to move.
   */
  get childRef(): string {
    return this.getAttribute('child-ref') ?? '';
  }

  /**
   * Get the reference to the new parent element.
   */
  get newParentRef(): string {
    return this.getAttribute('new-parent-ref') ?? '';
  }

  /**
   * Get the optional slot name within the new parent.
   */
  get slotName(): string {
    return this.getAttribute('slot') ?? '';
  }

  /**
   * Execute the reparent operation.
   *
   * Steps:
   * 1. Resolve child and parent references
   * 2. Get old mount for relocation announcement
   * 3. Perform DOM move (to slot if specified)
   * 4. Update mount path
   * 5. Announce relocation via transport
   *
   * @param env - Environment with bindings for reference resolution
   * @returns The new mount path of the child
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const childRef = this.childRef;
    const newParentRef = this.newParentRef;
    const slotName = this.slotName;

    if (!childRef || !newParentRef) {
      throw new Error('[SlReparent] child-ref and new-parent-ref are required');
    }

    // Resolve references - check environment first, then DOM
    const child = this._resolveRef(childRef, env as SlEnv);
    const newParent = this._resolveRef(newParentRef, env as SlEnv);

    if (!(child instanceof HTMLElement)) {
      throw new Error(`[SlReparent] child-ref "${childRef}" did not resolve to an HTMLElement`);
    }
    if (!(newParent instanceof HTMLElement)) {
      throw new Error(`[SlReparent] new-parent-ref "${newParentRef}" did not resolve to an HTMLElement`);
    }

    // Get old mount for relocation announcement
    const oldMount = child.getAttribute('mount') || child.id || '';

    // Perform DOM move
    if (slotName) {
      // Look for slot by various attributes
      const slot = newParent.querySelector(
        `[slot="${slotName}"], [data-slot="${slotName}"], [name="${slotName}"]`
      );
      if (slot) {
        slot.appendChild(child);
      } else {
        // No slot found - append directly to parent
        this.warnKernel(`[SlReparent] Slot "${slotName}" not found, appending to parent`);
        newParent.appendChild(child);
      }
    } else {
      newParent.appendChild(child);
    }

    // Calculate new mount path
    const parentMount = newParent.getAttribute('mount') || '';
    const childId = child.id || child.getAttribute('name') || 'child';
    const newMount = parentMount ? `${parentMount}.${childId}` : childId;

    // Update mount attribute on child
    child.setAttribute('mount', newMount);

    // Announce relocation via transport (if available)
    if (this._context?.transport && oldMount) {
      const eventsTopic = TopicRouter.events(oldMount);
      const envelope: MxEnvelope = {
        op: 'relocated',
        payload: { oldMount, newMount, slot: slotName || null },
        from: oldMount,
      };
      this._context.transport.publish(eventsTopic, envelope);
    }

    // Log for debugging
    this.infoKernel(
      `%c[SlReparent] Moved ${childRef} from ${oldMount} to ${newMount}`,
      'color: #00e676; font-weight: bold'
    );

    return newMount;
  }

  /**
   * Resolve a reference to an element.
   *
   * Resolution order:
   * 1. Environment binding
   * 2. DOM by ID
   * 3. DOM by mount attribute
   */
  private _resolveRef(ref: string, env: SlEnv): unknown {
    // Check environment first
    const fromEnv = SlBootstrap.lookup(ref, env);
    if (fromEnv !== undefined && fromEnv !== null) {
      return fromEnv;
    }

    // Check DOM by ID
    const byId = document.getElementById(ref);
    if (byId) return byId;

    // Check by mount attribute (exact match or ends with .ref)
    const byMount = document.querySelector(
      `[mount="${ref}"], [mount$=".${ref}"]`
    );
    if (byMount) return byMount;

    return null;
  }

  /**
   * Convert to S-expression.
   */
  toSExpr(): SExpr {
    const args: SExpr[] = ['reparent', this.childRef, this.newParentRef];
    if (this.slotName) {
      args.push(this.slotName);
    }
    return args;
  }
}

// Register custom element
if (!customElements.get('sl-reparent')) {
  customElements.define('sl-reparent', SlReparent);
}
