import { updateExcalidrawLib } from "src/constants/constants";
import { ExcalidrawLib } from "../../types/excalidrawLib";
import { Packages } from "../../types/types";
import { Notice } from "obsidian";
import ExcalidrawPlugin from "src/core/main";
import { errorHandler } from "../../utils/ErrorHandler";
import React from "react";
import ReactDOM from "react-dom/client";

declare let REACT_PACKAGES: string;
declare let react: typeof React;
declare let reactDOM: typeof ReactDOM;
declare let excalidrawLib: typeof ExcalidrawLib;
declare const unpackExcalidraw: () => string;

export class PackageManager {
  private packageMap: Map<Window, Packages> = new Map<Window, Packages>();
  private EXCALIDRAW_PACKAGE: string;
  private plugin: ExcalidrawPlugin;
  private fallbackPackage: Packages | null = null;

  constructor(plugin: ExcalidrawPlugin) {
    this.plugin = plugin;

    try {
      this.EXCALIDRAW_PACKAGE = unpackExcalidraw();

      // Use safe evaluation for unpacking the Excalidraw package
      excalidrawLib = errorHandler.safeEval(
        `(function() {${this.EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`,
        "PackageManager constructor - excalidrawLib initialization",
        window,
      );

      if (!excalidrawLib) {
        throw new Error("Failed to initialize excalidrawLib");
      }

      // Update the exported functions
      updateExcalidrawLib();

      // Create a package with the loaded libraries
      const initialPackage = { react, reactDOM, excalidrawLib };

      // Validate the package before storing
      if (this.validatePackage(initialPackage)) {
        this.setPackage(window, initialPackage);
        this.fallbackPackage = initialPackage; // Store a valid package as fallback
      } else {
        throw new Error("Invalid initial package");
      }
    } catch (e) {
      errorHandler.handleError(e, "PackageManager constructor");
      new Notice(
        "Error loading the Excalidraw package. Some features may not work correctly.",
        10000,
      );
      console.error("Error loading the Excalidraw package", e);
    }

    plugin.logStartupEvent("Excalidraw package unpacked");
  }

  /**
   * Validates that a package contains all required components
   */
  private validatePackage(pkg: Packages): boolean {
    if (!pkg) {
      return false;
    }

    // Check that all components exist
    if (!pkg.react || !pkg.reactDOM || !pkg.excalidrawLib) {
      return false;
    }

    // Verify that excalidrawLib has essential methods
    const lib = pkg.excalidrawLib;
    return (
      typeof lib === "object" &&
      lib !== null &&
      typeof lib.restoreElements === "function" &&
      typeof lib.exportToSvg === "function"
    );
  }

  /**
   * Store a package for a specific window
   */
  public setPackage(window: Window, pkg: Packages) {
    if (this.validatePackage(pkg)) {
      this.packageMap.set(window, pkg);

      // Update fallback if we don't have one
      if (!this.fallbackPackage) {
        this.fallbackPackage = pkg;
      }
    } else {
      errorHandler.handleError(
        "Attempted to set invalid package",
        "PackageManager.setPackage",
      );
    }
  }

  public getPackageMap() {
    return this.packageMap;
  }

  /**
   * Gets a package for a window, creating it if necessary
   * with robust error handling
   */
  public getPackage(win: Window): Packages {
    try {
      // Return existing package if available
      if (this.packageMap.has(win)) {
        const pkg = this.packageMap.get(win);
        if (this.validatePackage(pkg)) {
          return pkg;
        }
        // If package exists but is invalid, delete it so we can recreate it
        this.packageMap.delete(win);
      }

      // Create new package
      return errorHandler.wrapWithTryCatch(
        () => {
          // Use safe evaluation to load packages in the window context
          const evalResult = errorHandler.safeEval<{
            react: typeof React;
            reactDOM: typeof ReactDOM;
            excalidrawLib: typeof ExcalidrawLib;
          }>(
            `(function() {
            ${REACT_PACKAGES + this.EXCALIDRAW_PACKAGE};
            return {react: React, reactDOM: ReactDOM, excalidrawLib: ExcalidrawLib};
          })()`,
            "PackageManager.getPackage - package evaluation",
            win,
          );

          if (!evalResult || !this.validatePackage(evalResult)) {
            throw new Error("Failed to create valid package");
          }

          const newPackage = {
            react: evalResult.react,
            reactDOM: evalResult.reactDOM,
            excalidrawLib: evalResult.excalidrawLib,
          };

          this.packageMap.set(win, newPackage);
          return newPackage;
        },
        "PackageManager.getPackage",
        this.fallbackPackage,
      );
    } catch (error) {
      errorHandler.handleError(error, "PackageManager.getPackage");

      // Return fallback package if available to prevent data loss
      if (this.fallbackPackage) {
        return this.fallbackPackage;
      }

      // If no fallback, throw error to prevent undefined behavior
      throw new Error("Failed to get package and no fallback available");
    }
  }

  public deletePackage(win: Window) {
    try {
      const pkg = this.packageMap.get(win);
      if (!pkg) {
        return;
      }

      const { react, reactDOM, excalidrawLib } = pkg;

      if (win.ExcalidrawLib === excalidrawLib) {
        // Safely clean up resources
        errorHandler.wrapWithTryCatch(() => {
          if (
            excalidrawLib &&
            typeof excalidrawLib.destroyObsidianUtils === "function"
          ) {
            excalidrawLib.destroyObsidianUtils();
          }
          delete win.ExcalidrawLib;
        }, "PackageManager.deletePackage - cleanup ExcalidrawLib");
      }

      if (win.React === react) {
        errorHandler.wrapWithTryCatch(() => {
          delete win.React;
        }, "PackageManager.deletePackage - cleanup React");
      }

      if (win.ReactDOM === reactDOM) {
        errorHandler.wrapWithTryCatch(() => {
          delete win.ReactDOM;
        }, "PackageManager.deletePackage - cleanup ReactDOM");
      }

      this.packageMap.delete(win);
    } catch (error) {
      errorHandler.handleError(error, "PackageManager.deletePackage");
    }
  }

  public setExcalidrawPackage(pkg: string) {
    this.EXCALIDRAW_PACKAGE = pkg;
  }

  public destroy() {
    try {
      REACT_PACKAGES = "";

      Array.from(this.packageMap.entries()).forEach(([win, _]) => {
        this.deletePackage(win);
      });

      this.packageMap.clear();
      this.EXCALIDRAW_PACKAGE = "";
      this.fallbackPackage = null;

      react = null;
      reactDOM = null;
      excalidrawLib = null;
    } catch (error) {
      errorHandler.handleError(error, "PackageManager.destroy");
    }
  }
}
