// src/runtime/ComponentFactory.ts
//
// Vision 45 §5: Format-Agnostic Component Creation API
//
// John McCarthy (LISP Creator, Turing Award):
//   "These are the EVAL entry points for Matrix's homoiconicity.
//    Each format is equally valid - all roads lead to running components."
//
// Rich Hickey (Clojure Creator):
//   "Data in, running code out. That's the essence of EVAL.
//    The format is just syntax - the semantics are what matter."
//
// This module provides the top-level API for creating components from
// any serialization format. It combines parsing (QUOTE inverse) with
// evaluation (EVAL) into a single convenient operation.

import type { IComponentSchema } from '../serialization/IComponentSchema';
import type { IMatrixContext } from '../engine/core/IMatrixContext';
import type { IParseResult } from '../serialization/IParseResult';
import { SchemaEvaluator, type ISchemaEvalResult, type ISchemaEvalOptions } from './SchemaEvaluator';
import { fromHtml } from '../serialization/fromHtml';
import { fromSExpr } from '../serialization/fromSExpr';

/**
 * Result of format-agnostic component creation.
 */
export interface ICreateResult extends ISchemaEvalResult {
  /** The parsed schema (for introspection) */
  schema: IComponentSchema;
}

/**
 * Options for component creation.
 */
export interface ICreateOptions extends ISchemaEvalOptions {
  /** Validation mode */
  validate?: 'strict' | 'loose' | 'none';
}

// ═══════════════════════════════════════════════════════════════════════════
// createFromJson - JSON → Schema → Component
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Create a component from JSON schema.
 *
 * This is the most direct path - JSON IS the schema format.
 *
 * @param json - JSON string or object representing IComponentSchema
 * @param context - Parent context for the component
 * @param options - Creation options
 * @returns Created component result
 *
 * @example
 * ```typescript
 * const result = await createFromJson({
 *   name: 'Counter',
 *   accepts: { increment: { params: {} } },
 *   emits: { changed: { payload: { value: { type: 'number' } } } },
 *   handlers: {
 *     increment: 'this.count++; this.emit("changed", { value: this.count });'
 *   },
 *   state: { count: 0 }
 * }, context);
 * ```
 */
export async function createFromJson(
  json: string | IComponentSchema,
  context: IMatrixContext,
  options?: ICreateOptions
): Promise<ICreateResult> {
  // Parse JSON string if needed
  let schema: IComponentSchema;
  if (typeof json === 'string') {
    try {
      schema = JSON.parse(json) as IComponentSchema;
    } catch (err) {
      throw new Error(`[createFromJson] Invalid JSON: ${err instanceof Error ? err.message : err}`);
    }
  } else {
    schema = json;
  }

  // Validate required fields
  if (!schema.name) {
    throw new Error('[createFromJson] Schema must have a name');
  }
  if (!schema.accepts) {
    schema.accepts = {};
  }
  if (!schema.emits) {
    schema.emits = {};
  }

  // Evaluate
  const evaluator = new SchemaEvaluator(context);
  const evalResult = await evaluator.eval(schema, options);

  return {
    ...evalResult,
    schema,
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// createFromHtml - HTML → Schema → Component
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Create a component from HTML markup.
 *
 * Parses custom element syntax and evaluates to running component.
 *
 * @param html - HTML string with Matrix custom elements
 * @param context - Parent context for the component
 * @param options - Creation options
 * @returns Created component result
 *
 * @example
 * ```typescript
 * const result = await createFromHtml(`
 *   <matrix-counter id="myCounter">
 *     <mx-accepts method="increment"></mx-accepts>
 *     <mx-emits event="changed" value="number"></mx-emits>
 *     <script type="handler/increment">
 *       this.count++;
 *       this.emit('changed', { value: this.count });
 *     </script>
 *   </matrix-counter>
 * `, context);
 * ```
 */
export async function createFromHtml(
  html: string,
  context: IMatrixContext,
  options?: ICreateOptions
): Promise<ICreateResult> {
  // Parse HTML to schema
  const parseResult: IParseResult<IComponentSchema> = fromHtml(html);

  if (!parseResult.success || !parseResult.value) {
    throw new Error(`[createFromHtml] Parse failed: ${parseResult.error}`);
  }

  const schema = parseResult.value;

  // Enhance schema with handlers from script tags if present
  enhanceSchemaFromHtml(schema, html);

  // Evaluate
  const evaluator = new SchemaEvaluator(context);
  const evalResult = await evaluator.eval(schema, options);

  return {
    ...evalResult,
    schema,
  };
}

/**
 * Enhance parsed schema with handler scripts from HTML.
 * Extracts <script type="handler/methodName"> elements.
 */
function enhanceSchemaFromHtml(schema: IComponentSchema, html: string): void {
  // Extract handlers from <script type="handler/methodName">
  const handlerRegex = /<script\s+type="handler\/(\w+)"[^>]*>([\s\S]*?)<\/script>/gi;
  let match: RegExpExecArray | null;

  while ((match = handlerRegex.exec(html)) !== null) {
    const [, methodName, code] = match;
    if (!schema.handlers) {
      schema.handlers = {};
    }
    schema.handlers[methodName] = {
      code: code.trim(),
      language: 'javascript',
    };
  }

  // Extract template from <template> or innerHTML
  const templateMatch = html.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
  if (templateMatch && !schema.template) {
    schema.template = templateMatch[1].trim();
  }

  // Extract styles from <style>
  const styleMatch = html.match(/<style[^>]*>([\s\S]*?)<\/style>/i);
  if (styleMatch && !schema.styles) {
    schema.styles = styleMatch[1].trim();
  }

  // Extract id from attributes
  const idMatch = html.match(/\bid="([^"]+)"/);
  if (idMatch && !schema.id) {
    schema.id = idMatch[1];
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// createFromSExpr - S-Expression → Schema → Component
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Create a component from S-expression (LISP) syntax.
 *
 * Full circle: LISP syntax → Matrix component → running actor.
 *
 * @param sexpr - S-expression string
 * @param context - Parent context for the component
 * @param options - Creation options
 * @returns Created component result
 *
 * @example
 * ```typescript
 * const result = await createFromSExpr(`
 *   (component "Counter"
 *     (accepts "increment")
 *     (emits "changed" (payload (value number)))
 *     (handler "increment"
 *       (lambda (payload)
 *         (begin
 *           (set! count (+ count 1))
 *           (emit "changed" (obj ("value" count)))))))
 * `, context);
 * ```
 */
export async function createFromSExpr(
  sexpr: string,
  context: IMatrixContext,
  options?: ICreateOptions
): Promise<ICreateResult> {
  // Parse S-expression to schema
  const parseResult: IParseResult<IComponentSchema> = fromSExpr(sexpr);

  if (!parseResult.success || !parseResult.value) {
    throw new Error(`[createFromSExpr] Parse failed: ${parseResult.error}`);
  }

  const schema = parseResult.value;

  // Enhance schema with LISP handlers if present
  enhanceSchemaFromSExpr(schema, sexpr);

  // Evaluate
  const evaluator = new SchemaEvaluator(context);
  const evalResult = await evaluator.eval(schema, options);

  return {
    ...evalResult,
    schema,
  };
}

/**
 * Enhance parsed schema with LISP handler expressions.
 * Extracts (handler "methodName" (lambda ...)) forms.
 */
function enhanceSchemaFromSExpr(schema: IComponentSchema, sexpr: string): void {
  // Extract (handler "methodName" ...) forms
  // This is a simplified extraction - full LISP parsing would be more robust
  const handlerRegex = /\(handler\s+"(\w+)"\s+([\s\S]*?)\)\s*(?=\((?:handler|accepts|emits|component)\s|$)/gi;
  let match: RegExpExecArray | null;

  while ((match = handlerRegex.exec(sexpr)) !== null) {
    const [, methodName, code] = match;
    if (!schema.handlers) {
      schema.handlers = {};
    }
    schema.handlers[methodName] = {
      code: code.trim(),
      language: 'lisp',
    };
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// createFromSchema - Direct schema evaluation (lowest level)
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Create a component directly from a schema object.
 *
 * This is the core method that all other createFrom* methods use.
 * Use this when you already have a schema and want direct evaluation.
 *
 * @param schema - Component schema
 * @param context - Parent context for the component
 * @param options - Creation options
 * @returns Created component result
 */
export async function createFromSchema(
  schema: IComponentSchema,
  context: IMatrixContext,
  options?: ICreateOptions
): Promise<ICreateResult> {
  // Validate schema
  if (!schema.name) {
    throw new Error('[createFromSchema] Schema must have a name');
  }
  if (!schema.accepts) {
    schema.accepts = {};
  }
  if (!schema.emits) {
    schema.emits = {};
  }

  // Evaluate
  const evaluator = new SchemaEvaluator(context);
  const evalResult = await evaluator.eval(schema, options);

  return {
    ...evalResult,
    schema,
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// Utility: detectFormat - Auto-detect format from source
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Detect the format of a source string.
 */
export function detectFormat(source: string): 'html' | 'sexpr' | 'json' | 'unknown' {
  const trimmed = source.trim();

  // JSON starts with { or [
  if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
    return 'json';
  }

  // S-expression starts with (
  if (trimmed.startsWith('(')) {
    return 'sexpr';
  }

  // HTML starts with <
  if (trimmed.startsWith('<')) {
    return 'html';
  }

  return 'unknown';
}

/**
 * Create a component from any supported format (auto-detected).
 *
 * @param source - Source string in any supported format
 * @param context - Parent context for the component
 * @param options - Creation options
 * @returns Created component result
 */
export async function createFromAny(
  source: string,
  context: IMatrixContext,
  options?: ICreateOptions
): Promise<ICreateResult> {
  const format = detectFormat(source);

  switch (format) {
    case 'json':
      return createFromJson(source, context, options);
    case 'html':
      return createFromHtml(source, context, options);
    case 'sexpr':
      return createFromSExpr(source, context, options);
    default:
      throw new Error(`[createFromAny] Could not detect format of source`);
  }
}
