// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import * as paths from 'path';
import * as fs from 'fs-extra';
import { AbstractGenerator } from './abstract-generator';

export class BundlerGenerator extends AbstractGenerator {

    async generate(): Promise<void> {
        if (await this.preferESBuild()) {
            await this.write(this.genESBuildBrowserPath, this.compileESBuildBrowserConfig());
            if (!this.pck.isBrowserOnly()) {
                await this.write(this.genESBuildNodePath, this.compileESBuildNodeConfig());
                if (this.pck.isElectron()) {
                    await this.write(this.genESBuildElectronPath, this.compileESBuildElectronConfig());
                }
            }
            if (await this.shouldGenerateUserESBuildConfig()) {
                await this.write(this.esbuildPath, this.compileESBuildUserConfig());
            }
        } else {
            await this.write(this.genConfigPath, this.compileWebpackConfig());
            if (!this.pck.isBrowserOnly()) {
                await this.write(this.genNodeConfigPath, this.compileNodeWebpackConfig());
            }
            if (await this.shouldGenerateUserWebpackConfig()) {
                await this.write(this.configPath, this.compileUserWebpackConfig());
            }
        }
    }

    async preferESBuild(): Promise<boolean> {
        // If an esbuild file already exists, prefer esbuild
        if (await fs.pathExists(this.esbuildPath)) {
            return true;
        }
        // If a webpack file already exists, prefer webpack
        if (await fs.pathExists(this.configPath)) {
            return false;
        }
        // Otherwise, prefer ESBuild (for performance)
        return true;
    }

    protected async shouldGenerateUserWebpackConfig(): Promise<boolean> {
        if (!(await fs.pathExists(this.configPath))) {
            return true;
        }
        const content = await fs.readFile(this.configPath, 'utf8');
        return !content.includes('gen-webpack');
    }

    protected async shouldGenerateUserESBuildConfig(): Promise<boolean> {
        if (!(await fs.pathExists(this.esbuildPath))) {
            return true;
        }
        const content = await fs.readFile(this.esbuildPath, 'utf8');
        return !content.includes('gen-esbuild');
    }

    get configPath(): string {
        return this.pck.path('webpack.config.js');
    }

    get genConfigPath(): string {
        return this.pck.path('gen-webpack.config.js');
    }

    get genNodeConfigPath(): string {
        return this.pck.path('gen-webpack.node.config.js');
    }

    get esbuildPath(): string {
        return this.pck.path('esbuild.mjs');
    }

    get genESBuildBrowserPath(): string {
        return this.pck.path('gen-esbuild.browser.mjs');
    }

    get genESBuildNodePath(): string {
        return this.pck.path('gen-esbuild.node.mjs');
    }

    get genESBuildElectronPath(): string {
        return this.pck.path('gen-esbuild.electron.mjs');
    }

    protected compileWebpackConfig(): string {
        return `/**
 * Don't touch this file. It will be regenerated by theia build.
 * To customize webpack configuration change ${this.configPath}
 */
// @ts-check
const path = require('path');
const webpack = require('webpack');
const yargs = require('yargs');
const resolvePackagePath = require('resolve-package-path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { MonacoWebpackPlugin } = require('@theia/bundle-plugin');

const outputPath = path.resolve(__dirname, 'lib', 'frontend');
const { mode, staticCompression }  = yargs.option('mode', {
    description: "Mode to use",
    choices: ["development", "production"],
    default: "production"
}).option('static-compression', {
    description: 'Controls whether to enable compression of static artifacts.',
    type: 'boolean',
    default: true
}).argv;
const development = mode === 'development';

const plugins = [
    new CopyWebpackPlugin({
        patterns: [
            {
                // copy secondary window html file to lib folder
                from: path.resolve(__dirname, 'src-gen/frontend/secondary-window.html')
            }${this.ifPackage('@theia/plugin-ext', `,
            {
                // copy webview files to lib folder
                from: path.join(resolvePackagePath('@theia/plugin-ext', __dirname), '..', 'src', 'main', 'browser', 'webview', 'pre'),
                to: path.resolve(__dirname, 'lib', 'webview', 'pre')
            }`)}
            ${this.ifPackage('@theia/plugin-ext-vscode', `,
            {
                // copy frontend plugin host files
                from: path.join(resolvePackagePath('@theia/plugin-ext-vscode', __dirname), '..', 'lib', 'node', 'context', 'plugin-vscode-init-fe.js'),
                to: path.resolve(__dirname, 'lib', 'frontend', 'context', 'plugin-vscode-init-fe.js')
            }`)}
            ${this.ifPackage('@theia/terminal', `,
            {
                // copy shell integration scripts
                from: path.join(resolvePackagePath('@theia/terminal', __dirname), '..', 'src', 'node', 'shell-integrations'),
                to: path.resolve(__dirname, 'lib', 'backend', 'shell-integrations')
            }`)}
        ]
    }),
    new webpack.ProvidePlugin({
        // the Buffer class doesn't exist in the browser but some dependencies rely on it
        Buffer: ['buffer', 'Buffer']
    }), 
    new MonacoWebpackPlugin()
];
// it should go after copy-plugin in order to compress monaco as well
if (staticCompression) {
    plugins.push(new CompressionPlugin({}));
}

module.exports = [{
    mode,
    plugins,
    devtool: 'source-map',
    entry: {
        bundle: path.resolve(__dirname, 'src-gen/frontend/index.js'),
        ${this.ifPackage('@theia/plugin-ext', "'plugin-worker': '@theia/plugin-ext/lib/hosted/browser/worker/worker-main.js',")}
    },
    output: {
        filename: '[name].js',
        path: outputPath,
        devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
        globalObject: 'self'
    },
    target: 'web',
    cache: staticCompression,
    module: {
        rules: [
            {
                test: /\\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\\.(ttf|eot|svg)(\\?v=\\d+\\.\\d+\\.\\d+)?$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 10000,
                    }
                },
                generator: {
                    dataUrl: {
                        mimetype: 'image/svg+xml'
                    }
                }
            },
            {
                test: /\\.(jpg|png|gif)$/,
                type: 'asset/resource',
                generator: {
                    filename: '[hash].[ext]'
                }
            },
            {
                // see https://github.com/eclipse-theia/theia/issues/556
                test: /source-map-support/,
                loader: 'ignore-loader'
            },
            {
                test: /\\.d\\.ts$/,
                loader: 'ignore-loader'
            },
            {
                test: /\\.js$/,
                enforce: 'pre',
                loader: 'source-map-loader',
                exclude: /jsonc-parser|fast-plist|onigasm/
            },
            {
                test: /\\.woff(2)?(\\?v=[0-9]\\.[0-9]\\.[0-9])?$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 10000,
                    }
                },
                generator: {
                    dataUrl: {
                        mimetype: 'image/svg+xml'
                    }
                }
            },
            {
                test: /node_modules[\\\\|\/](vscode-languageserver-types|vscode-uri|jsonc-parser|vscode-languageserver-protocol)/,
                loader: 'umd-compat-loader'
            },
            {
                test: /\\.wasm$/,
                type: 'asset/resource'
            },
            {
                test: /\\.plist$/,
                type: 'asset/resource'
            }
        ]
    },
    resolve: {
        fallback: {
            'child_process': false,
            'crypto': false,
            'net': false,
            'path': require.resolve('path-browserify'),
            'process': false,
            'os': false,
            'timers': false
        },
        ${this.ifMonaco(() => `alias: {
            // Replace Monaco's nls module with Theia's localization-aware version.
            // ESM exports are immutable so we cannot override localize/localize2 at runtime.
            // Using the resolved absolute path ensures that both external imports
            // (e.g. '@theia/monaco-editor-core/esm/vs/nls') and internal relative
            // imports within Monaco (e.g. '../nls.js') are redirected.
            [path.join(resolvePackagePath('@theia/monaco-editor-core', __dirname), '..', 'esm', 'vs', 'nls.js')]:
                path.join(resolvePackagePath('@theia/monaco', __dirname), '..', 'lib', 'browser', 'monaco-nls.js')
        },`)}
        extensions: ['.js']
    },
    stats: {
        warnings: true,
        children: true
    },
    ignoreWarnings: [
        // Some packages do not have source maps, that's ok
        /Failed to parse source map/,
        {
            // Monaco uses 'require' in a non-standard way
            module: /@theia\\/monaco-editor-core/,
            message: /require function is used in a way in which dependencies cannot be statically extracted/
        }
    ]
},
${this.ifMonaco(() => `{
    // The Monaco editor worker must be built separately without the NLS alias.
    // The NLS alias redirects to monaco-nls.ts which imports from @theia/core,
    // and those modules are not available in the web worker context.
    mode,
    devtool: 'source-map',
    entry: {
        'editor.worker': '@theia/monaco-editor-core/esm/vs/editor/common/services/editorWebWorkerMain.js'
    },
    output: {
        filename: '[name].js',
        path: outputPath,
        devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
        globalObject: 'self'
    },
    target: 'webworker',
    cache: staticCompression,
    resolve: {
        extensions: ['.js']
    },
    ignoreWarnings: [
        {
            module: /@theia\\/monaco-editor-core/,
            message: /require function is used in a way in which dependencies cannot be statically extracted/
        }
    ]
},`)}
{
    mode,
    plugins: [
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            filename: "[name].css",
            chunkFilename: "[id].css",
        }),
        new MonacoWebpackPlugin(),
    ],
    devtool: 'source-map',
    entry: {
        "secondary-window": path.resolve(__dirname, 'src-gen/frontend/secondary-index.js'),
    },
    output: {
        filename: '[name].js',
        path: outputPath,
        devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
        globalObject: 'self'
    },
    target: 'web',
    cache: staticCompression,
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader, "css-loader"]
            },
            {
                test: /\.wasm$/,
                type: 'asset/resource'
            }
        ]
    },
    resolve: {
        fallback: {
            'child_process': false,
            'crypto': false,
            'net': false,
            'path': require.resolve('path-browserify'),
            'process': false,
            'os': false,
            'timers': false
        },
        ${this.ifMonaco(() => `alias: {
            // Replace Monaco's nls module with Theia's localization-aware version.
            // ESM exports are immutable so we cannot override localize/localize2 at runtime.
            // Using the resolved absolute path ensures that both external imports
            // (e.g. '@theia/monaco-editor-core/esm/vs/nls') and internal relative
            // imports within Monaco (e.g. '../nls.js') are redirected.
            [path.join(resolvePackagePath('@theia/monaco-editor-core', __dirname), '..', 'esm', 'vs', 'nls.js')]:
                path.join(resolvePackagePath('@theia/monaco', __dirname), '..', 'lib', 'browser', 'monaco-nls.js')
        },`)}
        extensions: ['.js']
    },
    stats: {
        warnings: true,
        children: true
    },
    ignoreWarnings: [
        {
            // Monaco uses 'require' in a non-standard way
            module: /@theia\\/monaco-editor-core/,
            message: /require function is used in a way in which dependencies cannot be statically extracted/
        }
    ]
}${this.ifElectron(`, {
    mode,
    devtool: 'source-map',
    entry: {
        "preload": path.resolve(__dirname, 'src-gen/frontend/preload.js'),
    },
    output: {
        filename: '[name].js',
        path: outputPath,
        devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
        globalObject: 'self'
    },
    target: 'electron-preload',
    cache: staticCompression,
    stats: {
        warnings: true,
        children: true
    }
}`)}];`;
    }

    protected compileUserWebpackConfig(): string {
        return `/**
 * This file can be edited to customize webpack configuration.
 * To reset delete this file and rerun theia build again.
 */
// @ts-check
const configs = require('./${paths.basename(this.genConfigPath)}');
${this.ifBrowserOnly('', `const nodeConfig = require('./${paths.basename(this.genNodeConfigPath)}');`)}

/**
 * Expose bundled modules on window.theia.moduleName namespace, e.g.
 * window['theia']['@theia/core/lib/common/uri'].
 * Such syntax can be used by external code, for instance, for testing.
configs[0].module.rules.push({
    test: /\\.js$/,
    loader: require.resolve('@theia/application-manager/lib/expose-loader')
}); */

${this.ifBrowserOnly('module.exports = configs;', `module.exports = [
    ...configs,
    nodeConfig.config
];`)}
`;
    }

    protected compileNodeWebpackConfig(): string {
        return `/**
 * Don't touch this file. It will be regenerated by theia build.
 * To customize webpack configuration change ${this.configPath}
 */
// @ts-check
const path = require('path');
const yargs = require('yargs');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const { NativeWebpackPlugin, MonacoWebpackPlugin } = require('@theia/bundle-plugin');

const { mode } = yargs.option('mode', {
    description: "Mode to use",
    choices: ["development", "production"],
    default: "production"
}).argv;

const production = mode === 'production';

/** @type {import('webpack').EntryObject} */
const commonJsLibraries = {};
for (const [entryPointName, entryPointPath] of Object.entries({
    ${this.ifPackage('@theia/plugin-ext', "'backend-init-theia': '@theia/plugin-ext/lib/hosted/node/scanners/backend-init-theia',")}
    ${this.ifPackage('@theia/filesystem', "'parcel-watcher': '@theia/filesystem/lib/node/parcel-watcher',")}
    ${this.ifPackage('@theia/plugin-ext-vscode', "'plugin-vscode-init': '@theia/plugin-ext-vscode/lib/node/plugin-vscode-init',")}
    ${this.ifPackage('@theia/api-provider-sample', "'gotd-api-init': '@theia/api-provider-sample/lib/plugin/gotd-api-init',")}
})) {
    commonJsLibraries[entryPointName] = {
        import: require.resolve(entryPointPath),
        library: {
            type: 'commonjs2',
        },
    };
}

const ignoredResources = new Set();

if (process.platform !== 'win32') {
    ignoredResources.add('@vscode/windows-ca-certs');
    ignoredResources.add('@vscode/windows-ca-certs/build/Release/crypt32.node');
}

const nativePlugin = new NativeWebpackPlugin({
    out: 'native',
    trash: ${this.ifPackage('@theia/filesystem', 'true', 'false')},
    ripgrep: ${this.ifPackage(['@theia/search-in-workspace', '@theia/file-search'], 'true', 'false')},
    pty: ${this.ifPackage('@theia/process', 'true', 'false')},
    nativeBindings: {
        drivelist: 'drivelist/build/Release/drivelist.node'
    }
});

${this.ifPackage('@theia/process', () => `// Ensure that node-pty is correctly hoisted
try {
    require.resolve('node-pty');
} catch {
    console.error('"node-pty" dependency is not installed correctly. Ensure that it is available in the root node_modules directory.');
    console.error('Exiting webpack build process.');
    process.exit(1);
}`)}

/** @type {import('webpack').Configuration} */
const config = {
    mode,
    devtool: mode === 'development' ? 'source-map' : false,
    target: 'node',
    node: {
        global: false,
        __filename: false,
        __dirname: false
    },
    resolve: {
        extensions: ['.js', '.json', '.wasm', '.node'],
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'lib', 'backend'),
        devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]?[loaders]',
    },${this.ifElectron(`
    externals: {
        electron: 'require("electron")'
    },`)}
    entry: {
        // Main entry point of the Theia application backend:
        'main': require.resolve('./src-gen/backend/main'),
        // Theia's IPC mechanism:
        'ipc-bootstrap': require.resolve('@theia/core/lib/node/messaging/ipc-bootstrap'),
        ${this.ifPackage('@theia/plugin-ext', () => `// VS Code extension support:
        'plugin-host': require.resolve('@theia/plugin-ext/lib/hosted/node/plugin-host'),`)}
        ${this.ifPackage('@theia/plugin-ext-headless', () => `// Theia Headless Plugin support:
        'plugin-host-headless': require.resolve('@theia/plugin-ext-headless/lib/hosted/node/plugin-host-headless'),`)}
        ${this.ifPackage('@theia/process', () => `// Make sure the node-pty thread workers can be executed:
        'worker/conoutSocketWorker': require.resolve('node-pty/lib/worker/conoutSocketWorker'),
        'conpty_console_list_agent': require.resolve('node-pty/lib/conpty_console_list_agent'),`)}        
        ${this.ifElectron("'electron-main': require.resolve('./src-gen/backend/electron-main'),")}
        ${this.ifPackage('@theia/dev-container', () => `// VS Code Dev-Container communication:
        'dev-container-server': require.resolve('@theia/dev-container/lib/dev-container-server/dev-container-server'),`)}
        ...commonJsLibraries
    },
    module: {
        rules: [
            // Make sure we can still find and load our native addons.
            {
                test: /\\.node$/,
                loader: 'node-loader',
                options: {
                    name: 'native/[name].[ext]'
                }
            },
            {
                test: /\\.d\\.ts$/,
                loader: 'ignore-loader'
            },
            {
                test: /\\.js$/,
                enforce: 'pre',
                loader: 'source-map-loader'
            },
            // node-pty uses a dynamic require which needs to be rewritten to work with webpack.
            {
                test: /node_modules[/\\\\]node-pty[/\\\\]lib[/\\\\]utils\.js$/,
                loader: 'string-replace-loader',
                options: {
                    search: /require\\(/,
                    replace: '__non_webpack_require__(',
                    flags: 'g'
                }
            },
            // jsonc-parser exposes its UMD implementation by default, which
            // confuses Webpack leading to missing js in the bundles.
            {
                test: /node_modules[\\/](jsonc-parser)/,
                loader: 'umd-compat-loader'
            }
        ]
    },
    plugins: [
        // Some native dependencies need special handling
        nativePlugin,
        // Optional node dependencies can be safely ignored
        new webpack.IgnorePlugin({
            checkResource: resource => ignoredResources.has(resource)
        }),
        new MonacoWebpackPlugin()
    ],
    optimization: {
        // Split and reuse code across the various entry points
        splitChunks: {
            chunks: 'all'
        },
        // Only minimize if we run webpack in production mode
        minimize: production,
        minimizer: [
            new TerserPlugin({
                exclude: /^(lib|builtins)\\//${this.ifPackage(['@theia/scanoss', '@theia/ai-anthropic', '@theia/ai-openai'], () => `,
                terserOptions: {
                    keep_classnames: /AbortSignal/
                }`)}
            })
        ]
    },
    ignoreWarnings: [
        // Some packages do not have source maps, that's ok
        /Failed to parse source map/,
        // require with expressions are not supported
        /the request of a dependency is an expression/,
        // Some packages use dynamic requires, we can safely ignore them (they are handled by the native webpack plugin)
        /require function is used in a way in which dependencies cannot be statically extracted/, {
            module: /yargs/
        }, {
            module: /node-pty/
        }, {
            module: /require-main-filename/
        }, {
            module: /ws/
        }, {
            module: /express/
        }, {
            module: /cross-spawn/
        }, {
            module: /@parcel\\/watcher/
        }
    ]
};

module.exports = {
    config,
    nativePlugin,
    ignoredResources
};
`;
    }

    protected compileESBuildBrowserConfig(): string {
        return `/**
 * Don't touch this file. It will be regenerated by theia build.
 * To customize the build process, change ./esbuild.mjs
 */
import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill';
import { copy } from 'esbuild-plugin-copy';
import { monacoNlsPlugin, problemMatcherPlugin, sourceMapPathsPlugin } from '@theia/bundle-plugin';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import resolvePackagePath from 'resolve-package-path';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

export const __dirname = path.dirname(fileURLToPath(import.meta.url));

export function join(...parts) {
    return path.join(...parts).replace(/\\\\/g, '/');
}

const { mode, watch } = yargs(hideBin(process.argv)).option('mode', {
    description: "Mode to use",
    choices: ["development", "production"],
    default: "production"
}).option('watch', {
    description: 'Controls whether to enable watch mode',
    type: 'boolean',
    default: false
}).argv;

const production = mode === 'production';
const sourcemap = production ? false : 'linked';
const minify = production;

export { mode, watch, sourcemap, minify };

/**
 * @type {Record<string, import('esbuild').Loader>}
 */
export const loader = {
    '.css': 'css',
    '.ttf': 'dataurl',
    '.eot': 'dataurl',
    '.svg': 'dataurl',
    '.woff': 'dataurl',
    '.woff2': 'dataurl',
    '.jpg': 'dataurl',
    '.png': 'dataurl',
    '.gif': 'dataurl',
    '.wasm': 'dataurl',
    '.plist': 'dataurl',
    '.node': 'file'
};

/**
 * @type {import('esbuild').BuildOptions}
 */
export const browserOptions = {
    entryPoints: {
        'bundle': './src-gen/frontend/index.js',
        'secondary-window': './src-gen/frontend/secondary-index.js',
        ${this.ifMonaco(() => "'editor.worker': '@theia/monaco-editor-core/esm/vs/editor/common/services/editorWebWorkerMain.js',")}
        ${this.ifPackage('@theia/plugin-ext', "'plugin-worker': '@theia/plugin-ext/lib/hosted/browser/worker/worker-main.js',")}
    },
    assetNames: '[name]',
    bundle: true,
    outdir: 'lib/frontend',
    platform: 'browser',
    // Support UMD libraries
    // but ensure that we also prioritize browser exports where possible
    mainFields: ['browser', 'module', 'main'],
    loader,
    minify,
    sourcemap,
    plugins: [
        problemMatcherPlugin(watch, 'browser'),
        ${this.ifMonaco(() => 'monacoNlsPlugin(),')}
        nodeModulesPolyfillPlugin({
            globals: {
                Buffer: true,
                process: false
            }
        }),
        copy({
            assets: [
                {
                    // copy secondary window html file to lib folder
                    from: join(__dirname, 'src-gen/frontend/secondary-window.html'),
                    to: join(__dirname, 'lib', 'frontend')
                }${this.ifPackage('@theia/plugin-ext', `,
                {
                    // copy webview files to lib folder
                    from: join(resolvePackagePath('@theia/plugin-ext', __dirname), '..', 'src', 'main', 'browser', 'webview', 'pre', '*'),
                    to: join(__dirname, 'lib', 'webview', 'pre')
                }`)}${this.ifPackage('@theia/plugin-ext-vscode', `,
                {
                    // copy frontend plugin host files
                    from: join(resolvePackagePath('@theia/plugin-ext-vscode', __dirname), '..', 'lib', 'node', 'context', 'plugin-vscode-init-fe.js'),
                    to: join(__dirname, 'lib', 'frontend', 'context')
                }`)}
            ]
        }),
        sourceMapPathsPlugin()
    ]
};
`;
    }

    protected compileESBuildNodeConfig(): string {
        return `/**
 * Don't touch this file. It will be regenerated by theia build.
 * To customize the build process, change ./esbuild.mjs
 */
import { nativeDependenciesPlugin, problemMatcherPlugin, sourceMapPathsPlugin } from '@theia/bundle-plugin';
import { watch, loader, minify, sourcemap, join, __dirname } from './gen-esbuild.browser.mjs';
import { copy } from 'esbuild-plugin-copy';
import resolvePackagePath from 'resolve-package-path';

/**
 * @type {Record<string, string>}
 */
export const nativeBindings = {
    drivelist: 'drivelist/build/Release/drivelist.node'
};

/**
 * @type {import('esbuild').BuildOptions}
 */
export const nodeOptions = {
    entryPoints: {
        'main': './src-gen/backend/main',
        'ipc-bootstrap': '@theia/core/lib/node/messaging/ipc-bootstrap',
        ${this.ifElectron("'electron-main': './src-gen/backend/electron-main',")}
        ${this.ifPackage('@theia/plugin-ext', () => `// VS Code extension support:
        'plugin-host': '@theia/plugin-ext/lib/hosted/node/plugin-host',`)}
        ${this.ifPackage('@theia/plugin-ext-headless', () => `// Theia Headless Plugin support:
        'plugin-host-headless': '@theia/plugin-ext-headless/lib/hosted/node/plugin-host-headless',`)}
        ${this.ifPackage('@theia/process', () => `// Make sure the node-pty thread workers can be executed:
        'worker/conoutSocketWorker': 'node-pty/lib/worker/conoutSocketWorker',
        'conpty_console_list_agent': 'node-pty/lib/conpty_console_list_agent',`)}
        ${this.ifPackage('@theia/dev-container', () => `// VS Code Dev-Container communication:
        'dev-container-server': '@theia/dev-container/lib/dev-container-server/dev-container-server',`)}
        ${this.ifPackage('@theia/plugin-ext', "'backend-init-theia': '@theia/plugin-ext/lib/hosted/node/scanners/backend-init-theia',")}
        ${this.ifPackage('@theia/filesystem', "'parcel-watcher': '@theia/filesystem/lib/node/parcel-watcher',")}
        ${this.ifPackage('@theia/plugin-ext-vscode', "'plugin-vscode-init': '@theia/plugin-ext-vscode/lib/node/plugin-vscode-init',")}
        ${this.ifPackage('@theia/api-provider-sample', "'gotd-api-init': '@theia/api-provider-sample/lib/plugin/gotd-api-init',")}
        ${this.ifPackage('@theia/git', "'git-locator-host': '@theia/git/lib/node/git-locator/git-locator-host',")}
    },
    assetNames: 'native/[name]',
    bundle: true,
    outdir: 'lib/backend',
    platform: 'node',
    mainFields: ['node', 'module', 'main'],
    external: ['electron'],
    loader,
    minify,
    sourcemap,
    plugins: [
        problemMatcherPlugin(watch, 'node'),
        nativeDependenciesPlugin({
            pty: ${this.ifPackage('@theia/process', 'true', 'false')},
            ripgrep: ${this.ifPackage(['@theia/search-in-workspace', '@theia/file-search'], 'true', 'false')},
            trash: ${this.ifPackage('@theia/filesystem', 'true', 'false')},
            nativeBindings
        }),
        copy({
            assets: [${this.ifPackage('@theia/terminal', `
                {
                    // copy shell integration scripts
                    from: join(resolvePackagePath('@theia/terminal', __dirname), '..', 'src', 'node', 'shell-integrations', '**', '*'),
                    to: join(__dirname, 'lib', 'backend', 'shell-integrations')
                },`)}
            ]
        }),
        sourceMapPathsPlugin()
    ]
};
`;
    }

    protected compileESBuildUserConfig(): string {
        return `/**
 * This file can be edited to adjust the ESBuild build process.
 * To reset, delete this file and rerun theia build again.
 */
import { browserOptions, watch } from './gen-esbuild.browser.mjs';
${this.ifBrowserOnly(`import esbuild from 'esbuild';

const browserContext = await esbuild.context(browserOptions);

if (watch) {
    await browserContext.watch();
} else {
    try {
        await browserContext.rebuild();
        await browserContext.dispose();
    } catch {
        process.exit(1);
    }
}`, `import { nodeOptions } from './gen-esbuild.node.mjs';
${this.ifElectron("import { electronOptions } from './gen-esbuild.electron.mjs';")}
import esbuild from 'esbuild';

const browserContext = await esbuild.context(browserOptions);
const nodeContext = await esbuild.context(nodeOptions);
${this.ifElectron('const electronContext = await esbuild.context(electronOptions);')}

if (watch) {
    await Promise.all([
        browserContext.watch(),
        nodeContext.watch(),${this.ifElectron(`
        electronContext.watch(),`)}
    ]);
} else {
    try {
        await browserContext.rebuild();
        await browserContext.dispose();
        await nodeContext.rebuild();
        await nodeContext.dispose();${this.ifElectron(`
        await electronContext.rebuild();
        await electronContext.dispose();`)}
    } catch {
        process.exit(1);
    }
}`)}
`;
    }

    protected compileESBuildElectronConfig(): string {
        return `/**
 * Don't touch this file. It will be regenerated by theia build.
 * To customize the build process, change ./esbuild.mjs
 */
import { problemMatcherPlugin, sourceMapPathsPlugin } from '@theia/bundle-plugin';
import { watch, loader, minify, sourcemap } from './gen-esbuild.browser.mjs';

/**
 * @type {import('esbuild').BuildOptions}
 */
export const electronOptions = {
    entryPoints: {
        'preload': './src-gen/frontend/preload'
    },
    bundle: true,
    outdir: 'lib/frontend',
    platform: 'node',
    mainFields: ['node', 'module', 'main'],
    external: ['electron'],
    loader,
    minify,
    sourcemap,
    plugins: [
        problemMatcherPlugin(watch, 'electron'),
        sourceMapPathsPlugin()
    ]
};
`;
    }

}
