/**
 * Extension Loader
 * Connects Extension Registry (manifests) with Extension Manager (lifecycle)
 */

import { convertFileSrc } from "@tauri-apps/api/core";
import { extensionManager } from "@/features/editor/extensions/manager";
import type { EditorAPI, ExtensionContext } from "@/features/editor/extensions/types";
import { logger } from "@/features/editor/utils/logger";
import { extensionRegistry } from "../registry/extension-registry";
import {
  getManifestCommandContributions,
  getManifestLanguageContributions,
} from "../types/extension-contributions";
import { activateExtensionContributions } from "../runtime/extension-contribution-runtime";
import type { BundledExtension } from "../types/extension-manifest";

/**
 * Create a minimal editor API for extension initialization
 * Extensions are loaded before the actual editor mounts
 */
function createDummyEditorAPI(): EditorAPI {
  return {
    getContent: () => "",
    setContent: () => {},
    getSelection: () => null,
    setSelection: () => {},
    getCursorPosition: () => ({ line: 0, column: 0, offset: 0 }),
    setCursorPosition: () => {},
    insertText: () => {},
    deleteRange: () => {},
    replaceRange: () => {},
    getLineCount: () => 0,
    getLines: () => [],
    getLine: () => undefined,
    duplicateLine: () => {},
    deleteLine: () => {},
    toggleComment: () => {},
    goToMatchingBracket: () => {},
    selectToBracket: () => {},
    removeBrackets: () => {},
    expandSelection: () => {},
    shrinkSelection: () => {},
    insertCursorAbove: () => {},
    insertCursorBelow: () => {},
    insertCursorsAtLineEnds: () => {},
    moveLineUp: () => {},
    moveLineDown: () => {},
    copyLineUp: () => {},
    copyLineDown: () => {},
    addDecoration: () => "",
    removeDecoration: () => {},
    updateDecoration: () => {},
    clearDecorations: () => {},
    undo: () => {},
    redo: () => {},
    canUndo: () => false,
    canRedo: () => false,
    selectAll: () => {},
    getSettings: () => ({
      fontSize: 14,
      lineHeight: 1.4,
      tabSize: 2,
      lineNumbers: true,
      wordWrap: false,
      renderWhitespace: "none",
      renderIndentGuides: true,
      theme: "athas-dark",
    }),
    updateSettings: () => {},
    on: () => () => {},
    off: () => {},
    emitEvent: () => {},
  };
}

/**
 * Convert a local file path to a fetchable URL
 * Uses convertFileSrc for absolute paths in Tauri
 */
async function toFetchableUrl(path: string): Promise<string> {
  // If it's already a URL (starts with http, https, or /), return as-is
  if (path.startsWith("http://") || path.startsWith("https://") || path.startsWith("/")) {
    return path;
  }
  // For absolute file paths, convert using Tauri's asset protocol
  return convertFileSrc(path);
}

/**
 * Generic LSP Extension
 * Handles any language with LSP support based on manifest
 */
class GenericLspExtension {
  private extension: BundledExtension;
  private isActivated = false;

  constructor(extension: BundledExtension) {
    this.extension = extension;
  }

  async activate(context: ExtensionContext): Promise<void> {
    if (this.isActivated) return;

    const manifest = this.extension.manifest;
    logger.info("ExtensionLoader", `Activating ${manifest.displayName} extension`);

    // Register languages
    for (const lang of getManifestLanguageContributions(manifest)) {
      context.registerLanguage({
        id: lang.id,
        extensions: lang.extensions,
        aliases: lang.aliases,
      });
    }

    // Load tree-sitter grammar if present
    if (manifest.grammar) {
      await this.loadGrammar(manifest.grammar);
    }

    // Register commands from manifest
    for (const cmd of getManifestCommandContributions(manifest)) {
      context.registerCommand(cmd.command, async () => {
        // Handle restart command
        if (cmd.command.includes("restart")) {
          await this.restartLSP();
        }
        // Handle toggle command
        else if (cmd.command.includes("toggle")) {
          await this.toggleLSP();
        }
      });
    }

    this.isActivated = true;
    logger.info("ExtensionLoader", `${manifest.displayName} extension activated`);
  }

  private async loadGrammar(grammar: any): Promise<void> {
    try {
      const basePath = this.extension.path;
      const isRelativeWasm = grammar.wasmPath.startsWith("./");

      // Resolve WASM path (relative to extension or absolute)
      let wasmPath = isRelativeWasm
        ? `${basePath}/${grammar.wasmPath.substring(2)}`
        : grammar.wasmPath;

      // Convert to fetchable URL if it was a relative path (now absolute file path)
      if (isRelativeWasm) {
        wasmPath = await toFetchableUrl(wasmPath);
      }

      logger.info("ExtensionLoader", `Loading grammar from ${wasmPath}`);

      // Fetch highlight query if path is provided
      let highlightQuery: string | undefined;
      if (grammar.highlightQueryPath) {
        try {
          const isRelativeQuery = grammar.highlightQueryPath.startsWith("./");

          let queryPath = isRelativeQuery
            ? `${basePath}/${grammar.highlightQueryPath.substring(2)}`
            : grammar.highlightQueryPath;

          // Convert to fetchable URL if it was a relative path
          if (isRelativeQuery) {
            queryPath = await toFetchableUrl(queryPath);
          }

          logger.info("ExtensionLoader", `Fetching highlight query from ${queryPath}`);

          const response = await fetch(queryPath);
          if (response.ok) {
            highlightQuery = await response.text();
            logger.info("ExtensionLoader", `Highlight query loaded for ${grammar.languageId}`);
          } else {
            logger.warn(
              "ExtensionLoader",
              `Failed to fetch highlight query from ${queryPath}: ${response.status}`,
            );
          }
        } catch (error) {
          logger.warn("ExtensionLoader", `Failed to load highlight query:`, error);
        }
      }

      // Load the tree-sitter parser
      const { wasmParserLoader } =
        await import("@/features/editor/lib/wasm-parser/wasm-parser-api");
      await wasmParserLoader.loadParser({
        languageId: grammar.languageId,
        wasmPath,
        highlightQuery,
      });

      logger.info("ExtensionLoader", `Grammar loaded for ${grammar.languageId}`);
    } catch (error) {
      logger.error("ExtensionLoader", `Failed to load grammar:`, error);
    }
  }

  async deactivate(): Promise<void> {
    this.isActivated = false;
    logger.info("ExtensionLoader", `${this.extension.manifest.displayName} extension deactivated`);
  }

  private async restartLSP(): Promise<void> {
    logger.info("ExtensionLoader", `Restarting LSP for ${this.extension.manifest.name}`);
    // LSP restart logic will be handled by the LSP manager
    // This is a placeholder for future implementation
  }

  private async toggleLSP(): Promise<void> {
    logger.info("ExtensionLoader", `Toggling LSP for ${this.extension.manifest.name}`);
    // LSP toggle logic will be handled by the LSP manager
    // This is a placeholder for future implementation
  }
}

/**
 * Extension Loader Service
 * Bridges Extension Registry and Extension Manager
 */
class ExtensionLoader {
  private loadedExtensions = new Set<string>();
  private initPromise: Promise<void> | null = null;

  /**
   * Wait for initialization to complete
   */
  async waitForInitialization(): Promise<void> {
    if (this.initPromise) {
      await this.initPromise;
    }
  }

  /**
   * Initialize all bundled extensions
   */
  async initialize(): Promise<void> {
    // Store promise so others can wait for it
    this.initPromise = this._initialize();
    return this.initPromise;
  }

  private async _initialize(): Promise<void> {
    logger.info("ExtensionLoader", "Initializing extension system");

    // Ensure extension manager is initialized with a dummy editor API
    // Extensions are loaded before the actual editor mounts
    if (!extensionManager.isInitialized()) {
      extensionManager.initialize();
      extensionManager.setEditor(createDummyEditorAPI());
    }

    // Wait for extension registry to be fully initialized
    await extensionRegistry.ensureInitialized();

    // Load all extensions from registry
    const extensions = extensionRegistry.getAllExtensions();

    const results = await Promise.allSettled(extensions.map((ext) => this.loadExtension(ext)));

    for (let i = 0; i < results.length; i++) {
      const result = results[i];
      if (result.status === "rejected") {
        logger.error(
          "ExtensionLoader",
          `Failed to load extension ${extensions[i].manifest.displayName}:`,
          result.reason,
        );
      }
    }

    logger.info("ExtensionLoader", `Loaded ${this.loadedExtensions.size} extensions`);
  }

  /**
   * Load a single extension
   */
  private async loadExtension(extension: BundledExtension): Promise<void> {
    if (this.loadedExtensions.has(extension.manifest.id)) {
      logger.warn("ExtensionLoader", `Extension ${extension.manifest.id} already loaded`);
      return;
    }

    logger.info("ExtensionLoader", `Loading extension: ${extension.manifest.displayName}`);

    // Create extension instance
    const extensionInstance = new GenericLspExtension(extension);

    // Convert to new extension format for Extension Manager
    const newExtension = {
      id: extension.manifest.id,
      displayName: extension.manifest.displayName,
      version: extension.manifest.version,
      description: extension.manifest.description,
      contributes: {
        commands: getManifestCommandContributions(extension.manifest).map((cmd) => ({
          id: cmd.command,
          title: cmd.title,
          category: cmd.category,
        })),
      },
      activate: async (context: ExtensionContext) => {
        await extensionInstance.activate(context);
      },
      deactivate: async () => {
        await extensionInstance.deactivate();
      },
    };

    // Load into Extension Manager
    await extensionManager.loadNewExtension(newExtension);

    // Mark extension as activated in registry
    extensionRegistry.setExtensionState(extension.manifest.id, "activated");

    await activateExtensionContributions(extension.manifest.id, extension.manifest, extension.path);

    this.loadedExtensions.add(extension.manifest.id);
    logger.info(
      "ExtensionLoader",
      `Extension ${extension.manifest.displayName} loaded successfully`,
    );
  }

  /**
   * Get loaded extension count
   */
  getLoadedCount(): number {
    return this.loadedExtensions.size;
  }

  /**
   * Check if extension is loaded
   */
  isExtensionLoaded(extensionId: string): boolean {
    return this.loadedExtensions.has(extensionId);
  }
}

// Global extension loader instance
export const extensionLoader = new ExtensionLoader();
