import { existsSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { fileURLToPath, URL } from "node:url";
import { defineConfig, type Plugin } from "vite";
import react from "@vitejs/plugin-react";

const require = createRequire(import.meta.url);
const webuiRoot = dirname(fileURLToPath(import.meta.url));
const installedModulesVirtualId = "virtual:govoplan-installed-modules";
const resolvedInstalledModulesVirtualId = `\0${installedModulesVirtualId}`;
const installedModuleVirtualPrefix = "virtual:govoplan-installed-module/";
const resolvedInstalledModuleVirtualPrefix = `\0${installedModuleVirtualPrefix}`;

const defaultWebModulePackages = [
  "@govoplan/access-webui",
  "@govoplan/approvals-webui",
  "@govoplan/admin-webui",
  "@govoplan/addresses-webui",
  "@govoplan/audit-webui",
  "@govoplan/calendar-webui",
  "@govoplan/cases-webui",
  "@govoplan/campaign-webui",
  "@govoplan/committee-webui",
  "@govoplan/dataflow-webui",
  "@govoplan/datasources-webui",
  "@govoplan/dashboard-webui",
  "@govoplan/docs-webui",
  "@govoplan/files-webui",
  "@govoplan/forms-webui",
  "@govoplan/forms-runtime-webui",
  "@govoplan/idm-webui",
  "@govoplan/mail-webui",
  "@govoplan/notifications-webui",
  "@govoplan/organizations-webui",
  "@govoplan/ops-webui",
  "@govoplan/policy-webui",
  "@govoplan/portal-webui",
  "@govoplan/postbox-webui",
  "@govoplan/projects-webui",
  "@govoplan/reporting-webui",
  "@govoplan/risk-compliance-webui",
  "@govoplan/scheduling-webui",
  "@govoplan/search-webui",
  "@govoplan/tenancy-webui",
  "@govoplan/views-webui",
  "@govoplan/voting-webui",
  "@govoplan/workflow-webui"
];

function configuredWebModulePackages(): string[] {
  const configured = process.env.GOVOPLAN_WEBUI_MODULE_PACKAGES;
  if (configured === undefined) return defaultWebModulePackages;
  return configured.split(",").map((specifier) => specifier.trim()).filter(Boolean);
}

function packageDirectory(specifier: string): string {
  const parts = specifier.startsWith("@") ? specifier.split("/") : [specifier];
  return join(webuiRoot, "node_modules", ...parts);
}

function packageInstalled(specifier: string): boolean {
  if (existsSync(join(packageDirectory(specifier), "package.json"))) return true;
  try {
    require.resolve(specifier);
    return true;
  } catch {
    return false;
  }
}

function availableWebModuleSpecifiers(): string[] {
  return configuredWebModulePackages().filter(packageInstalled);
}

function deferredVendorChunk(id: string): string | undefined {
  const normalized = id.replaceAll("\\", "/");
  if (!normalized.includes("/node_modules/")) return undefined;
  if (
    normalized.includes("/@tiptap/core/")
    || normalized.includes("/@tiptap/react/")
  ) {
    return "rich-text-core";
  }
  if (normalized.includes("/@tiptap/")) {
    return "rich-text-extensions";
  }
  if (
    normalized.includes("/prosemirror-")
    || normalized.includes("/linkifyjs/")
    || normalized.includes("/orderedmap/")
    || normalized.includes("/rope-sequence/")
  ) {
    return "rich-text-engine";
  }
  if (normalized.includes("/bpmn-js-properties-panel/")) {
    return "bpmn-properties-provider";
  }
  if (normalized.includes("/@bpmn-io/properties-panel/")) {
    return "bpmn-properties-ui";
  }
  if (normalized.includes("/@codemirror/view/")) {
    return "bpmn-expression-view";
  }
  if (normalized.includes("/@codemirror/state/")) {
    return "bpmn-expression-state";
  }
  if (normalized.includes("/@codemirror/language/")) {
    return "bpmn-expression-language";
  }
  if (normalized.includes("/@codemirror/")) {
    return "bpmn-expression-tools";
  }
  if (normalized.includes("/@lezer/")) {
    return "bpmn-expression-parser";
  }
  if (
    normalized.includes("/@bpmn-io/feel-editor/")
    || normalized.includes("/@bpmn-io/feelers-editor/")
  ) {
    return "bpmn-expression-editors";
  }
  if (normalized.includes("/focus-trap/")) {
    return "bpmn-properties-focus";
  }
  if (normalized.includes("/bpmn-js/")) return "bpmn-modeler";
  if (normalized.includes("/diagram-js/")) return "bpmn-diagram-engine";
  if (
    normalized.includes("/bpmn-moddle/")
    || normalized.includes("/moddle/")
    || normalized.includes("/moddle-xml/")
    || normalized.includes("/saxen/")
  ) {
    return "bpmn-xml";
  }
  return undefined;
}

function govoplanInstalledModulesPlugin(): Plugin {
  const specifierByModuleVirtualId = new Map(
    availableWebModuleSpecifiers().map((specifier) => [
      `${installedModuleVirtualPrefix}${encodeURIComponent(specifier)}`,
      specifier
    ])
  );

  return {
    name: "govoplan-installed-modules",
    resolveId(id: string) {
      if (id === installedModulesVirtualId) return resolvedInstalledModulesVirtualId;
      if (specifierByModuleVirtualId.has(id)) return `\0${id}`;
      return null;
    },
    load(id: string) {
      if (id === resolvedInstalledModulesVirtualId) {
        const loaders = [...specifierByModuleVirtualId].map(([virtualId, specifier]) => (
          `{ packageName: ${JSON.stringify(specifier)}, load: () => import(${JSON.stringify(virtualId)}) }`
        ));
        return [
          `export const installedWebModuleLoaders = [${loaders.join(", ")}];`,
          "export default installedWebModuleLoaders;"
        ].join("\n");
      }
      if (!id.startsWith(resolvedInstalledModuleVirtualPrefix)) return null;
      const publicVirtualId = id.slice(1);
      const specifier = specifierByModuleVirtualId.get(publicVirtualId);
      if (!specifier) return null;
      const moduleEntry = join(packageDirectory(specifier), "src", "module.ts");
      if (!existsSync(moduleEntry)) {
        throw new Error(`${specifier} does not expose src/module.ts`);
      }
      // Import the contribution descriptor directly. Package root barrels may
      // intentionally re-export pages and would otherwise defeat route splitting.
      return `export { default } from ${JSON.stringify(moduleEntry)};`;
    }
  };
}

export default defineConfig({
  plugins: [govoplanInstalledModulesPlugin(), react()],
  optimizeDeps: {
    exclude: availableWebModuleSpecifiers(),
    // Dataflow is discovered through the virtual module registry, so Vite's
    // initial scan cannot see this CommonJS-backed transitive dependency.
    include: ["@xyflow/react"]
  },
  build: {
    manifest: true,
    rollupOptions: {
      output: {
        manualChunks: deferredVendorChunk,
        // Keep dependencies of deferred BPMN packages in their lazy graph. The
        // legacy Rollup behavior merged those dependencies into manual chunks
        // and hoisted the properties-panel runtime into the application entry.
        onlyExplicitManualChunks: true
      }
    },
    chunkSizeWarningLimit: 500
  },
  resolve: {
    preserveSymlinks: false,
    dedupe: [
      "@tiptap/core",
      "@tiptap/pm",
      "@tiptap/react",
      "@xyflow/react",
      "@bpmn-io/properties-panel",
      "bpmn-js",
      "bpmn-js-properties-panel",
      "bpmn-moddle",
      "lucide-react",
      "react",
      "react-dom",
      "react-router",
      "read-excel-file"
    ],
    alias: [
      { find: "@govoplan/core-webui/app", replacement: fileURLToPath(new URL("./src/app.ts", import.meta.url)) },
      { find: "@govoplan/core-webui/definition-graph", replacement: fileURLToPath(new URL("./src/definitionGraph.ts", import.meta.url)) },
      { find: "@govoplan/core-webui/wysiwyg", replacement: fileURLToPath(new URL("./src/wysiwyg.ts", import.meta.url)) },
      { find: "@govoplan/core-webui", replacement: fileURLToPath(new URL("./src/index.ts", import.meta.url)) }
    ]
  },
  server: {
    fs: {
      allow: [
        fileURLToPath(new URL('.', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-access/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-approvals/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-admin/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-addresses/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-audit/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-calendar/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-cases/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-dataflow/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-datasources/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-dashboard/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-docs/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-files/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-forms/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-forms-runtime/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-idm/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-mail/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-notifications/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-organizations/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-campaign/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-ops/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-policy/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-portal/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-postbox/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-risk-compliance/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-scheduling/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-search/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-tenancy/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-views/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-voting/webui', import.meta.url)),
        fileURLToPath(new URL('../../govoplan-workflow/webui', import.meta.url))
      ]
    },
    proxy: {
      "/api": {
        target: process.env.VITE_API_PROXY_TARGET || "http://127.0.0.1:8000",
        changeOrigin: true
      },
      "/health": {
        target: process.env.VITE_API_PROXY_TARGET || "http://127.0.0.1:8000",
        changeOrigin: true
      }
    }
  }
});
