// src/serialization/shadowlisp/SlDomParser.ts
//
// Vision 48: ShadowLisp DOM to IComponentSchema Parser
//
// Gerald Sussman (SICP co-author):
//   "The parser doesn't parse text - it extracts structure from DOM.
//    The browser already parsed the HTML. We just traverse the tree."
//
// This converts ShadowLisp DOM elements into IComponentSchema
// for evaluation by SchemaEvaluator.

import type { IComponentSchema } from '../IComponentSchema';
import type { IMethodSchema } from '../IMethodSchema';
import type { IEventSchema } from '../IEventSchema';
import type { IParamSchema } from '../IParamSchema';
import type { SExpr } from '../../lisp/SExpr';

/**
 * Parse result from SlDomParser.
 */
export interface ISlParseResult {
  schema: IComponentSchema;
  errors: string[];
  warnings: string[];
}

/**
 * SlDomParser - Extracts IComponentSchema from ShadowLisp DOM.
 *
 * Converts <sl-form op="define-actor">...</sl-form> into IComponentSchema
 * that can be evaluated by SchemaEvaluator.
 *
 * @example
 * const form = document.querySelector('sl-form[op="define-actor"]');
 * const schema = SlDomParser.parse(form);
 * const result = await schemaEvaluator.eval(schema, options);
 */
export class SlDomParser {
  /**
   * Parse a ShadowLisp DOM element into IComponentSchema.
   *
   * @param element - sl-form element with op="define-actor" or op="define-composite"
   * @returns Parsed IComponentSchema
   */
  static parse(element: Element): IComponentSchema {
    const result = this.parseWithDiagnostics(element);
    if (result.errors.length > 0) {
      throw new Error(`ShadowLisp parse errors: ${result.errors.join(', ')}`);
    }
    return result.schema;
  }

  /**
   * Parse with diagnostics (errors and warnings).
   */
  static parseWithDiagnostics(element: Element): ISlParseResult {
    const errors: string[] = [];
    const warnings: string[] = [];

    // Must be sl-form
    if (element.tagName !== 'SL-FORM') {
      errors.push(`Expected sl-form, got ${element.tagName}`);
      return {
        schema: this._emptySchema(),
        errors,
        warnings
      };
    }

    const op = element.getAttribute('op');

    switch (op) {
      case 'define-actor':
        return {
          schema: this._parseActorDefinition(element, errors, warnings),
          errors,
          warnings
        };

      case 'define-composite':
        return {
          schema: this._parseCompositeDefinition(element, errors, warnings),
          errors,
          warnings
        };

      case 'component':
        return {
          schema: this._parseComponentRef(element, errors, warnings),
          errors,
          warnings
        };

      default:
        // Generic form - wrap as expression
        return {
          schema: this._parseGenericForm(element, errors, warnings),
          errors,
          warnings
        };
    }
  }

  /**
   * Parse define-actor form into IComponentSchema.
   *
   * <sl-form op="define-actor">
   *   <sl-atom kind="sym">counter</sl-atom>
   *   <sl-form op="accepts">...</sl-form>
   *   <sl-form op="emits">...</sl-form>
   *   <sl-form op="state">...</sl-form>
   *   <sl-form op="handler" name="increment">...</sl-form>
   * </sl-form>
   */
  private static _parseActorDefinition(
    element: Element,
    errors: string[],
    warnings: string[]
  ): IComponentSchema {
    const children = Array.from(element.children);

    // First child should be the name atom
    const nameAtom = children.find(c => c.tagName === 'SL-ATOM');
    const name = nameAtom?.textContent?.trim() ?? 'unnamed';

    const schema: IComponentSchema = {
      name,
      accepts: {},
      emits: {},
      handlers: {},
      state: {},
      sourceFormat: 'shadowlisp',
    };

    // Parse other children
    for (const child of children) {
      if (child.tagName !== 'SL-FORM') continue;

      const childOp = child.getAttribute('op');
      switch (childOp) {
        case 'accepts':
          schema.accepts = this._parseAccepts(child, errors);
          break;

        case 'emits':
          schema.emits = this._parseEmits(child, errors);
          break;

        case 'state':
          schema.state = this._parseState(child, errors);
          break;

        case 'handler':
          const handlerName = child.getAttribute('name');
          if (handlerName) {
            schema.handlers = schema.handlers ?? {};
            schema.handlers[handlerName] = this._parseHandler(child, errors);
          } else {
            warnings.push('Handler missing name attribute');
          }
          break;
      }
    }

    return schema;
  }

  /**
   * Parse accepts block.
   */
  private static _parseAccepts(
    element: Element,
    errors: string[]
  ): Record<string, IMethodSchema> {
    const accepts: Record<string, IMethodSchema> = {};

    for (const child of Array.from(element.children)) {
      if (child.tagName !== 'SL-FORM') continue;

      const op = child.getAttribute('op');
      if (op === 'command') {
        const name = child.getAttribute('name');
        if (name) {
          accepts[name] = this._parseMethodSchema(child);
        }
      } else {
        // Treat as command name with inline params
        const cmdName = op ?? 'unknown';
        accepts[cmdName] = this._parseMethodSchema(child);
      }
    }

    return accepts;
  }

  /**
   * Parse emits block.
   */
  private static _parseEmits(
    element: Element,
    errors: string[]
  ): Record<string, IEventSchema> {
    const emits: Record<string, IEventSchema> = {};

    for (const child of Array.from(element.children)) {
      if (child.tagName !== 'SL-FORM') continue;

      const op = child.getAttribute('op');
      if (op === 'event') {
        const name = child.getAttribute('name');
        if (name) {
          emits[name] = this._parseEventSchema(child);
        }
      } else {
        // Treat as event name with inline schema
        const eventName = op ?? 'unknown';
        emits[eventName] = this._parseEventSchema(child);
      }
    }

    return emits;
  }

  /**
   * Parse state block.
   */
  private static _parseState(
    element: Element,
    errors: string[]
  ): Record<string, unknown> {
    const state: Record<string, unknown> = {};

    for (const child of Array.from(element.children)) {
      if (child.tagName !== 'SL-FORM') continue;

      const op = child.getAttribute('op');
      if (op === 'binding') {
        const atoms = Array.from(child.children).filter(c => c.tagName === 'SL-ATOM');
        if (atoms.length >= 2) {
          const name = atoms[0].textContent?.trim();
          const value = this._parseAtomValue(atoms[1]);
          if (name) {
            state[name] = value;
          }
        }
      }
    }

    return state;
  }

  /**
   * Parse handler form.
   */
  private static _parseHandler(
    element: Element,
    errors: string[]
  ): { code: string; language: 'lisp' } {
    // Extract the handler body as S-expression
    const bodyForms = Array.from(element.children).filter(
      c => c.tagName === 'SL-FORM' || c.tagName === 'SL-ATOM'
    );

    // Convert to S-expression string representation
    const sexprs = bodyForms.map(f => this._elementToSExprText(f));
    const code = sexprs.length === 1 ? sexprs[0] : `(begin ${sexprs.join(' ')})`;

    return {
      code,
      language: 'lisp'
    };
  }

  /**
   * Parse method schema from command form.
   */
  private static _parseMethodSchema(element: Element): IMethodSchema {
    const params: Record<string, IParamSchema> = {};

    for (const child of Array.from(element.children)) {
      if (child.tagName === 'SL-ATOM') {
        const paramName = child.textContent?.trim();
        const kind = child.getAttribute('kind') ?? 'sym';
        if (paramName) {
          params[paramName] = { type: this._kindToType(kind) };
        }
      }
    }

    return { params };
  }

  /**
   * Parse event schema from event form.
   */
  private static _parseEventSchema(element: Element): IEventSchema {
    const payload: Record<string, IParamSchema> = {};

    for (const child of Array.from(element.children)) {
      if (child.tagName === 'SL-ATOM') {
        const fieldName = child.textContent?.trim();
        const kind = child.getAttribute('kind') ?? 'sym';
        if (fieldName) {
          payload[fieldName] = { type: this._kindToType(kind) };
        }
      }
    }

    return { payload };
  }

  /**
   * Parse composite definition.
   */
  private static _parseCompositeDefinition(
    element: Element,
    errors: string[],
    warnings: string[]
  ): IComponentSchema {
    // Similar to actor but with children and supervision
    const schema = this._parseActorDefinition(element, errors, warnings);
    schema.componentType = 'composite';
    schema.children = [];

    // Find children block
    for (const child of Array.from(element.children)) {
      if (child.tagName !== 'SL-FORM') continue;

      const op = child.getAttribute('op');
      if (op === 'children') {
        for (const childDef of Array.from(child.children)) {
          if (childDef.tagName === 'SL-FORM' && childDef.getAttribute('op') === 'child') {
            const childSchema = this._parseChildDefinition(childDef, errors);
            if (childSchema) {
              schema.children.push(childSchema);
            }
          }
        }
      } else if (op === 'supervise') {
        // Parse supervision policy (stored in config)
        schema.config = schema.config ?? {};
        schema.config.supervision = this._parseSupervision(child, errors);
      }
    }

    return schema;
  }

  /**
   * Parse child definition.
   */
  private static _parseChildDefinition(
    element: Element,
    errors: string[]
  ): IComponentSchema | null {
    const id = element.getAttribute('id');
    const slot = element.getAttribute('slot') ?? 'default';

    // Child content is the component
    const componentForm = Array.from(element.children).find(c => c.tagName === 'SL-FORM');
    if (!componentForm) {
      errors.push(`Child ${id} has no component definition`);
      return null;
    }

    const childSchema = this.parse(componentForm);
    childSchema.id = id ?? childSchema.name;
    childSchema.slot = slot;

    return childSchema;
  }

  /**
   * Parse supervision policy.
   */
  private static _parseSupervision(
    element: Element,
    errors: string[]
  ): Record<string, unknown> {
    const policy: Record<string, unknown> = {};

    for (const child of Array.from(element.children)) {
      if (child.tagName !== 'SL-FORM') continue;

      const op = child.getAttribute('op');
      const value = this._parseAtomValue(child.children[0] as Element);

      switch (op) {
        case 'strategy':
          policy.strategy = value;
          break;
        case 'max-restarts':
          policy.maxRestarts = value;
          break;
        case 'within-ms':
          policy.withinMs = value;
          break;
      }
    }

    return policy;
  }

  /**
   * Parse component reference.
   */
  private static _parseComponentRef(
    element: Element,
    errors: string[],
    warnings: string[]
  ): IComponentSchema {
    const className = element.getAttribute('class');
    const name = element.getAttribute('name') ?? className ?? 'component';

    return {
      name,
      componentType: className ?? undefined,
      accepts: {},
      emits: {},
      sourceFormat: 'shadowlisp',
    };
  }

  /**
   * Parse generic form (expression).
   */
  private static _parseGenericForm(
    element: Element,
    errors: string[],
    warnings: string[]
  ): IComponentSchema {
    const op = element.getAttribute('op') ?? 'unknown';

    return {
      name: `form:${op}`,
      accepts: {},
      emits: {},
      config: {
        expression: this._elementToSExprText(element)
      },
      sourceFormat: 'shadowlisp',
    };
  }

  /**
   * Convert element to S-expression text.
   */
  private static _elementToSExprText(element: Element): string {
    if (element.tagName === 'SL-ATOM') {
      const kind = element.getAttribute('kind') ?? 'sym';
      const value = element.textContent?.trim() ?? '';
      if (kind === 'str') return `"${value}"`;
      if (kind === 'num') return value;
      if (kind === 'bool') return value === 'true' ? '#t' : '#f';
      if (kind === 'nil') return 'nil';
      return value;
    }

    if (element.tagName === 'SL-FORM') {
      const op = element.getAttribute('op') ?? '';
      const children = Array.from(element.children).map(c => this._elementToSExprText(c));
      return `(${op} ${children.join(' ')})`;
    }

    return element.textContent ?? '';
  }

  /**
   * Parse atom value from element.
   */
  private static _parseAtomValue(element: Element | undefined): unknown {
    if (!element) return null;
    if (element.tagName !== 'SL-ATOM') return element.textContent;

    const kind = element.getAttribute('kind') ?? 'sym';
    const text = element.textContent?.trim() ?? '';

    switch (kind) {
      case 'num': return parseFloat(text) || 0;
      case 'bool': return text === 'true' || text === '#t';
      case 'nil': return null;
      default: return text;
    }
  }

  /**
   * Convert atom kind to type string.
   */
  private static _kindToType(kind: string): 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any' {
    switch (kind) {
      case 'num': return 'number';
      case 'bool': return 'boolean';
      case 'str': return 'string';
      case 'obj': return 'object';
      case 'arr': return 'array';
      default: return 'any';
    }
  }

  /**
   * Create empty schema for error cases.
   */
  private static _emptySchema(): IComponentSchema {
    return {
      name: 'error',
      accepts: {},
      emits: {},
    };
  }
}
