#!/usr/bin/env node
// Copies mobile/nodejs-assets/nodejs-project into the Android project's assets
// so the nodejs-mobile-cordova plugin can find/copy it, and generates
// dir.list/file.list for the plugin's runtime copy step.
//
// nodejs-mobile-cordova's build.gradle is `apply from`'d into BOTH the `app`
// and `capacitor-cordova-android-plugins` modules, and each checks for
// `<module>/src/main/assets/www/` at *configuration* time — the build fails
// with "couldn't find the www folder" if either is missing. So we place a
// copy in both.
//
// IMPORTANT: `npx cap sync` regenerates capacitor-cordova-android-plugins'
// assets folder (wiping anything placed here), so this script must run AFTER
// `npx cap sync` and the build must run with `--no-sync`
// (npm run android does this in the right order).
//
// Run after `./scripts/sync-nodejs-project.sh` and (optionally) `npm install`
// inside nodejs-assets/nodejs-project.

'use strict';

const fs = require('fs');
const path = require('path');
const zlib = require('zlib');

const MOBILE_DIR = path.join(__dirname, '..');
const SRC = path.join(MOBILE_DIR, 'nodejs-assets', 'nodejs-project');

if (!fs.existsSync(SRC)) {
  console.error(`Source not found: ${SRC}\nRun ./scripts/sync-nodejs-project.sh first.`);
  process.exit(1);
}

function rmrf(p) {
  fs.rmSync(p, { recursive: true, force: true });
}

function copyDir(src, dest) {
  fs.mkdirSync(dest, { recursive: true });
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    if (entry.name === '.bin') continue; // symlinks under node_modules/.bin break the asset copy
    // Android's asset merger treats foo.js + foo.js.gz as duplicate resources.
    if (entry.name.endsWith('.gz')) continue;
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    if (entry.isSymbolicLink()) {
      const real = fs.realpathSync(srcPath);
      if (fs.statSync(real).isDirectory()) copyDir(real, destPath);
      else fs.copyFileSync(real, destPath);
    } else if (entry.isDirectory()) {
      copyDir(srcPath, destPath);
    } else {
      fs.copyFileSync(srcPath, destPath);
    }
  }
}

function enumerate(absDir, relDir, dirs, files) {
  for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) {
    if (entry.name.startsWith('.')) continue;
    if (entry.name.endsWith('.gz') || entry.name.endsWith('~')) continue;
    const absPath = path.join(absDir, entry.name);
    const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
    if (entry.isDirectory()) {
      dirs.push(relPath);
      enumerate(absPath, relPath, dirs, files);
    } else {
      files.push(relPath);
    }
  }
}

// Patch a known issue in nodejs-mobile-cordova 0.4.3: NodeJS.java references
// `BuildConfig.DEBUG`, which doesn't resolve in Capacitor's cordova-plugins
// library module (no matching BuildConfig in that package). This file is
// re-copied verbatim from node_modules by `cap sync`, so patch it each time.
const nodeJsJava = path.join(
  MOBILE_DIR, 'android', 'capacitor-cordova-android-plugins', 'src', 'main', 'java',
  'com', 'janeasystems', 'cdvnodejsmobile', 'NodeJS.java'
);
if (fs.existsSync(nodeJsJava)) {
  const src = fs.readFileSync(nodeJsJava, 'utf8');
  const patched = src.replace('if (BuildConfig.DEBUG) {', 'if (false) {');
  if (patched !== src) {
    fs.writeFileSync(nodeJsJava, patched);
    console.log(`Patched ${path.relative(MOBILE_DIR, nodeJsJava)} (BuildConfig.DEBUG -> false)`);
  }
}

// Local mode runs the embedded Node server on http://127.0.0.1:3000, which
// Android's Network Security Config blocks by default (cleartext traffic).
// `cap sync`/`cap add android` don't know about this, so (re)write the config
// and wire it into AndroidManifest.xml's <application> tag each time.
const networkSecurityConfigXml = `<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!-- Local mode runs an embedded Node.js server on 127.0.0.1; the WebView
         (https://localhost) needs to reach it over plain HTTP. -->
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">127.0.0.1</domain>
        <domain includeSubdomains="false">localhost</domain>
    </domain-config>
</network-security-config>
`;
const networkSecurityConfigPath = path.join(
  MOBILE_DIR, 'android', 'app', 'src', 'main', 'res', 'xml', 'network_security_config.xml'
);
fs.mkdirSync(path.dirname(networkSecurityConfigPath), { recursive: true });
fs.writeFileSync(networkSecurityConfigPath, networkSecurityConfigXml);

const manifestPath = path.join(MOBILE_DIR, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
if (fs.existsSync(manifestPath)) {
  const src = fs.readFileSync(manifestPath, 'utf8');
  if (!src.includes('android:networkSecurityConfig')) {
    const patched = src.replace(
      /(<application\b)/,
      '$1\n        android:networkSecurityConfig="@xml/network_security_config"'
    );
    fs.writeFileSync(manifestPath, patched);
    console.log(`Patched ${path.relative(MOBILE_DIR, manifestPath)} (added networkSecurityConfig)`);
  }
}

// nodejs-mobile-cordova's build.gradle sets up a CMake build with
// `cmake.path = "libs/cdvnodejsmobile/CMakeLists.txt"` (relative to each
// module's projectDir) for both `app` and `capacitor-cordova-android-plugins`.
// `cap sync` doesn't replicate the plugin.xml `<source-file target-dir="libs/cdvnodejsmobile">`
// copies, so populate that directory ourselves in both modules.
const NODEJS_MOBILE = path.join(MOBILE_DIR, 'node_modules', 'nodejs-mobile-cordova');
const ABIS = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'];

function gunzipFile(src, dest) {
  const data = fs.readFileSync(src);
  fs.writeFileSync(dest, zlib.gunzipSync(data));
}

function populateNativeLibs(moduleDir) {
  const dest = path.join(moduleDir, 'libs', 'cdvnodejsmobile');
  rmrf(dest);
  fs.mkdirSync(dest, { recursive: true });

  for (const f of ['CMakeLists.txt']) {
    fs.copyFileSync(path.join(NODEJS_MOBILE, 'src', 'android', f), path.join(dest, f));
  }
  for (const f of ['native-lib.cpp']) {
    fs.copyFileSync(path.join(NODEJS_MOBILE, 'src', 'android', 'jni', f), path.join(dest, f));
  }
  for (const f of ['cordova-bridge.cpp', 'cordova-bridge.h']) {
    fs.copyFileSync(path.join(NODEJS_MOBILE, 'src', 'common', 'cordova-bridge', f), path.join(dest, f));
  }

  const libnodeSrc = path.join(NODEJS_MOBILE, 'libs', 'android', 'libnode');
  const libnodeDest = path.join(dest, 'libnode');
  copyDir(path.join(libnodeSrc, 'include'), path.join(libnodeDest, 'include'));
  for (const abi of ABIS) {
    const binDest = path.join(libnodeDest, 'bin', abi);
    fs.mkdirSync(binDest, { recursive: true });
    gunzipFile(
      path.join(libnodeSrc, 'bin', abi, 'libnode.so.gz'),
      path.join(binDest, 'libnode.so')
    );
  }

  console.log(`Populated ${path.relative(MOBILE_DIR, dest)} (native CMake build sources + libnode)`);
}

for (const moduleDir of [
  path.join(MOBILE_DIR, 'android', 'app'),
  path.join(MOBILE_DIR, 'android', 'capacitor-cordova-android-plugins'),
]) {
  populateNativeLibs(moduleDir);
}

const targets = [
  path.join(MOBILE_DIR, 'android', 'capacitor-cordova-android-plugins', 'src', 'main', 'assets'),
  path.join(MOBILE_DIR, 'android', 'app', 'src', 'main', 'assets'),
];

// The plugin's copyNodeJSAssets() also expects a `nodejs-mobile-cordova-assets`
// folder at the root of the assets (builtin modules like cordova-bridge);
// without it, assetManager.list() returns empty and the fallback
// assetManager.open() throws FileNotFoundException.
const BUILTIN_ASSETS_SRC = path.join(NODEJS_MOBILE, 'install', 'nodejs-mobile-cordova-assets');

for (const assetsRoot of targets) {
  const builtinDest = path.join(assetsRoot, 'nodejs-mobile-cordova-assets');
  rmrf(builtinDest);
  copyDir(BUILTIN_ASSETS_SRC, builtinDest);

  const dest = path.join(assetsRoot, 'www', 'nodejs-project');
  console.log(`Copying ${SRC} -> ${dest}`);
  rmrf(dest);
  copyDir(SRC, dest);

  // dir.list/file.list, paths relative to assetsRoot (e.g. "www/nodejs-project/main.js"),
  // matching the format produced by the cordova after_prepare hook.
  const dirs = [];
  const files = [];
  enumerate(dest, 'www/nodejs-project', dirs, files);

  fs.writeFileSync(path.join(assetsRoot, 'dir.list'), dirs.join('\n') + '\n');
  fs.writeFileSync(path.join(assetsRoot, 'file.list'), files.join('\n') + '\n');

  console.log(`Wrote ${dirs.length} dirs / ${files.length} files to ${path.relative(MOBILE_DIR, assetsRoot)}/{dir,file}.list`);
}
