/**
 * Component Resolver
 * Resolves component imports from local paths or web URLs
 */

export interface ImportStatement {
  clause: ImportClause;
  source: ImportSource;
}

export type ImportClause =
  | { type: "named"; names: string[] }
  | { type: "default"; name: string };

export type ImportSource =
  | { type: "local"; path: string }
  | { type: "url"; url: string };

export interface ComponentDefinition {
  module: any;
  template: string;
}

export interface ResolverOptions {
  /**
   * Base directory for resolving relative local paths
   * Defaults to current working directory
   */
  baseDir?: string;

  /**
   * Cache resolved components to avoid re-fetching
   * Defaults to true
   */
  cache?: boolean;

  /**
   * Custom fetch function for URL imports
   * Useful for adding authentication, custom headers, etc.
   */
  customFetch?: (url: string) => Promise<string>;
}

/**
 * Component Resolver
 * Resolves and loads components from local files or remote URLs
 */
export class ComponentResolver {
  private cache = new Map<string, ComponentDefinition>();
  private options: Required<ResolverOptions>;

  constructor(options: ResolverOptions = {}) {
    this.options = {
      baseDir: options.baseDir || process.cwd(),
      cache: options.cache ?? true,
      customFetch: options.customFetch || this.defaultFetch.bind(this),
    };
  }

  /**
   * Resolve a component from an import statement
   */
  async resolve(
    importStmt: ImportStatement
  ): Promise<Record<string, ComponentDefinition>> {
    const sourcePath = this.getSourcePath(importStmt.source);

    // Check cache first
    if (this.options.cache && this.cache.has(sourcePath)) {
      const cached = this.cache.get(sourcePath)!;
      return this.extractComponents(importStmt.clause, cached);
    }

    // Load the component
    let component: ComponentDefinition;
    if (importStmt.source.type === "local") {
      component = await this.resolveLocal(importStmt.source.path);
    } else {
      component = await this.resolveUrl(importStmt.source.url);
    }

    // Cache it
    if (this.options.cache) {
      this.cache.set(sourcePath, component);
    }

    return this.extractComponents(importStmt.clause, component);
  }

  /**
   * Resolve a component from a local file path
   */
  private async resolveLocal(path: string): Promise<ComponentDefinition> {
    // For local paths, we expect the build system to have already
    // processed them into the generated components file
    // This is a fallback for dynamic resolution
    throw new Error(
      `Dynamic local component resolution not yet implemented: ${path}\n` +
        `Please use the build-components.ts script to generate component imports.`
    );
  }

  /**
   * Resolve a component from a URL
   */
  private async resolveUrl(url: string): Promise<ComponentDefinition> {
    try {
      const response = await this.options.customFetch(url);
      const data = JSON.parse(response);

      // Expected format: { module: {...}, template: "..." }
      if (!data.module || !data.template) {
        throw new Error(
          `Invalid component format from ${url}. Expected { module, template }`
        );
      }

      return data;
    } catch (error) {
      throw new Error(
        `Failed to resolve component from ${url}: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  }

  /**
   * Default fetch implementation
   */
  private async defaultFetch(url: string): Promise<string> {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.text();
  }

  /**
   * Extract the requested components based on the import clause
   */
  private extractComponents(
    clause: ImportClause,
    component: ComponentDefinition
  ): Record<string, ComponentDefinition> {
    if (clause.type === "default") {
      return {
        [clause.name]: component,
      };
    } else {
      // Named imports - for now, we only support single exports
      // In the future, we could support exporting multiple components
      // from a single file
      const result: Record<string, ComponentDefinition> = {};
      for (const name of clause.names) {
        result[name] = component;
      }
      return result;
    }
  }

  /**
   * Get the source path as a string (for caching)
   */
  private getSourcePath(source: ImportSource): string {
    return source.type === "local" ? source.path : source.url;
  }

  /**
   * Clear the component cache
   */
  clearCache(): void {
    this.cache.clear();
  }

  /**
   * Parse import statements from Hypen DSL text
   * This is a simple parser that extracts import statements
   */
  static parseImports(text: string): ImportStatement[] {
    const imports: ImportStatement[] = [];
    const importRegex =
      /import\s+(?:(\{[^}]*\})|(\w+))\s+from\s+["']([^"']+)["']/g;

    let match;
    while ((match = importRegex.exec(text)) !== null) {
      const [, namedImports, defaultImport, source] = match;

      if (!source) continue;

      let clause: ImportClause;
      if (namedImports) {
        // Named imports: { Button, Card }
        const names = namedImports
          .slice(1, -1) // Remove { and }
          .split(",")
          .map((n) => n.trim())
          .filter((n) => n.length > 0);
        clause = { type: "named", names };
      } else if (defaultImport) {
        // Default import: HomePage
        clause = { type: "default", name: defaultImport };
      } else {
        continue;
      }

      // Determine if source is URL or local path
      const sourceObj: ImportSource = source.startsWith("http://") ||
        source.startsWith("https://")
        ? { type: "url", url: source }
        : { type: "local", path: source };

      imports.push({ clause, source: sourceObj });
    }

    return imports;
  }
}
