/**
 * @fileoverview OrdoJS SSR Engine - Server-side rendering implementation
 */

import {
  type ComponentAST,
  type ComponentData,
  type HydrationData,
  type Props
} from '../types/index.js';
import { OrdoJSCodeGenerator } from './code-generator-fixed.js';

/**
 * SSR Engine options
 */
export interface SSROptions {
  /**
   * Whether to include hydration markers in the generated HTML
   */
  includeHydrationMarkers: boolean;

  /**
   * Whether to include component data for client-side hydration
   */
  includeHydrationData: boolean;

  /**
   * Custom data fetching function
   */
  dataFetcher?: (component: string, props: Props) => Promise<Record<string, any>>;

  /**
   * Route configuration for server-side routing
   */
  routes?: RouteConfig[];
}

/**
 * Route configuration for server-side routing
 */
export interface RouteConfig {
  /**
   * Route path pattern (e.g., '/users/:id')
   */
  path: string;

  /**
   * Component name to render for this route
   */
  component: string;

  /**
   * Data fetching function for this route
   */
  dataFetcher?: (params: Record<string, string>, query: Record<string, string>) => Promise<Record<string, any>>;

  /**
   * Layout component to wrap the route component
   */
  layout?: string;
}

/**
 * Default SSR options
 */
const DEFAULT_SSR_OPTIONS: SSROptions = {
  includeHydrationMarkers: true,
  includeHydrationData: true
};

/**
 * OrdoJS SSR Engine
 * Handles server-side rendering of components
 */
export class OrdoJSSSR {
  options: SSROptions;
  private codeGenerator: OrdoJSCodeGenerator;
  private componentRegistry: Map<string, ComponentAST> = new Map();

  constructor(options: Partial<SSROptions> = {}) {
    this.options = { ...DEFAULT_SSR_OPTIONS, ...options };
    this.codeGenerator = new OrdoJSCodeGenerator({
      minify: false,
      sourceMaps: false,
      target: 'production'
    });
  }

  /**
   * Register a component for SSR
   */
  registerComponent(ast: ComponentAST): void {
    this.componentRegistry.set(ast.component.name, ast);
  }

  /**
   * Register multiple components for SSR
   */
  registerComponents(components: ComponentAST[]): void {
    for (const component of components) {
      this.registerComponent(component);
    }
  }

  /**
   * Render a component to HTML string
   */
  async renderComponent(componentName: string, props: Props = {}): Promise<string> {
    const ast = this.componentRegistry.get(componentName);
    if (!ast) {
      throw new Error(`Component "${componentName}" not found in registry`);
    }

    // Fetch data if needed
    let componentData = {};
    if (this.options.dataFetcher) {
      componentData = await this.options.dataFetcher(componentName, props);
    }

    // Generate HTML with hydration markers
    const html = this.codeGenerator.generateHTML(ast, { ...props, ...componentData });

    // Generate hydration data if needed
    if (this.options.includeHydrationData) {
      const hydrationData = this.generateHydrationData(ast, { ...props, ...componentData });
      return this.injectHydrationData(html, hydrationData);
    }

    return html;
  }

  /**
   * Generate hydration data for a component
   */
  generateHydrationData(ast: ComponentAST, data: Record<string, any> = {}): HydrationData {
    return {
      componentName: ast.component.name,
      componentId: `ordojs_${ast.component.name}_${Date.now().toString(36)}`,
      props: data,
      initialState: this.extractInitialState(ast),
      version: '1.0'
    };
  }

  /**
   * Extract initial state from component AST
   */
  private extractInitialState(ast: ComponentAST): Record<string, any> {
    const initialState: Record<string, any> = {};

    if (ast.component.clientBlock) {
      for (const variable of ast.component.clientBlock.reactiveVariables) {
        if (variable.initialValue && variable.initialValue.expressionType === 'LITERAL') {
          initialState[variable.name] = variable.initialValue.value;
        } else {
          // For non-literal initial values, we'll use null as a placeholder
          initialState[variable.name] = null;
        }
      }
    }

    return initialState;
  }

  /**
   * Inject hydration data into HTML
   */
  private injectHydrationData(html: string, hydrationData: HydrationData): string {
    const hydrationScript = `
<script type="application/json" id="ordojs-hydration-data">
${JSON.stringify(hydrationData, null, 2)}
</script>
`;

    // Insert before closing body tag if present, otherwise append
    if (html.includes('</body>')) {
      return html.replace('</body>', `${hydrationScript}</body>`);
    }

    return html + hydrationScript;
  }

  /**
   * Handle data fetching for SSR
   */
  async handleDataFetching(componentName: string, props: Props = {}): Promise<ComponentData> {
    const ast = this.componentRegistry.get(componentName);
    if (!ast || !ast.component.serverBlock) {
      return { props };
    }

    // Find data fetching functions in server block
    const dataFetchers = ast.component.serverBlock.functions.filter(
      f => f.name === 'getServerSideProps' || f.name === 'getStaticProps'
    );

    if (dataFetchers.length === 0) {
      return { props };
    }

    // Execute data fetcher function if available
    if (this.options.dataFetcher) {
      const data = await this.options.dataFetcher(componentName, props);
      return {
        props: { ...props, ...data }
      };
    }

    return { props };
  }

  /**
   * Render a route to HTML
   */
  async renderRoute(url: string): Promise<string> {
    if (!this.options.routes || this.options.routes.length === 0) {
      throw new Error('No routes configured for SSR');
    }

    // Parse URL
    const parsedUrl = new URL(url, 'http://localhost');
    const pathname = parsedUrl.pathname;

    // Find matching route
    const route = this.findMatchingRoute(pathname);
    if (!route) {
      throw new Error(`No route found for path: ${pathname}`);
    }

    // Extract route params and query params
    const params = this.extractRouteParams(route.path, pathname);
    const query = this.extractQueryParams(parsedUrl.searchParams);

    // Fetch data for route
    let routeData = {};
    if (route.dataFetcher) {
      routeData = await route.dataFetcher(params, query);
    }

    // Render component with data
    const componentHtml = await this.renderComponent(route.component, {
      ...routeData,
      routeParams: params,
      queryParams: query
    });

    // Wrap with layout if specified
    if (route.layout) {
      return this.wrapWithLayout(componentHtml, route.layout, {
        ...routeData,
        routeParams: params,
        queryParams: query
      });
    }

    return componentHtml;
  }

  /**
   * Find a matching route for a path
   */
  private findMatchingRoute(pathname: string): RouteConfig | undefined {
    if (!this.options.routes) return undefined;

    return this.options.routes.find(route => {
      // Convert route path to regex
      const pattern = route.path
        .replace(/:[^/]+/g, '([^/]+)')
        .replace(/\*/g, '.*');

      const regex = new RegExp(`^${pattern}$`);
      return regex.test(pathname);
    });
  }

  /**
   * Extract route parameters from path
   */
  private extractRouteParams(routePath: string, pathname: string): Record<string, string> {
    const params: Record<string, string> = {};

    // Extract param names from route path
    const paramNames = (routePath.match(/:[^/]+/g) || [])
      .map(param => param.substring(1));

    // Extract param values from pathname
    const pattern = routePath
      .replace(/:[^/]+/g, '([^/]+)')
      .replace(/\*/g, '.*');

    const regex = new RegExp(`^${pattern}$`);
    const matches = pathname.match(regex);

    if (matches && matches.length > 1 && paramNames.length > 0) {
      // Skip the first match (full string)
      for (let i = 0; i < paramNames.length; i++) {
        if (i < matches.length - 1) {
          params[paramNames[i]] = matches[i + 1];
        }
      }
    }

    return params;
  }

  /**
   * Extract query parameters from URL
   */
  private extractQueryParams(searchParams: URLSearchParams): Record<string, string> {
    const query: Record<string, string> = {};

    searchParams.forEach((value, key) => {
      query[key] = value;
    });

    return query;
  }

  /**
   * Wrap component HTML with a layout
   */
  private async wrapWithLayout(
    componentHtml: string,
    layoutName: string,
    data: Record<string, any>
  ): Promise<string> {
    const ast = this.componentRegistry.get(layoutName);
    if (!ast) {
      throw new Error(`Layout "${layoutName}" not found in registry`);
    }

    // Replace layout content placeholder with component HTML
    const layoutHtml = this.codeGenerator.generateHTML(ast, data);

    // Replace content placeholder with component HTML
    return layoutHtml.replace(
      /<div[^>]*data-content-placeholder[^>]*>.*?<\/div>/i,
      componentHtml
    );
  }

  /**
   * Generate a complete HTML document with SSR content
   */
  generateDocument(
    content: string,
    title: string = 'OrdoJS App',
    scripts: string[] = [],
    styles: string[] = []
  ): string {
    const scriptTags = scripts
      .map(src => `<script src="${src}" defer></script>`)
      .join('\n    ');

    const styleTags = styles
      .map(href => `<link rel="stylesheet" href="${href}">`)
      .join('\n    ');

    return `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${title}</title>
    ${styleTags}
  </head>
  <body>
    <div id="app">${content}</div>
    ${scriptTags}
  </body>
</html>`;
  }
}
