import { defineConfig, type Plugin } from 'vite';
import preact from '@preact/preset-vite';
import dts from 'vite-plugin-dts';
import { visualizer } from 'rollup-plugin-visualizer';
import { createReadStream, existsSync } from 'node:fs';
import { resolve } from 'node:path';

const PRODUCT_CHUNKS = {
  main: 'asukadesigner',
  fabric: 'asuka-fabric',
  babylon: 'asuka-babylon',
} as const;

const PRODUCT_PREMIUM_ADDONS = [
  {
    chunk: 'studio-addon',
    modules: [
      '/src/premium/addons/studio-addon',
      '/src/components/modules/StudioModule',
      '/src/components/modules/Studio3DModule',
    ],
  },
  {
    chunk: 'pricing-addon',
    modules: ['/src/premium/addons/pricing-addon', '/src/components/modules/PricingModule'],
  },
  {
    chunk: 'pricing-rules-addon',
    modules: ['/src/premium/addons/pricing-rules-addon', '/src/components/modules/PricingRulesModule'],
  },
  {
    chunk: 'marker-addon',
    modules: ['/src/premium/addons/marker-addon', '/src/components/modules/FixedSlotsModule'],
  },
  {
    chunk: 'drawing-areas-addon',
    modules: ['/src/premium/addons/drawing-areas-addon', '/src/components/modules/DrawingAreasModule'],
  },
  {
    chunk: 'model-optimizer-addon',
    modules: ['/src/premium/addons/model-optimizer-addon', '/src/components/modules/ModelOptimizerModule'],
  },
  {
    chunk: 'assembly-addon',
    modules: ['/src/premium/addons/assembly-addon', '/src/components/modules/AssemblyModule'],
  },
  {
    chunk: 'product-parameters-addon',
    modules: ['/src/premium/addons/product-parameters-addon', '/src/components/modules/ProductParametersModule'],
  },
] as const;

const PRODUCT_PREMIUM_ADDON_FILES = PRODUCT_PREMIUM_ADDONS.map(({ chunk }) => `${chunk}.js`);

const PRODUCT_FABRIC_FEATURE_MODULES = [
  '/src/components/modules/TextFormattingFabricModule',
  '/src/components/modules/TextCurveModule',
  '/src/components/modules/ShapesModule',
  '/src/components/modules/MagnifierActionModule',
  '/src/components/modules/RulerActionModule',
  '/src/components/modules/StudioFabricModule',
] as const;

const PRODUCT_BABYLON_FEATURE_MODULES = [
  '/src/components/modules/CameraModule',
  '/src/components/modules/TextFormatting3DModule',
  '/src/components/domain/elements/Product3DElementFields',
  '/src/components/domain/parametric/ProductParameterControls',
] as const;

const PRODUCT_DISTRIBUTION_FILES = new Map([
  ['asukadesigner.min.js', 'text/javascript; charset=utf-8'],
  ['asuka-fabric.min.js', 'text/javascript; charset=utf-8'],
  ['asuka-babylon.min.js', 'text/javascript; charset=utf-8'],
  ...PRODUCT_PREMIUM_ADDON_FILES.map((fileName) => [fileName, 'text/javascript; charset=utf-8'] as const),
  ['asukadesigner.min.css', 'text/css; charset=utf-8'],
]);

function serveRawProductDistribution(): Plugin {
  return {
    name: 'serve-raw-product-distribution',
    apply: 'serve' as const,
    configureServer(server) {
      server.middlewares.use('/__asuka-product__/', (request, response, next) => {
        const fileName = String(request.url ?? '').split(/[?#]/, 1)[0].replace(/^\/+/, '');
        const contentType = PRODUCT_DISTRIBUTION_FILES.get(fileName);
        if (!contentType) return next();

        const absolute = resolve(process.cwd(), 'dist', 'product', fileName);
        if (!existsSync(absolute)) {
          response.statusCode = 404;
          response.end(`Missing product build file: ${fileName}`);
          return;
        }

        response.statusCode = 200;
        response.setHeader('Content-Type', contentType);
        response.setHeader('Cache-Control', 'no-store');
        createReadStream(absolute).pipe(response);
      });
    },
  };
}

function useWordPressFreePremiumStubs(enabled: boolean): Plugin {
  const addonIds = new Set(PRODUCT_PREMIUM_ADDONS.map(({ chunk }) => chunk));

  return {
    name: 'use-wordpress-free-premium-stubs',
    enforce: 'pre',
    resolveId(source, importer) {
      if (!enabled || !importer || !normalizeChunkPath(importer).endsWith('/src/premium/registerPremiumRuntime.ts')) {
        return null;
      }

      const match = source.match(/^\.\/addons\/([a-z0-9-]+)$/i);
      if (!match || !addonIds.has(match[1] as (typeof PRODUCT_PREMIUM_ADDONS)[number]['chunk'])) {
        return null;
      }

      return resolve(process.cwd(), 'src', 'wordpress-free', 'addons', `${match[1]}.ts`);
    },
  };
}

function normalizeChunkPath(id: string): string {
  return id.replace(/\\/g, '/');
}

function isFabricDesignerSupportModule(id: string): boolean {
  const normalized = normalizeChunkPath(id);
  return normalized.includes('/src/core/designer.ts')
    || normalized.includes('/src/core/designer.d.ts')
    || (normalized.includes('/src/core/runtime/') && !normalized.includes('/src/core/runtime/runtimeRoles'))
    || normalized.includes('/src/core/rendererRuntime.ts')
    || normalized.includes('/src/core/documentActions.ts')
    || normalized.includes('/src/core/designerNamespaces.ts')
    || normalized.includes('/src/core/studio/studioDesignerAdapter')
    || normalized.includes('/src/core/debugFabric')
    || normalized.includes('/src/core/canvas/asukaControls')
    || normalized.includes('/src/core/fabricCrashKiller')
    || normalized.endsWith('/src/core/fabric.ts');
}

function resolveProductManualChunkName(id: string): string {
  const normalized = normalizeChunkPath(id);
  const wordpressFreeAddon = PRODUCT_PREMIUM_ADDONS.find(({ chunk }) =>
    normalized.includes(`/src/wordpress-free/addons/${chunk}.ts`)
  );
  if (wordpressFreeAddon) return wordpressFreeAddon.chunk;

  const premiumAddon = PRODUCT_PREMIUM_ADDONS.find(({ modules }) => modules.some((needle) => normalized.includes(needle)));
  if (premiumAddon) return premiumAddon.chunk;

  if (
    normalized.includes('/node_modules/@babylonjs/')
    || normalized.includes('/src/addons/asuka-3d-engine.addon')
    || PRODUCT_BABYLON_FEATURE_MODULES.some((needle) => normalized.includes(needle))
  ) {
    return PRODUCT_CHUNKS.babylon;
  }

  if (
    normalized.includes('/node_modules/fabric/')
    || normalized.includes('/src/addons/asuka-fabric-engine.addon')
    || isFabricDesignerSupportModule(normalized)
    || PRODUCT_FABRIC_FEATURE_MODULES.some((needle) => normalized.includes(needle))
  ) {
    return PRODUCT_CHUNKS.fabric;
  }

  // Product workflows and commercial modules deliberately collapse into the
  // public runtime. Only the renderer boundary remains lazy for customers.
  return PRODUCT_CHUNKS.main;
}

const PRODUCT_ENTRY_POINTS = {
  asukadesigner: 'src/product.ts',
} as const;

function resolveProductEntryFileName(chunkInfo: { name?: string }): string {
  const name = String(chunkInfo.name ?? '');
  if (name === PRODUCT_CHUNKS.main) return 'asukadesigner.min.js';
  if (name === PRODUCT_CHUNKS.fabric) return 'asuka-fabric.min.js';
  if (name === PRODUCT_CHUNKS.babylon) return 'asuka-babylon.min.js';
  if (PRODUCT_PREMIUM_ADDONS.some(({ chunk }) => chunk === name)) return `${name}.js`;
  return '[name].js';
}

function resolveProductChunkFileName(chunkInfo: { name?: string }): string {
  return resolveProductEntryFileName(chunkInfo);
}

/**
 * Build matrix:
 * - demo (default): offline demo app in `dist/demo/`
 * - lib (`--mode lib`): lightweight ESM + UMD bundles in `dist/lib/`
 * - product (`--mode product`): main + Fabric + Babylon runtime distribution in `dist/product/`
 * - product-source (`--mode product-source`): readable bundles and source maps for GPL distributions
 * - product-wordpress (`--mode product-wordpress`): GPL core with empty Premium extension points
 * - product-wordpress-source (`--mode product-wordpress-source`): readable GPL WordPress build
 * - product-single (`--mode product-single`): single-file runtime bundle in `dist/product-single/`
 * - product-analyze (`--mode product-analyze`): same product bundle + bundle visualizer
 */
export default defineConfig(({ mode }) => {
  const isLib = mode === 'lib';
  const isWordPressFree = mode === 'product-wordpress' || mode === 'product-wordpress-source';
  const isProductSource = mode === 'product-source' || mode === 'product-wordpress-source';
  const isProduct = mode === 'product' || mode === 'product-analyze' || isProductSource || isWordPressFree;
  const isProductSingle = mode === 'product-single';
  const isBrowserGlobal = mode === 'browser-global';
  const shouldAnalyze = mode === 'product-analyze' || mode === 'lib-analyze' || mode === 'demo-analyze';
  const skipDts = process.env.ASUKA_SKIP_DTS === '1';

  return {
    base: './',
    server: {
      host: true,
      port: 5173,
    },
    optimizeDeps: {
      // Fabric 7 already ships a browser-ready ESM bundle. Keeping it out of
      // Vite 5's esbuild pre-bundling also avoids a Windows optimizer crash.
      // Babylon 8 loads several shaders lazily. Pre-bundling the engine while
      // serving those shader modules directly creates two ShaderStore
      // instances, so the engine falls back to missing .fx requests.
      exclude: ['fabric', '@babylonjs/core', '@babylonjs/loaders'],
    },
    esbuild: {
      drop: (isProduct && !isProductSource) || isProductSingle ? ['console', 'debugger'] : [],
      pure: (isProduct && !isProductSource) || isProductSingle ? ['dbg', 'dbgWarn', 'dbgError'] : []
    },
    plugins: [
      serveRawProductDistribution(),
      useWordPressFreePremiumStubs(isWordPressFree),
      preact(),
      ...(!skipDts && isLib ? [dts({
        include: ['src'],
        exclude: ['src/**/__tests__/**', 'src/**/*.test.ts', 'src/**/*.test.tsx'],
        outDirs: 'dist/lib',
        insertTypesEntry: true
      })] : []),
      ...(shouldAnalyze
        ? [
            visualizer({
              filename: isLib
                ? 'dist/lib/bundle-analysis.html'
                : isProduct
                ? 'dist/product/bundle-analysis.html'
                : isProductSingle
                ? 'dist/product-single/bundle-analysis.html'
                : 'dist/demo/bundle-analysis.html',
              open: true,
              gzipSize: true,
              brotliSize: true,
              template: 'treemap'
            })
          ]
        : [])
    ],
    build: isLib
      ? {
          outDir: 'dist/lib',
          emptyOutDir: true,
          lib: {
            entry: 'src/lib.ts',
            name: 'AsukaDesigner',
            formats: ['es', 'umd'],
            fileName: (format) =>
              format === 'es' ? 'asuka-designer.esm.js' : 'asuka-designer.umd.cjs'
          },
          rollupOptions: {
            external: ['preact', 'fabric', 'jspdf'],
            output: {
              globals: {
                preact: 'preact',
                fabric: 'fabric',
                jspdf: 'jspdf'
              }
            }
          },
          sourcemap: false,
          chunkSizeWarningLimit: 1_600,
          minify: true
        }
      : isProduct
      ? {
          modulePreload: false,
          manifest: false,
          outDir: isWordPressFree
            ? (isProductSource ? 'dist/product-wordpress-source' : 'dist/product-wordpress')
            : (isProductSource ? 'dist/product-source' : 'dist/product'),
          emptyOutDir: true,
          cssCodeSplit: false,
          rollupOptions: {
            input: PRODUCT_ENTRY_POINTS,
            preserveEntrySignatures: 'allow-extension',
            output: {
              format: 'es',
              entryFileNames: (chunkInfo) => resolveProductEntryFileName(chunkInfo),
              manualChunks: (id) => resolveProductManualChunkName(id),
              // Shared code stays in the shell/renderer files; licensed UI roots
              // remain optional add-ons that customers may omit at deployment.
              onlyExplicitManualChunks: true as any,
              chunkFileNames: (chunkInfo) => resolveProductChunkFileName(chunkInfo),
              assetFileNames: (assetInfo) => {
                if (assetInfo.name && assetInfo.name.endsWith('.css')) {
                  return 'asukadesigner.min.css';
                }
                return 'assets/[name]-[hash][extname]';
              }
            }
          },
          sourcemap: isProductSource,
          // Exact per-file budgets are enforced by audit-product-bundle.cjs.
          chunkSizeWarningLimit: 4_200,
          minify: !isProductSource
        }
      : isProductSingle
      ? {
          outDir: 'dist/product-single',
          emptyOutDir: true,
          cssCodeSplit: false,
          rollupOptions: {
            input: 'src/product.ts',
            output: {
              format: 'es',
              inlineDynamicImports: true,
              entryFileNames: 'asukadesigner.min.js',
              assetFileNames: (assetInfo) => {
                if (assetInfo.name && assetInfo.name.endsWith('.css')) {
                  return 'asukadesigner.min.css';
                }
                return 'assets/[name]-[hash][extname]';
              }
            }
          },
          sourcemap: false,
          chunkSizeWarningLimit: 5_500,
          minify: true
        }

      : isBrowserGlobal
      ? {
          modulePreload: false,
          manifest: false,
          outDir: 'dist/browser-global',
          emptyOutDir: true,
          cssCodeSplit: false,
          rollupOptions: {
            input: PRODUCT_ENTRY_POINTS,
            preserveEntrySignatures: 'allow-extension',
            output: {
              format: 'es',
              entryFileNames: (chunkInfo) => resolveProductEntryFileName(chunkInfo),
              manualChunks: (id) => resolveProductManualChunkName(id),
              onlyExplicitManualChunks: true as any,
              chunkFileNames: (chunkInfo) => resolveProductChunkFileName(chunkInfo),
              assetFileNames: (assetInfo) => {
                if (assetInfo.name && assetInfo.name.endsWith('.css')) {
                  return 'asukadesigner.min.css';
                }
                return 'assets/[name]-[hash][extname]';
              }
            }
          },
          sourcemap: false,
          chunkSizeWarningLimit: 4_200,
          minify: true
        }
      : {
          outDir: 'dist/demo',
          emptyOutDir: true,
          cssCodeSplit: false,
          rollupOptions: {
            input: 'index.html',
            output: {
              entryFileNames: 'asukadesigner.demo.min.js',
              inlineDynamicImports: true,
              assetFileNames: (assetInfo) => {
                if (assetInfo.name && assetInfo.name.endsWith('.css')) {
                  return 'asukadesigner.demo.min.css';
                }
                return '[name][extname]';
              }
            }
          },
          sourcemap: false,
          chunkSizeWarningLimit: 5_500,
          minify: true
        }
  };
});
