/** Commands for maintaining the generated Capacitor package. */

import { spawnSync } from "node:child_process";
import {
  copyFileSync,
  existsSync,
  mkdirSync,
  readFileSync,
  readdirSync,
  writeFileSync,
} from "node:fs";
import { join, resolve } from "node:path";
import { loadAppConfig } from "@wrnexus/styles";
import { compileNativeWireFile } from "@wrnexus/compiler";

function validPackageName(value: string): boolean {
  return /^(?:@[a-z0-9._~-]+\/)?[a-z0-9._~-]+(?:@[a-zA-Z0-9._~^<>=|*+-]+)?$/.test(value);
}

function run(command: string, args: string[], cwd: string): void {
  const result = spawnSync(command, args, {
    cwd,
    stdio: "inherit",
    shell: process.platform === "win32",
  });
  if (result.error) throw result.error;
  if (result.status !== 0) {
    throw new Error(`${command} ${args.join(" ")} failed with exit code ${result.status}`);
  }
}

function escapeHtml(value: string): string {
  return value.replace(/[&<>"']/g, (char) => {
    const entities: Record<string, string> = {
      "&": "&amp;",
      "<": "&lt;",
      ">": "&gt;",
      '"': "&quot;",
      "'": "&#39;",
    };
    return entities[char]!;
  });
}

async function updateMobileErrorPage(root: string, mobileDir: string): Promise<void> {
  const config = await loadAppConfig(root);
  const mobile = config.mobile ?? {};
  const title = mobile.errorTitle ?? "Connection unavailable";
  const message =
    mobile.errorMessage ?? "Check your Wi-Fi and make sure the WrNexus server is running.";
  const serverUrl = mobile.serverUrl ?? "http://localhost:3000";
  const background = mobile.backgroundColor ?? "#0f172a";
  const retryUrl = JSON.stringify(serverUrl).replaceAll("<", "\\u003c");
  const page = `<!doctype html>
<html lang="en">
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(title)}</title>
  <style>html{color-scheme:dark}body{margin:0;min-height:100vh;display:grid;place-items:center;font:16px system-ui;background:${background};color:#e2e8f0}.card{max-width:28rem;padding:2rem;text-align:center}button{padding:.8rem 1.2rem;border:0;border-radius:.75rem;background:#6366f1;color:white;font-weight:700}</style></head>
  <body><main class="card"><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p><button id="retry">Try again</button></main><script>document.getElementById("retry").onclick=()=>location.replace(${retryUrl});</script></body>
</html>
`;
  const path = join(mobileDir, "web", "error.html");
  mkdirSync(resolve(path, ".."), { recursive: true });
  writeFileSync(path, page, "utf8");
  console.log("  ✓ mobile/web/error.html updated from wrnexus.config.ts");
}

function configureAndroidNetworkErrors(mobileDir: string): void {
  const javaRoot = join(mobileDir, "android", "app", "src", "main", "java");
  if (!existsSync(javaRoot)) return;
  const file = (readdirSync(javaRoot, { recursive: true }) as string[])
    .map((entry) => join(javaRoot, entry))
    .find((entry) => entry.endsWith("MainActivity.java"));
  if (!file) return;
  const current = readFileSync(file, "utf8");
  if (current.includes("WRNEXUS_NETWORK_ERROR_ONLY")) return;
  const packageName = /^package\s+([\w.]+);/m.exec(current)?.[1];
  if (
    !packageName ||
    !/public\s+class\s+MainActivity\s+extends\s+BridgeActivity\s*\{\s*\}/s.test(current)
  ) {
    console.warn("  • MainActivity.java is customized — network-error handling was not changed");
    return;
  }
  writeFileSync(
    file,
    `package ${packageName};

import android.os.Bundle;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.BridgeWebViewClient;

// WRNEXUS_NETWORK_ERROR_ONLY
public class MainActivity extends BridgeActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        bridge.setWebViewClient(new BridgeWebViewClient(bridge) {
            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                if (request.isForMainFrame()) view.loadUrl("file:///android_asset/public/error.html");
            }

            @Override
            public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse response) {
                // Preserve WrNexus HTTP error pages (404, 500, etc.).
            }
        });
    }
}
`,
    "utf8",
  );
  console.log("  ✓ Android network-only error page configured");
}

export async function runMobileCommand(
  appRoot: string,
  subcommand?: string,
  args: string[] = [],
): Promise<void> {
  const root = resolve(appRoot);
  const mobileDir = join(root, "mobile");
  if (!existsSync(join(mobileDir, "package.json"))) {
    throw new Error("No mobile project found. Run `wrnexus generate mobile` first.");
  }
  const mobilePackage = JSON.parse(readFileSync(join(mobileDir, "package.json"), "utf8")) as {
    wrnexus?: { mode?: "webview" | "native" };
  };
  const mode = mobilePackage.wrnexus?.mode ?? "webview";

  if (subcommand === "compile") {
    if (mode !== "native") throw new Error("`wrnexus mobile compile` requires native mode.");
    const pagesDir = join(root, "app", "pages");
    if (!existsSync(pagesDir)) throw new Error(`Pages directory not found: ${pagesDir}`);
    let count = 0;
    for (const entry of readdirSync(pagesDir, { recursive: true }) as string[]) {
      if (!entry.endsWith(".wrn")) continue;
      const source = join(pagesDir, entry);
      const relative = entry.replace(/\.wrn$/, ".tsx");
      const output = join(mobileDir, "app", relative);
      mkdirSync(resolve(output, ".."), { recursive: true });
      try {
        writeFileSync(output, compileNativeWireFile(readFileSync(source, "utf8")), "utf8");
      } catch (error) {
        throw new Error(
          `Native compilation failed for app/pages/${entry}: ${(error as Error).message}`,
          { cause: error },
        );
      }
      count++;
    }
    console.log(`  ✓ compiled ${count} .wrn page${count === 1 ? "" : "s"} to mobile/app`);
    return;
  }

  if (subcommand === "add") {
    const packages = args.filter((arg) => !arg.startsWith("--"));
    if (!packages.length || packages.some((name) => !validPackageName(name))) {
      throw new Error("Usage: wrnexus mobile add <native-package...>");
    }
    if (mode === "native") {
      run("bunx", ["expo", "install", ...packages], mobileDir);
      return;
    }
    // The root app needs the JavaScript proxy for its future browser bundle;
    // the mobile package needs the dependency so Capacitor can sync native code.
    run("bun", ["add", ...packages], root);
    run("bun", ["add", ...packages], mobileDir);
    await updateMobileErrorPage(root, mobileDir);
    run("bun", ["run", "sync"], mobileDir);
    configureAndroidNetworkErrors(mobileDir);
    return;
  }

  if (subcommand === "sync") {
    if (mode === "native") {
      run("bunx", ["expo", "prebuild"], mobileDir);
      return;
    }
    await updateMobileErrorPage(root, mobileDir);
    run("bun", ["run", "sync"], mobileDir);
    configureAndroidNetworkErrors(mobileDir);
    return;
  }

  if (subcommand === "assets") {
    const config = await loadAppConfig(root);
    if (!config.mobile?.icon) {
      throw new Error("Set `mobile.icon` in wrnexus.config.ts before generating native assets.");
    }
    const source = resolve(root, config.mobile.icon);
    if (!existsSync(source)) throw new Error(`Mobile icon not found: ${source}`);
    const resources = join(mobileDir, "resources");
    mkdirSync(resources, { recursive: true });
    copyFileSync(source, join(resources, "icon.png"));
    if (mode === "native") {
      console.log("  ✓ mobile/resources/icon.png copied (reference it with `mobile.expo.icon`)");
      return;
    }
    run("bunx", ["capacitor-assets", "generate"], mobileDir);
    return;
  }

  throw new Error("Usage: wrnexus mobile <compile|add <package...>|sync|assets>");
}
