// src/serialization/toSExpr.ts
// Serialize Matrix component to S-expression representation
//
// S-expressions provide a LISP-compatible syntax for component definition.
//
// Reference: Vision/14-HOMOICONIC-ARCHITECTURE.md, Vision/15-LISP-ON-PROTOCOL.md

import type { IComponentSchema } from './IComponentSchema';
import type { ISerializationOptions } from './ISerializationOptions';

/**
 * Convert component schema to S-expression representation
 *
 * @example
 * toSExpr({ name: 'Button', accepts: { click: { params: { id: { type: 'string' } } } }, emits: { clicked: {} } })
 * // Returns:
 * // (component "Button"
 * //   (accepts "click" (params (id string)))
 * //   (emits "clicked"))
 */
export function toSExpr(
  schema: IComponentSchema,
  options: ISerializationOptions = {}
): string {
  const { pretty = true, indent = '  ' } = options;

  if (pretty) {
    return toSExprPretty(schema, 0, indent);
  } else {
    return toSExprCompact(schema);
  }
}

/**
 * Pretty-printed S-expression
 */
function toSExprPretty(schema: IComponentSchema, depth: number, indent: string): string {
  const pad = indent.repeat(depth);
  const pad1 = indent.repeat(depth + 1);
  const lines: string[] = [];

  // Component declaration
  lines.push(`${pad}(component "${schema.name}"`);

  // Mount (if present)
  if (schema.mount) {
    lines.push(`${pad1}(mount "${schema.mount}")`);
  }

  // Accepts (methods)
  for (const [name, method] of Object.entries(schema.accepts)) {
    const paramsStr = formatParamsSExpr(method.params);
    if (paramsStr) {
      lines.push(`${pad1}(accepts "${name}" ${paramsStr})`);
    } else {
      lines.push(`${pad1}(accepts "${name}")`);
    }
  }

  // Emits (events)
  for (const [name, event] of Object.entries(schema.emits)) {
    const payloadStr = formatParamsSExpr(event.payload);
    if (payloadStr) {
      lines.push(`${pad1}(emits "${name}" ${payloadStr})`);
    } else {
      lines.push(`${pad1}(emits "${name}")`);
    }
  }

  // Children
  if (schema.children) {
    for (const child of schema.children) {
      lines.push(toSExprPretty(child, depth + 1, indent));
    }
  }

  lines.push(`${pad})`);

  return lines.join('\n');
}

/**
 * Compact S-expression (single line)
 */
function toSExprCompact(schema: IComponentSchema): string {
  const parts: string[] = [];

  parts.push(`(component "${schema.name}"`);

  // Mount (if present)
  if (schema.mount) {
    parts.push(`(mount "${schema.mount}")`);
  }

  // Accepts
  for (const [name, method] of Object.entries(schema.accepts)) {
    const paramsStr = formatParamsSExpr(method.params);
    parts.push(paramsStr ? `(accepts "${name}" ${paramsStr})` : `(accepts "${name}")`);
  }

  // Emits
  for (const [name, event] of Object.entries(schema.emits)) {
    const payloadStr = formatParamsSExpr(event.payload);
    parts.push(payloadStr ? `(emits "${name}" ${payloadStr})` : `(emits "${name}")`);
  }

  // Children
  if (schema.children) {
    for (const child of schema.children) {
      parts.push(toSExprCompact(child));
    }
  }

  parts.push(')');

  return parts.join(' ');
}

/**
 * Format parameters as S-expression params list
 */
function formatParamsSExpr(
  params: Record<string, { type: string; optional?: boolean }> | undefined
): string {
  if (!params || Object.keys(params).length === 0) {
    return '';
  }

  const entries = Object.entries(params).map(([name, schema]) => {
    const type = schema.optional ? `${schema.type}?` : schema.type;
    return `(${name} ${type})`;
  });

  return `(params ${entries.join(' ')})`;
}

/**
 * Convert any value to S-expression string
 */
export function valueToSExpr(value: unknown): string {
  if (value === null || value === undefined) {
    return 'nil';
  }
  if (typeof value === 'string') {
    return `"${value.replace(/"/g, '\\"')}"`;
  }
  if (typeof value === 'number' || typeof value === 'boolean') {
    return String(value);
  }
  if (Array.isArray(value)) {
    const items = value.map(v => valueToSExpr(v));
    return `(list ${items.join(' ')})`;
  }
  if (typeof value === 'object') {
    const entries = Object.entries(value).map(
      ([k, v]) => `("${k}" ${valueToSExpr(v)})`
    );
    return `(obj ${entries.join(' ')})`;
  }
  return String(value);
}
