/**
 * @fileoverview OrdoJS Code Generator - Refactored modular implementation
 * @author OrdoJS Framework Team
 */

import type {
    ComponentAST,
    ComponentNode,
    ExpressionNode,
    GeneratedCode,
    MarkupBlockNode,
    Props,
    ServerBlockNode,
    SourceMap,
    StatementNode
} from '../types/index.js';

/**
 * Code generation target environments
 */
export type CodeGenTarget = 'client' | 'server' | 'static' | 'universal';

/**
 * Code generation options
 */
export interface CodeGeneratorOptions {
  /** Target environment */
  target?: 'development' | 'production';
  /** Enable code minification */
  minify?: boolean;
  /** Generate source maps */
  sourceMaps?: boolean;
  /** Target ES version */
  esTarget?: 'es2022' | 'es2020' | 'es2018' | 'es5';
  /** Enable tree shaking */
  treeShaking?: boolean;
  /** Custom transformers */
  transformers?: CodeTransformer[];
  /** Output format */
  format?: 'esm' | 'cjs' | 'umd' | 'iife';
  /** Enable hot module replacement */
  hmr?: boolean;
  /** Bundle splitting strategy */
  splitting?: 'none' | 'chunks' | 'modules';
}

/**
 * Code transformer interface for plugin-based transformations
 */
export interface CodeTransformer {
  /** Transformer name */
  name: string;
  /** Target phase */
  phase: 'pre' | 'post' | 'optimize';
  /** Target code type */
  targets: CodeGenTarget[];
  /** Transform function */
  transform(code: string, context: TransformContext): TransformResult;
}

/**
 * Transform context for code transformers
 */
export interface TransformContext {
  /** Original AST */
  ast: ComponentAST;
  /** Generation options */
  options: Required<CodeGeneratorOptions>;
  /** Target environment */
  target: CodeGenTarget;
  /** Component metadata */
  metadata: ComponentMetadata;
  /** Source map builder */
  sourceMapBuilder?: SourceMapBuilder;
}

/**
 * Transform result
 */
export interface TransformResult {
  /** Transformed code */
  code: string;
  /** Source map changes */
  sourceMap?: SourceMapSegment[];
  /** Additional metadata */
  metadata?: Record<string, any>;
}

/**
 * Component metadata for code generation
 */
export interface ComponentMetadata {
  name: string;
  hasClientCode: boolean;
  hasServerCode: boolean;
  hasStyles: boolean;
  dependencies: string[];
  exports: string[];
  props: string[];
  state: string[];
  functions: string[];
  lifecycle: string[];
}

/**
 * Source map segment
 */
export interface SourceMapSegment {
  generatedLine: number;
  generatedColumn: number;
  originalLine: number;
  originalColumn: number;
  source: string;
  name?: string;
}

/**
 * Source map builder utility
 */
export class SourceMapBuilder {
  private segments: SourceMapSegment[] = [];
  private sources: string[] = [];
  private names: string[] = [];

  addMapping(
    generatedLine: number,
    generatedColumn: number,
    originalLine: number,
    originalColumn: number,
    source: string,
    name?: string
  ): void {
    if (!this.sources.includes(source)) {
      this.sources.push(source);
    }

    if (name && !this.names.includes(name)) {
      this.names.push(name);
    }

    this.segments.push({
      generatedLine,
      generatedColumn,
      originalLine,
      originalColumn,
      source,
      name
    });
  }

  build(): SourceMap {
    return {
      version: 3,
      sources: this.sources,
      names: this.names,
      mappings: this.encodeMappings(),
      sourcesContent: []
    };
  }

  private encodeMappings(): string {
    // Simplified VLQ encoding - in production, use proper source map library
    return this.segments
      .map(seg => `${seg.generatedLine},${seg.generatedColumn},${seg.originalLine},${seg.originalColumn}`)
      .join(';');
  }
}

/**
 * Code generation context for tracking state
 */
export interface CodeGenContext {
  /** Current indentation level */
  indent: number;
  /** Generated code lines */
  lines: string[];
  /** Source map builder */
  sourceMap?: SourceMapBuilder;
  /** Variable scope tracking */
  scopes: Map<string, Set<string>>;
  /** Current scope depth */
  scopeDepth: number;
  /** Import statements */
  imports: Map<string, Set<string>>;
  /** Export statements */
  exports: Set<string>;
}

/**
 * Enhanced OrdoJS Code Generator with modular architecture
 */
export class OrdoJSCodeGenerator {
  private options: Required<CodeGeneratorOptions>;
  private transformers: Map<string, CodeTransformer[]>;

  constructor(options: CodeGeneratorOptions = {}) {
    this.options = {
      target: 'development',
      minify: false,
      sourceMaps: true,
      esTarget: 'es2022',
      treeShaking: true,
      transformers: [],
      format: 'esm',
      hmr: false,
      splitting: 'none',
      ...options
    };

    this.transformers = new Map([
      ['pre', []],
      ['post', []],
      ['optimize', []]
    ]);

    this.registerDefaultTransformers();
    this.registerCustomTransformers();
  }

  /**
   * Generate code for all targets
   */
  generate(ast: ComponentAST): GeneratedCode {
    const metadata = this.extractMetadata(ast);
    const context = this.createTransformContext(ast, metadata);

    const results: Partial<GeneratedCode> = {};

    // Generate client code
    if (metadata.hasClientCode || ast.component.markupBlock) {
      results.client = this.generateClientCode(ast, context);
    }

    // Generate server code
    if (metadata.hasServerCode) {
      results.server = this.generateServerCode(ast, context);
    }

    // Generate HTML
    results.html = this.generateHTML(ast, context);

    // Generate CSS
    if (metadata.hasStyles) {
      results.css = this.generateCSS(ast, context);
    }

    // Generate source map
    if (this.options.sourceMaps) {
      results.sourceMap = context.sourceMapBuilder?.build() || this.createEmptySourceMap();
    }

    return results as GeneratedCode;
  }

  /**
   * Generate client-side code
   */
  generateClientCode(ast: ComponentAST, context: TransformContext): string {
    const genContext = this.createCodeGenContext();

    this.generateClientImports(ast, genContext);
    this.generateClientComponent(ast.component, genContext);
    this.generateClientExports(ast, genContext);

    let code = genContext.lines.join('\n');

    // Apply transformers
    code = this.applyTransformers(code, { ...context, target: 'client' });

    return code;
  }

  /**
   * Generate server-side code
   */
  generateServerCode(ast: ComponentAST, context: TransformContext): string {
    if (!ast.component.serverBlock) {
      return '';
    }

    const genContext = this.createCodeGenContext();

    this.generateServerImports(ast, genContext);
    this.generateServerFunctions(ast.component.serverBlock, genContext);
    this.generateServerExports(ast, genContext);

    let code = genContext.lines.join('\n');

    // Apply transformers
    code = this.applyTransformers(code, { ...context, target: 'server' });

    return code;
  }

  /**
   * Generate HTML template
   */
  generateHTML(ast: ComponentAST, context: TransformContext, props: Props = {}): string {
    const genContext = this.createCodeGenContext();

    this.generateHTMLStructure(ast.component.markupBlock, genContext, props);

    let code = genContext.lines.join('\n');

    // Apply transformers
    code = this.applyTransformers(code, { ...context, target: 'static' });

    return code;
  }

  /**
   * Generate CSS styles
   */
  generateCSS(ast: ComponentAST, context: TransformContext): string {
    // CSS generation would be implemented here
    return '';
  }

  /**
   * Add custom transformer
   */
  addTransformer(transformer: CodeTransformer): void {
    const phaseTransformers = this.transformers.get(transformer.phase) || [];
    phaseTransformers.push(transformer);
    this.transformers.set(transformer.phase, phaseTransformers);
  }

  /**
   * Remove transformer by name
   */
  removeTransformer(name: string): boolean {
    for (const [phase, transformers] of this.transformers) {
      const index = transformers.findIndex(t => t.name === name);
      if (index >= 0) {
        transformers.splice(index, 1);
        return true;
      }
    }
    return false;
  }

  private registerDefaultTransformers(): void {
    // Minification transformer
    this.addTransformer({
      name: 'minifier',
      phase: 'optimize',
      targets: ['client', 'server'],
      transform: (code: string, context: TransformContext): TransformResult => {
        if (!context.options.minify) {
          return { code };
        }

        // Simple minification - in production, use proper minifier
        const minified = code
          .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
          .replace(/\/\/.*$/gm, '') // Remove single-line comments
          .replace(/\s+/g, ' ') // Collapse whitespace
          .replace(/;\s*}/g, '}') // Remove unnecessary semicolons
          .trim();

        return { code: minified };
      }
    });

    // ES target transformer
    this.addTransformer({
      name: 'es-target',
      phase: 'post',
      targets: ['client', 'server'],
      transform: (code: string, context: TransformContext): TransformResult => {
        if (context.options.esTarget === 'es2022') {
          return { code };
        }

        // Transform modern JS to older versions
        let transformed = code;

        if (context.options.esTarget === 'es2018' || context.options.esTarget === 'es5') {
          // Transform arrow functions to regular functions
          transformed = transformed.replace(
            /(\w+)\s*=>\s*{([^}]*)}/g,
            'function($1) { $2 }'
          );

          // Transform const/let to var for ES5
          if (context.options.esTarget === 'es5') {
            transformed = transformed.replace(/\b(const|let)\b/g, 'var');
          }
        }

        return { code: transformed };
      }
    });

    // HMR transformer
    this.addTransformer({
      name: 'hmr',
      phase: 'post',
      targets: ['client'],
      transform: (code: string, context: TransformContext): TransformResult => {
        if (!context.options.hmr || context.options.target === 'production') {
          return { code };
        }

        const hmrCode = `
// Hot Module Replacement
if (module.hot) {
  module.hot.accept();
  module.hot.dispose(() => {
    // Cleanup code
  });
}
`;

        return { code: code + '\n' + hmrCode };
      }
    });
  }

  private registerCustomTransformers(): void {
    // Register any custom transformers from options
    for (const transformer of this.options.transformers) {
      this.addTransformer(transformer);
    }
  }

  private extractMetadata(ast: ComponentAST): ComponentMetadata {
    const component = ast.component;

    return {
      name: component.name,
      hasClientCode: !!component.clientBlock,
      hasServerCode: !!component.serverBlock,
      hasStyles: false, // Would check for style blocks
      dependencies: ast.dependencies,
      exports: ast.exports,
      props: component.props.map(p => p.name),
      state: component.clientBlock?.reactiveVariables.map(v => v.name) || [],
      functions: [
        ...(component.clientBlock?.functions.map(f => f.name) || []),
        ...(component.serverBlock?.functions.map(f => f.name) || [])
      ],
      lifecycle: component.clientBlock?.lifecycle.map(l => l.hookType.toString()) || []
    };
  }

  private createTransformContext(ast: ComponentAST, metadata: ComponentMetadata): TransformContext {
    return {
      ast,
      options: this.options,
      target: 'client',
      metadata,
      sourceMapBuilder: this.options.sourceMaps ? new SourceMapBuilder() : undefined
    };
  }

  private createCodeGenContext(): CodeGenContext {
    return {
      indent: 0,
      lines: [],
      sourceMap: this.options.sourceMaps ? new SourceMapBuilder() : undefined,
      scopes: new Map(),
      scopeDepth: 0,
      imports: new Map(),
      exports: new Set()
    };
  }

  private generateClientImports(ast: ComponentAST, context: CodeGenContext): void {
    // Generate import statements
    if (this.options.format === 'esm') {
      context.lines.push("import { createSignal, createEffect } from '@ordojs/runtime';");
    } else {
      context.lines.push("const { createSignal, createEffect } = require('@ordojs/runtime');");
    }
    context.lines.push('');
  }

  private generateClientComponent(component: ComponentNode, context: CodeGenContext): void {
    const componentName = component.name;

    // Function declaration
    context.lines.push(`function ${componentName}(props = {}) {`);
    context.indent++;

    // Generate reactive variables
    if (component.clientBlock?.reactiveVariables) {
      for (const variable of component.clientBlock.reactiveVariables) {
        this.generateReactiveVariable(variable, context);
      }
      context.lines.push('');
    }

    // Generate functions
    if (component.clientBlock?.functions) {
      for (const func of component.clientBlock.functions) {
        this.generateFunction(func, context);
      }
      context.lines.push('');
    }

    // Generate lifecycle hooks
    if (component.clientBlock?.lifecycle) {
      for (const hook of component.clientBlock.lifecycle) {
        this.generateLifecycleHook(hook, context);
      }
      context.lines.push('');
    }

    // Generate render function
    this.generateRenderFunction(component.markupBlock, context);

    // Return component API
    context.lines.push('  return {');
    context.lines.push('    mount,');
    context.lines.push('    unmount,');
    context.lines.push('    render,');
    context.lines.push('    getState: () => ({ ...state }),');
    context.lines.push('    setState: (key, value) => { state[key] = value; }');
    context.lines.push('  };');

    context.indent--;
    context.lines.push('}');
    context.lines.push('');
  }

  private generateReactiveVariable(variable: any, context: CodeGenContext): void {
    const indent = '  '.repeat(context.indent);
    const initialValue = this.generateExpression(variable.initialValue);

    if (variable.isConst) {
      context.lines.push(`${indent}const ${variable.name} = ${initialValue};`);
    } else {
      context.lines.push(`${indent}const [${variable.name}, set${this.capitalize(variable.name)}] = createSignal(${initialValue});`);
    }
  }

  private generateFunction(func: any, context: CodeGenContext): void {
    const indent = '  '.repeat(context.indent);
    const params = func.parameters.map((p: any) => p.name).join(', ');

    context.lines.push(`${indent}function ${func.name}(${params}) {`);

    for (const statement of func.body) {
      this.generateStatement(statement, context);
    }

    context.lines.push(`${indent}}`);
  }

  private generateLifecycleHook(hook: any, context: CodeGenContext): void {
    const indent = '  '.repeat(context.indent);

    context.lines.push(`${indent}createEffect(() => {`);

    for (const statement of hook.handler) {
      this.generateStatement(statement, context);
    }

    context.lines.push(`${indent}});`);
  }

  private generateRenderFunction(markupBlock: MarkupBlockNode, context: CodeGenContext): void {
    const indent = '  '.repeat(context.indent);

    context.lines.push(`${indent}function render() {`);
    context.lines.push(`${indent}  const element = document.createElement('div');`);
    context.lines.push(`${indent}  element.className = 'ordojs-component';`);

    // Generate markup rendering logic
    for (const element of markupBlock.elements) {
      this.generateHTMLElement(element, context);
    }

    context.lines.push(`${indent}  return element;`);
    context.lines.push(`${indent}}`);
    context.lines.push('');

    context.lines.push(`${indent}function mount(target) {`);
    context.lines.push(`${indent}  const element = render();`);
    context.lines.push(`${indent}  target.appendChild(element);`);
    context.lines.push(`${indent}}`);
    context.lines.push('');

    context.lines.push(`${indent}function unmount() {`);
    context.lines.push(`${indent}  // Cleanup logic`);
    context.lines.push(`${indent}}`);
  }

  private generateHTMLElement(element: any, context: CodeGenContext): void {
    const indent = '  '.repeat(context.indent + 1);
    context.lines.push(`${indent}// HTML element: ${element.tagName || 'div'}`);
  }

  private generateStatement(statement: StatementNode, context: CodeGenContext): void {
    const indent = '  '.repeat(context.indent + 1);

    if (statement.expression) {
      const expr = this.generateExpression(statement.expression);
      context.lines.push(`${indent}${expr};`);
    }
  }

  private generateExpression(expression: ExpressionNode): string {
    switch (expression.expressionType) {
      case 'LITERAL':
        return JSON.stringify(expression.value);
      case 'IDENTIFIER':
        return expression.identifier || 'undefined';
      case 'BINARY':
        const left = this.generateExpression(expression.left!);
        const right = this.generateExpression(expression.right!);
        return `${left} ${expression.operator} ${right}`;
      case 'CALL':
        const callee = this.generateExpression(expression.callee!);
        const args = expression.arguments?.map(arg => this.generateExpression(arg)).join(', ') || '';
        return `${callee}(${args})`;
      default:
        return 'undefined';
    }
  }

  private generateServerImports(ast: ComponentAST, context: CodeGenContext): void {
    context.lines.push("const { createServerFunction } = require('@ordojs/runtime');");
    context.lines.push('');
  }

  private generateServerFunctions(serverBlock: ServerBlockNode, context: CodeGenContext): void {
    for (const func of serverBlock.functions) {
      this.generateServerFunction(func, context);
    }
  }

  private generateServerFunction(func: any, context: CodeGenContext): void {
    const params = func.parameters.map((p: any) => p.name).join(', ');
    const visibility = func.isPublic ? 'public' : 'private';

    context.lines.push(`// ${visibility} server function`);
    context.lines.push(`async function ${func.name}(${params}) {`);

    for (const statement of func.body) {
      this.generateStatement(statement, context);
    }

    context.lines.push('}');
    context.lines.push('');

    if (func.isPublic) {
      context.exports.add(func.name);
    }
  }

  private generateServerExports(ast: ComponentAST, context: CodeGenContext): void {
    if (context.exports.size > 0) {
      const exports = Array.from(context.exports).join(', ');

      if (this.options.format === 'esm') {
        context.lines.push(`export { ${exports} };`);
      } else {
        context.lines.push(`module.exports = { ${exports} };`);
      }
    }
  }

  private generateClientExports(ast: ComponentAST, context: CodeGenContext): void {
    const componentName = ast.component.name;

    if (this.options.format === 'esm') {
      context.lines.push(`export default ${componentName};`);
      context.lines.push(`export { ${componentName} };`);
    } else if (this.options.format === 'cjs') {
      context.lines.push(`module.exports = ${componentName};`);
      context.lines.push(`module.exports.${componentName} = ${componentName};`);
    } else if (this.options.format === 'umd') {
      context.lines.push(`
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = global || self, factory(global.OrdoJS = {}));
}(this, (function (exports) {
  exports.${componentName} = ${componentName};
})));`);
    } else if (this.options.format === 'iife') {
      context.lines.push(`window.${componentName} = ${componentName};`);
    }
  }

  private generateHTMLStructure(markupBlock: MarkupBlockNode, context: CodeGenContext, props: Props): void {
    context.lines.push('<div class="ordojs-component">');

    // Generate HTML from markup block
    for (const element of markupBlock.elements) {
      context.lines.push(`  <!-- ${element.tagName || 'element'} -->`);
      context.lines.push('  <div>Generated HTML content</div>');
    }

    context.lines.push('</div>');
  }

  private applyTransformers(code: string, context: TransformContext): string {
    const phases: Array<'pre' | 'post' | 'optimize'> = ['pre', 'post', 'optimize'];

    for (const phase of phases) {
      const transformers = this.transformers.get(phase) || [];

      for (const transformer of transformers) {
        if (transformer.targets.includes(context.target)) {
          const result = transformer.transform(code, context);
          code = result.code;
        }
      }
    }

    return code;
  }

  private createEmptySourceMap(): SourceMap {
    return {
      version: 3,
      sources: [],
      names: [],
      mappings: '',
      sourcesContent: []
    };
  }

  private capitalize(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
}

/**
 * Default code transformers
 */
export const defaultTransformers: CodeTransformer[] = [
  {
    name: 'import-optimizer',
    phase: 'optimize',
    targets: ['client', 'server'],
    transform: (code: string, context: TransformContext): TransformResult => {
      // Remove unused imports
      const lines = code.split('\n');
      const optimizedLines = lines.filter(line => {
        if (line.trim().startsWith('import') || line.trim().startsWith('const')) {
          // Simple check - in production, use proper AST analysis
          return true;
        }
        return true;
      });

      return { code: optimizedLines.join('\n') };
    }
  },
  {
    name: 'dead-code-eliminator',
    phase: 'optimize',
    targets: ['client', 'server'],
    transform: (code: string, context: TransformContext): TransformResult => {
      if (!context.options.treeShaking) {
        return { code };
      }

      // Simple dead code elimination - in production, use proper analysis
      const lines = code.split('\n');
      const optimizedLines = lines.filter(line => {
        const trimmed = line.trim();
        return trimmed !== '' && !trimmed.startsWith('//');
      });

      return { code: optimizedLines.join('\n') };
    }
  }
];
