/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import { writeFileSync } from 'node:fs';
import { wasmLoader } from 'esbuild-plugin-wasm';

let esbuild;
try {
  esbuild = (await import('esbuild')).default;
} catch {
  console.error('esbuild not available - cannot build bundle');
  process.exit(1);
}

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const require = createRequire(import.meta.url);
const pkg = require(path.resolve(__dirname, 'package.json'));

function createWasmPlugins() {
  const wasmBinaryPlugin = {
    name: 'wasm-binary',
    setup(build) {
      build.onResolve({ filter: /\.wasm\?binary$/ }, (args) => {
        const specifier = args.path.replace(/\?binary$/, '');
        const resolveDir = args.resolveDir || '';
        const isBareSpecifier =
          !path.isAbsolute(specifier) &&
          !specifier.startsWith('./') &&
          !specifier.startsWith('../');

        let resolvedPath;
        if (isBareSpecifier) {
          resolvedPath = require.resolve(specifier, {
            paths: resolveDir ? [resolveDir, __dirname] : [__dirname],
          });
        } else {
          resolvedPath = path.isAbsolute(specifier)
            ? specifier
            : path.join(resolveDir, specifier);
        }

        return { path: resolvedPath, namespace: 'wasm-embedded' };
      });
    },
  };

  return [wasmBinaryPlugin, wasmLoader({ mode: 'embedded' })];
}

const external = [
  '@lydell/node-pty',
  'node-pty',
  '@lydell/node-pty-darwin-arm64',
  '@lydell/node-pty-darwin-x64',
  '@lydell/node-pty-linux-x64',
  '@lydell/node-pty-win32-arm64',
  '@lydell/node-pty-win32-x64',
  '@github/keytar',
];

const baseConfig = {
  bundle: true,
  platform: 'node',
  format: 'esm',
  external,
  loader: { '.node': 'file' },
  write: true,
};

const commonAliases = {
  punycode: 'punycode/',
};

const cliConfig = {
  ...baseConfig,
  banner: {
    js: `const require = (await import('node:module')).createRequire(import.meta.url); const __chunk_filename = (await import('node:url')).fileURLToPath(import.meta.url); const __chunk_dirname = (await import('node:path')).dirname(__chunk_filename);`,
  },
  entryPoints: { gemini: 'packages/cli/index.ts' },
  outdir: 'bundle',
  splitting: true,
  define: {
    __filename: '__chunk_filename',
    __dirname: '__chunk_dirname',
    'process.env.CLI_VERSION': JSON.stringify(pkg.version),
    'process.env.GEMINI_SANDBOX_IMAGE_DEFAULT': JSON.stringify(
      pkg.config?.sandboxImageUri,
    ),
    'process.env.NODE_ENV': JSON.stringify(
      process.env.NODE_ENV || 'production',
    ),
    'process.env.DEV': JSON.stringify(process.env.DEV || 'false'),
  },
  plugins: createWasmPlugins(),
  alias: {
    'is-in-ci': path.resolve(__dirname, 'packages/cli/src/patches/is-in-ci.ts'),
    'https-proxy-agent': path.resolve(
      __dirname,
      'packages/cli/src/patches/https-proxy-agent.ts',
    ),
    'http-proxy-agent': path.resolve(
      __dirname,
      'packages/cli/src/patches/http-proxy-agent.ts',
    ),
    '@google/gemini-cli-devtools': path.resolve(
      __dirname,
      'packages/devtools/src/index.ts',
    ),
    ...commonAliases,
  },
  metafile: true,
};

const workerConfig = {
  ...baseConfig,
  banner: {
    js: `const require = (await import('node:module')).createRequire(import.meta.url); const __chunk_filename = (await import('node:url')).fileURLToPath(import.meta.url); const __chunk_dirname = (await import('node:path')).dirname(__chunk_filename);`,
  },
  entryPoints: {
    'worker/worker-entry': path.join(
      path.dirname(require.resolve('ink')),
      'worker/worker-entry.js',
    ),
  },
  outdir: 'bundle',
  define: {
    __filename: '__chunk_filename',
    __dirname: '__chunk_dirname',
    'process.env.NODE_ENV': JSON.stringify(
      process.env.NODE_ENV || 'production',
    ),
  },
  plugins: createWasmPlugins(),
  alias: commonAliases,
};

const a2aServerConfig = {
  ...baseConfig,
  banner: {
    js: `const require = (await import('node:module')).createRequire(import.meta.url); const __chunk_filename = (await import('node:url')).fileURLToPath(import.meta.url); const __chunk_dirname = (await import('node:path')).dirname(__chunk_filename);`,
  },
  entryPoints: ['packages/a2a-server/src/http/server.ts'],
  outfile: 'packages/a2a-server/dist/a2a-server.mjs',
  define: {
    __filename: '__chunk_filename',
    __dirname: '__chunk_dirname',
    'process.env.CLI_VERSION': JSON.stringify(pkg.version),
    'process.env.NODE_ENV': JSON.stringify(
      process.env.NODE_ENV || 'production',
    ),
    'process.env.DEV': JSON.stringify(process.env.DEV || 'false'),
  },
  plugins: createWasmPlugins(),
  alias: commonAliases,
};

Promise.allSettled([
  esbuild.build(cliConfig).then(({ metafile }) => {
    if (process.env.DEV === 'true') {
      writeFileSync('./bundle/esbuild.json', JSON.stringify(metafile, null, 2));
    }
  }),
  esbuild.build(workerConfig),
  esbuild.build(a2aServerConfig),
]).then((results) => {
  const [cliResult, workerResult, a2aResult] = results;
  if (cliResult.status === 'rejected') {
    console.error('gemini.js build failed:', cliResult.reason);
    process.exit(1);
  }
  if (workerResult.status === 'rejected') {
    console.error('worker-entry.js build failed:', workerResult.reason);
    process.exit(1);
  }
  // error in a2a-server bundling will not stop gemini.js bundling process
  if (a2aResult.status === 'rejected') {
    console.warn('a2a-server build failed:', a2aResult.reason);
  }
});
