import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import {sha256} from 'js-sha256';
import {readdir, writeFile, mkdir, readFile} from 'fs/promises';
import webpack, {Configuration} from 'webpack';

const webpackConfig: Configuration = {
  entry: '__ADDED_AT_RUNTIME__',
  output: {
    filename: 'bundle.js',
    path: '__ADDED_AT_RUNTIME__',  
    libraryTarget: 'umd',  // Universal Module Definition
    globalObject: 'apptileSDK',  // Necessary for UMD to work in Node and browser environments
  },
  stats: {
    colors: false
  },
  mode: 'development',
  externals: {
    'react': {
      commonjs: 'react',
      commonjs2: 'react',
      amd: 'react',
      root: 'React',  // Global variable in non-module environments
    },
    'react-native': {
      commonjs: 'react-native',
      commonjs2: 'react-native',
      amd: 'react-native',
      root: 'ReactNative',  // Global variable in non-module environments
    },
    'apptile-core': {
      commonjs: 'apptile-core',
      commonjs2: 'apptile-core',
      amd: 'apptile-core',
      root: 'ApptileCore',  // Global variable in non-module environments
    },
    'react-native-svg': {
      commonjs: 'react-native-svg',
      commonjs2: 'react-native-svg',
      amd: 'react-native-svg',
      root: 'ReactNativeSvg',  // Global variable in non-module environments
    },
    'react-native-linear-gradient': {
      commonjs: 'react-native-linear-gradient',
      commonjs2: 'react-native-linear-gradient',
      amd: 'react-native-linear-gradient',
      root: 'ReactNativeLinearGradient',  // Global variable in non-module environments
    },
    'react-native-fast-image': {
      commonjs: 'react-native-fast-image',
      commonjs2: 'react-native-fast-image',
      amd: 'react-native-fast-image',
      root: 'ReactNativeFastImage'
    },
    '@react-navigation/native': {
      commonjs: '@react-navigation/native',
      commonjs2: '@react-navigation/native',
      amd: '@react-navigation/native',
      root: 'ReactNavNative',  // Global variable in non-module environments
    },
    '@react-navigation/stack': {
      commonjs:  '@react-navigation/stack',
      commonjs2: '@react-navigation/stack',
      amd: '@react-navigation/stack',
      root: 'ReactNavStack',  // Global variable in non-module environments
    },
    'graphql-tag': {
      commonjs: 'graphql-tag',
      commonjs2: 'graphql-tag',
      amd: 'graphql-tag',
      root: 'GraphqlTag'
    },
    '@react-navigation/bottom-tabs': {
      commonjs: '@react-navigation/bottom-tabs',
      commonjs2: '@react-navigation/bottom-tabs',
      amd: '@react-navigation/bottom-tabs',
      root: 'ReactNavBottomTabs',  // Global variable in non-module environments
    },
    'lottie-react-native': {
      commonjs: 'lottie-react-native',
      commonjs2: 'lottie-react-native',
      amd: 'lottie-react-native',
      root: 'Lottie',  // Global variable in non-module environments
    },
    'react-redux': {
      commonjs: 'react-redux',
      commonjs2: 'react-redux',
      amd: 'react-redux',
      root: 'ReactRedux',
    },
    'react-native-safe-area-context': {
      commonjs: 'react-native-safe-area-context',
      commonjs2: 'react-native-safe-area-context',
      amd: 'react-native-safe-area-context',
      root: 'ReactNativeSafeAreaContext',
    },
    'react-native-joi': {
      commonjs: 'react-native-joi',
      commonjs2: 'react-native-joi',
      amd: 'react-native-joi',
      root: 'Joi'
    },
    'apptile-shopify': {
      commonjs: 'apptile-shopify',
      commonjs2: 'apptile-shopify',
      amd: 'apptile-shopify',
      root: 'apptileShopify'
    },
    'react-native-reanimated': {
      commonjs: 'react-native-reanimated',
      commonjs2: 'react-native-reanimated',
      amd: 'react-native-reanimated',
      root: 'ReactNativeReanimated',
    },
    'react-native-gesture-handler': {
      commonjs: 'react-native-gesture-handler',
      commonjs2: 'react-native-gesture-handler',
      amd: 'react-native-gesture-handler',
      root: 'ReactNativeGestureHandler'
    },
    'react-native-webview': {
      commonjs: 'react-native-webview',
      commonjs2: 'react-native-webview',
      amd: 'react-native-webview',
      root: 'ReactNativeWebview'
    },
    '@gorhom/portal': {
      commonjs: '@gorhom/portal',
      commonjs2: '@gorhom/portal',
      amd: '@gorhom/portal',
      root: 'GorhomPortal'
    },
    'moment': {
      commonjs: 'moment',
      commonjs2: 'moment',
      amd: 'moment',
      root: 'moment'
    }, 
    'lodash': {
      commonjs: 'lodash',
      commonjs2: 'lodash',
      amd: 'lodash',
      root: 'lodash'
    }
  },
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/, // Include both .js, .jsx, .ts, .tsx
        exclude: modulePath => (
          /node_modules/.test(modulePath) && !/node_modules\/sdk-plugin-example/.test(modulePath)
        ),
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            // The 'metro-react-native-babel-preset' preset is recommended to match React Native's packager
            presets: ['module:@react-native/babel-preset', "@babel/preset-typescript"],
            // Re-write paths to import only the modules needed by the app
            // plugins: ['react-native-web']
          },
        },
      },
    ],
  },
  resolve: {
    extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.jsx', '.web.js', '.jsx', '.js', '.css'],
    modules: ['__ADDED_AT_RUNTIME__']
  },
};

export function toCamelCase(kebabCaseName: string) {
  const words = kebabCaseName.split("-");
  for (let i = 1; i < words.length; ++i) {
    if (words && words[i]) {
      const word = words[i];
      if (word && word[0]) {
        words[i] = word[0].toUpperCase() + word.slice(1);
      }
    }
  }
  return words.join("");
}

async function metroCodegenPlugins(remoteCode: string, pluginNames: string[]) {
  let contents = `export function initPlugins() {
return [
`; 
  for (let name of pluginNames) {
    // Look for metadata.json and if it exists generate the import entry path accordingly
    let entry = 'source/widget';
    const metadataPath = path.resolve(remoteCode, `plugins/${name}/metadata.json`);
    try {
      const metadata = await readFile(metadataPath, {encoding: 'utf8'});
      const parsedMeta = JSON.parse(metadata);
      if (parsedMeta && typeof parsedMeta.entry === 'string') {
        entry = parsedMeta.entry;
      }
    } catch (err) {
      console.error("Metadata file not found for plugin ", name);
    }
    const camelCasePackageName = toCamelCase(name);
    contents = `import ${camelCasePackageName} from "./plugins/${name}/${entry}";\n` + contents;
    contents += `    ${camelCasePackageName},\n`;
  }
  contents = "// This file is generated. Do not edit.\n" + contents + "  ];\n}";
  const iosRemoteEntryPath = path.resolve(remoteCode, 'index.js');
  await writeFile(iosRemoteEntryPath, contents)
  console.log(`Code generation finished for ios. Output written to ${iosRemoteEntryPath}`);
}

type CompileResult = {message: string; errors?: Array<{error: string; location: string;}>};

async function compileMultiplePlugins(remoteCode: string, pluginNames: string[], compiledDirName: string): Promise<CompileResult> {
  try {
    const generatedPath = path.resolve(remoteCode, 'generated', compiledDirName);
    const generatedEntryPath = path.resolve(generatedPath, 'index.js');
    const distPath = path.resolve(generatedPath, 'dist');
    const pluginsRoot = path.resolve(remoteCode, 'plugins');
    await mkdir(distPath, { recursive: true });

    // Generate for web
    console.log("Generating import file for plugins " + pluginNames.join(','));
    let contents = `\n\nexport default [\n`;

    for (let name of pluginNames) {
      let entry = 'source/widget';
      const metadataPath = path.resolve(remoteCode, `plugins/${name}/metadata.json`);
      try {
        const metadata = await readFile(metadataPath, {encoding: 'utf8'});
        const parsedMeta = JSON.parse(metadata);
        if (parsedMeta && typeof parsedMeta.entry === 'string') {
          entry = parsedMeta.entry;
        }
      } catch (err) {
        console.error("Metadata file not found for plugin ", name);
      }
      contents = `import ${toCamelCase(name)} from "${path.resolve(pluginsRoot, name, entry)}";\n` + contents;    
      contents += `  ${toCamelCase(name)},\n`;
    }
    contents += "]";

    await writeFile(generatedEntryPath, contents)
    console.log(`Code generation finished. Output written to ${generatedEntryPath}`);

    await metroCodegenPlugins(remoteCode, pluginNames);

    const config = {...webpackConfig}
    config.entry = generatedEntryPath;
    config.output!.path = distPath;
    config.resolve!.modules = [
      path.resolve(remoteCode, "../node_modules"),
    ];

    const compiler = webpack(config);
    if (!compiler) {
      return Promise.reject({
        message: 'Failed to create webpack compiler'
      });
    }
    return new Promise((resolve, reject) => {
      compiler.run((err, stats) => {
        if (err || stats?.hasErrors()) {
          console.log(`compilation failed: ${err?.message}`);
          if (stats) {
            const compilationStats = stats.toJson({
              errors: true,
              errorsCount: true
            });
            console.log(`compilation failed with ${compilationStats.errorsCount} errors`);
            const result = {
              message: `compilation failed with ${compilationStats.errorsCount} errors`,
              errors: compilationStats.errors?.map(it => {
                console.log('------------------------------------------------------------------')
                console.log(it.message.replace(/\x1B\[[0-9;]*m/g, ''));
                console.log(`location: \n${it.moduleId} at ${it.loc}`)
                return {
                  error: it.message.replace(/\x1B\[[0-9;]*m/g, ''),
                  location: `${it.moduleId} at ${it.loc}`
                }
              })
            };
            reject(result);
          } else {
            const result = { message: err?.toString() || "error is null" };
            reject(result);
            console.log(JSON.stringify(result, null, 2));
          }
        } else {
          console.log("compilation finished! ", stats?.compilation.assets);
          if (stats) {
            const serializedStats = stats.toJson();
            const result = {
              message: `Compilation finished with ${serializedStats.errorsCount} errors and ${serializedStats.warningsCount} warnings`
            };
            resolve(result);
            console.log(JSON.stringify(result, null, 2));
          } else {
            resolve({
              message: `Compilation completed but no stats were generated`
            });
            console.log(`Compilation completed but no stats were generated`);
          }
        }
      })
    });
  } catch (err: any) {
    console.log(`compilation failed due to ${err.toString()}`);
    return Promise.reject({
      message: `compilation failed due to ${err.toString()}`
    });
  }
}

export async function compileAction() {
	try {
		console.log(chalk.blue('Initializing compilation...'));

		// Check if apptile.config.json exists in current directory
		const configPath = path.resolve(process.cwd(), "apptile.config.json");
		if (!fs.existsSync(configPath)) {
			console.log(chalk.red("Error: apptile.config.json not found in current directory."));
			console.log(chalk.red("Please run this command from the root of your apptile project."));
			process.exit(1);
		}

		// Check if remoteCode folder exists
		const remoteCodePath = path.resolve(process.cwd(), "remoteCode");
		if (!fs.existsSync(remoteCodePath)) {
			console.log(chalk.red("Error: remoteCode folder not found in current directory."));
			console.log(chalk.red("Please ensure you have a remoteCode folder in your project."));
			process.exit(1);
		}

		console.log(chalk.blue('Reading plugins...'));

		// Reproduce the logic from compilePluginBundle
		const appLocation = process.cwd(); // repoPath is simply process.cwd()
		const remoteCode = path.resolve(appLocation, 'remoteCode');
		const pluginsRoot = path.resolve(remoteCode, 'plugins');
		
		let pluginEntries;
		try {
			pluginEntries = await readdir(pluginsRoot, {withFileTypes: true});
		} catch (err: any) {
			if (err.code === 'ENOENT') {
				console.log(chalk.red("Error: plugins folder not found in remoteCode directory."));
				console.log(chalk.red("Please ensure you have a plugins folder in remoteCode."));
				process.exit(1);
			}
			throw err;
		}

		const plugins: string[] = [];
		for (let entry of pluginEntries) {
			if (entry.isDirectory()) {
				plugins.push(entry.name);
			}
		}

		if (plugins.length === 0) {
			console.log(chalk.yellow("No plugins found in remoteCode/plugins directory."));
			process.exit(0);
		}

		console.log(chalk.green(`Found ${plugins.length} plugin(s): ${plugins.join(', ')}`));
		console.log(chalk.blue('Compiling plugins...'));

		const generatedFolderName = sha256(plugins.join('_')).toString();
		
		// Use the imported compileMultiplePlugins function
		const result = await compileMultiplePlugins(remoteCode, plugins, generatedFolderName);
		
		if (result.errors) {
			console.log(chalk.red('Compilation failed:'), result.message);
			console.log(chalk.red('\nErrors:'));
			result.errors.forEach(err => {
				console.log(chalk.red(`${err.error}`));
				console.log(chalk.yellow(`Location: ${err.location}`));
				console.log('');
			});
			process.exit(1);
		} else {
			console.log(chalk.green('✓ Compilation successful!'));
			console.log(chalk.green(result.message));
		}

	} catch (err: any) {
		console.log(chalk.red('Compilation error:'), err.message);
		process.exit(1);
	}
}
