/**
 * Component Discovery
 *
 * Automatically discovers Hypen components from the filesystem.
 *
 * Supports multiple conventions:
 *   1. Folder-based: ComponentName/component.ts + ComponentName/component.hypen
 *   2. Sibling files: ComponentName.ts + ComponentName.hypen
 *   3. Index-based: ComponentName/index.ts + ComponentName/index.hypen
 */

import { existsSync, readdirSync, readFileSync, watch } from "fs";
import { join, basename, resolve } from "path";
import type { HypenModuleDefinition } from "./app.js";

export interface DiscoveredComponent {
  name: string;
  hypenPath: string;
  modulePath: string | null;
  template: string;
  hasModule: boolean;
}

export interface DiscoveryOptions {
  /**
   * Which naming patterns to look for
   * Default: ["folder", "sibling", "index"]
   */
  patterns?: ("folder" | "sibling" | "index")[];

  /**
   * Recursively scan subdirectories
   * Default: false
   */
  recursive?: boolean;

  /**
   * Enable debug logging
   * Default: false
   */
  debug?: boolean;
}

export interface WatchOptions extends DiscoveryOptions {
  /**
   * Callback when components change
   */
  onChange?: (components: DiscoveredComponent[]) => void;

  /**
   * Callback when a component is added
   */
  onAdd?: (component: DiscoveredComponent) => void;

  /**
   * Callback when a component is removed
   */
  onRemove?: (name: string) => void;

  /**
   * Callback when a component is updated
   */
  onUpdate?: (component: DiscoveredComponent) => void;
}

/**
 * Remove import statements from Hypen template
 */
function removeImports(text: string): string {
  return text.replace(
    /import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g,
    ""
  );
}

/**
 * Discover all Hypen components in a directory
 */
export async function discoverComponents(
  baseDir: string,
  options: DiscoveryOptions = {}
): Promise<DiscoveredComponent[]> {
  const {
    patterns = ["folder", "sibling", "index"],
    recursive = false,
    debug = false,
  } = options;

  const log = debug
    ? (...args: unknown[]) => console.log("[discovery]", ...args)
    : () => {};

  const resolvedDir = resolve(baseDir);
  const components: DiscoveredComponent[] = [];
  const seen = new Set<string>();

  log("Scanning directory:", resolvedDir);
  log("Patterns:", patterns);

  // Helper to add a component if not already seen
  const addComponent = (
    name: string,
    hypenPath: string,
    modulePath: string | null
  ) => {
    if (seen.has(name)) {
      log(`Skipping duplicate: ${name}`);
      return;
    }

    seen.add(name);
    const templateRaw = readFileSync(hypenPath, "utf-8");
    const template = removeImports(templateRaw).trim();

    components.push({
      name,
      hypenPath,
      modulePath,
      template,
      hasModule: modulePath !== null,
    });

    log(`Found: ${name} (${modulePath ? "with module" : "stateless"})`);
  };

  // Scan directory for folder-based components
  const scanForFolderComponents = (dir: string) => {
    if (!existsSync(dir)) return;

    const entries = readdirSync(dir, { withFileTypes: true });

    for (const entry of entries) {
      if (!entry.isDirectory()) continue;

      const folderPath = join(dir, entry.name);
      const componentName = entry.name;

      // Check folder-based pattern: Name/component.hypen
      if (patterns.includes("folder")) {
        const hypenPath = join(folderPath, "component.hypen");
        if (existsSync(hypenPath)) {
          const modulePath = join(folderPath, "component.ts");
          addComponent(
            componentName,
            hypenPath,
            existsSync(modulePath) ? modulePath : null
          );
          continue;
        }
      }

      // Check index-based pattern: Name/index.hypen
      if (patterns.includes("index")) {
        const hypenPath = join(folderPath, "index.hypen");
        if (existsSync(hypenPath)) {
          const modulePath = join(folderPath, "index.ts");
          addComponent(
            componentName,
            hypenPath,
            existsSync(modulePath) ? modulePath : null
          );
          continue;
        }
      }

      // Recursive scan
      if (recursive) {
        scanForFolderComponents(folderPath);
      }
    }
  };

  // Scan for sibling file components
  const scanForSiblingComponents = (dir: string) => {
    if (!existsSync(dir)) return;

    const entries = readdirSync(dir, { withFileTypes: true });

    for (const entry of entries) {
      if (entry.isDirectory()) {
        if (recursive) {
          scanForSiblingComponents(join(dir, entry.name));
        }
        continue;
      }

      if (!entry.name.endsWith(".hypen")) continue;

      const hypenPath = join(dir, entry.name);
      const baseName = basename(entry.name, ".hypen");

      // Skip component.hypen and index.hypen (handled by folder patterns)
      if (baseName === "component" || baseName === "index") continue;

      const modulePath = join(dir, `${baseName}.ts`);
      addComponent(
        baseName,
        hypenPath,
        existsSync(modulePath) ? modulePath : null
      );
    }
  };

  // Run scans based on patterns
  if (patterns.includes("folder") || patterns.includes("index")) {
    scanForFolderComponents(resolvedDir);
  }

  if (patterns.includes("sibling")) {
    scanForSiblingComponents(resolvedDir);
  }

  log(`Discovered ${components.length} components`);

  return components;
}

/**
 * Load discovered components into a map for use with Hypen
 */
export async function loadDiscoveredComponents(
  components: DiscoveredComponent[]
): Promise<
  Map<
    string,
    {
      name: string;
      module: HypenModuleDefinition<any>;
      template: string;
    }
  >
> {
  // Dynamically import app to avoid circular dependencies
  const { app } = await import("./app.js");

  const loaded = new Map<
    string,
    {
      name: string;
      module: HypenModuleDefinition;
      template: string;
    }
  >();

  for (const component of components) {
    let module: HypenModuleDefinition<any>;

    if (component.modulePath) {
      // Import the TypeScript module
      const moduleExport = await import(component.modulePath);
      module = moduleExport.default as HypenModuleDefinition<any>;
    } else {
      // Create a stateless module
      module = app.defineState({}).build();
    }

    loaded.set(component.name, {
      name: component.name,
      module,
      template: component.template,
    });
  }

  return loaded;
}

/**
 * Watch a directory for component changes
 */
export function watchComponents(
  baseDir: string,
  options: WatchOptions = {}
): { stop: () => void } {
  const resolvedDir = resolve(baseDir);
  const {
    onChange,
    onAdd,
    onRemove,
    onUpdate,
    debug = false,
    ...discoveryOptions
  } = options;

  const log = debug
    ? (...args: unknown[]) => console.log("[discovery:watch]", ...args)
    : () => {};

  let currentComponents = new Map<string, DiscoveredComponent>();
  let debounceTimer: ReturnType<typeof setTimeout> | null = null;

  // Initial scan
  const initialScan = async () => {
    const components = await discoverComponents(resolvedDir, discoveryOptions);
    currentComponents = new Map(components.map((c) => [c.name, c]));
    onChange?.(components);
  };

  // Debounced rescan
  const rescan = async () => {
    if (debounceTimer) {
      clearTimeout(debounceTimer);
    }

    debounceTimer = setTimeout(async () => {
      log("Rescanning...");
      const newComponents = await discoverComponents(
        resolvedDir,
        discoveryOptions
      );
      const newMap = new Map(newComponents.map((c) => [c.name, c]));

      // Find added/removed/updated
      for (const [name, component] of newMap) {
        const existing = currentComponents.get(name);
        if (!existing) {
          log("Added:", name);
          onAdd?.(component);
        } else if (
          existing.template !== component.template ||
          existing.modulePath !== component.modulePath
        ) {
          log("Updated:", name);
          onUpdate?.(component);
        }
      }

      for (const name of currentComponents.keys()) {
        if (!newMap.has(name)) {
          log("Removed:", name);
          onRemove?.(name);
        }
      }

      currentComponents = newMap;
      onChange?.(newComponents);
    }, 100);
  };

  // Start watching
  const watcher = watch(
    resolvedDir,
    { recursive: true },
    (event, filename) => {
      if (!filename) return;
      if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
        log("File changed:", filename);
        rescan();
      }
    }
  );

  // Initial scan
  initialScan();

  return {
    stop: () => {
      watcher.close();
      if (debounceTimer) {
        clearTimeout(debounceTimer);
      }
    },
  };
}

/**
 * Generate a components object from discovered components
 * Useful for static imports / code generation
 */
export async function generateComponentsCode(
  baseDir: string,
  options: DiscoveryOptions = {}
): Promise<string> {
  const components = await discoverComponents(baseDir, options);
  const resolvedDir = resolve(baseDir);

  let code = `/**
 * Auto-generated component imports
 * Generated by @hypen-space/core discovery
 */

`;

  for (const component of components) {
    const relativePath = component.modulePath
      ? "./" +
        component.modulePath
          .replace(resolvedDir + "/", "")
          .replace(/\.ts$/, ".js")
      : null;

    if (relativePath) {
      code += `import ${component.name}Module from "${relativePath}";\n`;
    }
  }

  code += `\nimport { app } from "@hypen-space/core";\n\n`;

  for (const component of components) {
    const templateJson = JSON.stringify(component.template);

    if (component.hasModule) {
      code += `export const ${component.name} = {
  module: ${component.name}Module,
  template: ${templateJson},
};\n\n`;
    } else {
      code += `const ${component.name}Module = app.defineState({}).build();
export const ${component.name} = {
  module: ${component.name}Module,
  template: ${templateJson},
};\n\n`;
    }
  }

  return code;
}
