// src/framework/shadowlisp/SlForm.ts
//
// Vision 48: ShadowLisp Universal S-Expression Element
//
// John McCarthy (LISP creator, Turing Award 1971):
//   "The power of LISP is that code and data share the same representation.
//    Here, a single DOM element IS an S-expression. The browser's DOM IS a Lisp tree."
//
// Guy Steele (Scheme co-designer):
//   "This isn't a metaphor. The structural equivalence is exact.
//    A DOM tree IS a tree of cons cells with car (tag/content) and cdr (siblings)."
//
// <sl-form> represents any S-expression (op arg1 arg2 ...)

import type { SExpr } from '../../lisp/SExpr';
import { SlAtom } from './SlAtom';

const SlFormElementBase = HTMLElement;

/**
 * SlForm - Universal S-expression custom element.
 *
 * The 'op' attribute is the operator (car of the list).
 * Children are the arguments (cdr of the list).
 *
 * @example
 * <!-- (+ 1 2) -->
 * <sl-form op="+">
 *   <sl-atom kind="num">1</sl-atom>
 *   <sl-atom kind="num">2</sl-atom>
 * </sl-form>
 *
 * <!-- (define x 42) -->
 * <sl-form op="define">
 *   <sl-atom kind="sym">x</sl-atom>
 *   <sl-atom kind="num">42</sl-atom>
 * </sl-form>
 *
 * <!-- Nested: (* (+ 1 2) 3) -->
 * <sl-form op="*">
 *   <sl-form op="+">
 *     <sl-atom kind="num">1</sl-atom>
 *     <sl-atom kind="num">2</sl-atom>
 *   </sl-form>
 *   <sl-atom kind="num">3</sl-atom>
 * </sl-form>
 */
export class SlForm extends SlFormElementBase {
  static observedAttributes = ['op'];

  constructor() {
    super();
    // No shadow DOM by default - forms are structural
  }

  /**
   * Get the operator (first element of S-expression).
   */
  get op(): string {
    return this.getAttribute('op') ?? '';
  }

  /**
   * Set the operator.
   */
  set op(value: string) {
    this.setAttribute('op', value);
  }

  /**
   * Get child elements that are part of the S-expression.
   * Filters to only SlForm and SlAtom children.
   */
  get args(): Array<SlForm | SlAtom> {
    const result: Array<SlForm | SlAtom> = [];
    for (const child of Array.from(this.children)) {
      if (child instanceof SlForm || child instanceof SlAtom) {
        result.push(child);
      } else if (child.tagName === 'SL-FORM') {
        // Handle case where custom element hasn't been upgraded yet
        result.push(child as unknown as SlForm);
      } else if (child.tagName === 'SL-ATOM') {
        result.push(child as unknown as SlAtom);
      } else if (child instanceof HTMLTemplateElement) {
        // <template> is quote - will be handled specially
        // For now, skip it in args
      }
    }
    return result;
  }

  /**
   * Convert this element tree to S-expression format.
   *
   * Gerald Sussman (SICP co-author):
   *   "The DOM IS the AST. toSExpr() simply extracts it into JSON wire format."
   *
   * @returns S-expression as JSON array: ["op", arg1, arg2, ...]
   */
  toSExpr(): SExpr {
    const result: SExpr[] = [this.op];

    for (const child of Array.from(this.children)) {
      if (child instanceof SlForm) {
        result.push(child.toSExpr());
      } else if (child instanceof SlAtom) {
        result.push(child.toSExpr());
      } else if (child.tagName === 'SL-FORM') {
        // Handle unupgraded element
        result.push((child as unknown as SlForm).toSExpr?.() ?? child.outerHTML);
      } else if (child.tagName === 'SL-ATOM') {
        result.push((child as unknown as SlAtom).toSExpr?.() ?? child.textContent);
      } else if (child instanceof HTMLTemplateElement) {
        // <template> = quote
        // Extract content and wrap in quote form
        const quotedContent = this._templateToSExpr(child);
        result.push(['quote', quotedContent]);
      } else if (child instanceof Text && child.textContent?.trim()) {
        // Text nodes become string atoms
        result.push(child.textContent.trim());
      }
      // Ignore other elements (comments, etc.)
    }

    return result;
  }

  /**
   * Convert a template element to S-expression.
   * Templates are QUOTE - content is not evaluated.
   */
  private _templateToSExpr(template: HTMLTemplateElement): SExpr {
    const content = template.content;
    const children: SExpr[] = [];

    for (const node of Array.from(content.childNodes)) {
      if (node instanceof HTMLElement) {
        if (node.tagName === 'SL-FORM') {
          children.push((node as unknown as SlForm).toSExpr?.() ?? ['error', 'unupgraded']);
        } else if (node.tagName === 'SL-ATOM') {
          children.push((node as unknown as SlAtom).toSExpr?.() ?? node.textContent);
        } else {
          // Other HTML elements - preserve as-is for DOM rendering
          children.push(['dom-element', node.tagName.toLowerCase(), node.outerHTML]);
        }
      } else if (node instanceof Text && node.textContent?.trim()) {
        children.push(node.textContent.trim());
      }
    }

    // If single child, return it directly
    if (children.length === 1) return children[0];
    // Multiple children become a list
    return ['list', ...children];
  }

  /**
   * Create an SlForm from an S-expression.
   *
   * @param sexp - S-expression to convert
   * @returns SlForm element representing the expression
   */
  static fromSExpr(sexp: SExpr): SlForm | SlAtom {
    // Atoms become SlAtom
    if (!Array.isArray(sexp)) {
      return SlAtom.fromSExpr(sexp);
    }

    // Empty list
    if (sexp.length === 0) {
      const form = document.createElement('sl-form') as SlForm;
      form.op = 'list';
      return form;
    }

    // Get operator
    const [op, ...args] = sexp;

    // Create form
    const form = document.createElement('sl-form') as SlForm;
    form.op = String(op);

    // Add children
    for (const arg of args) {
      const child = SlForm.fromSExpr(arg);
      form.appendChild(child);
    }

    return form;
  }

  /**
   * Pretty-print this form as S-expression text.
   */
  toSExprText(): string {
    return this._formatSExpr(this.toSExpr());
  }

  private _formatSExpr(sexp: SExpr, indent = 0): string {
    if (!Array.isArray(sexp)) {
      if (sexp === null) return 'nil';
      if (typeof sexp === 'string') {
        // Quote strings with spaces
        if (sexp.includes(' ')) return `"${sexp}"`;
        return sexp;
      }
      return String(sexp);
    }

    if (sexp.length === 0) return '()';

    const parts = sexp.map(s => this._formatSExpr(s, indent + 2));
    const oneLine = `(${parts.join(' ')})`;

    // If short enough, keep on one line
    if (oneLine.length < 60) return oneLine;

    // Otherwise, format multi-line
    const [first, ...rest] = parts;
    const pad = ' '.repeat(indent + 2);
    return `(${first}\n${rest.map(p => pad + p).join('\n')})`;
  }

  /**
   * Get the closest SlRuntime ancestor.
   */
  getRuntime(): HTMLElement | null {
    return this.closest('sl-runtime');
  }

  /**
   * Dispatch a custom event for S-expression changes.
   */
  attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void {
    if (name === 'op') {
      this.dispatchEvent(new CustomEvent('sl-op-changed', {
        bubbles: true,
        detail: { op: this.op }
      }));
    }
  }

  connectedCallback(): void {
    // Notify runtime that a form was added
    this.dispatchEvent(new CustomEvent('sl-form-connected', {
      bubbles: true,
      detail: { form: this }
    }));
  }

  disconnectedCallback(): void {
    this.dispatchEvent(new CustomEvent('sl-form-disconnected', {
      bubbles: true,
      detail: { form: this }
    }));
  }
}

// Define the custom element
if (!customElements.get('sl-form')) {
  customElements.define('sl-form', SlForm);
}
