import fs from "node:fs/promises";
import path from "node:path";
import { createRequire } from "node:module";

export type UiPatcherLogger = {
  info: (message: string) => void;
  warn: (message: string) => void;
  error: (message: string) => void;
};

export type UiPatcherOptions = {
  logger: UiPatcherLogger;
  pluginRoot: string;
  stateDir: string;
  autoRestoreOnStop: boolean;
  runtimeConfig: {
    watermarkUrl: string;
    watermarkOpacity: number;
    watermarkWidth: string;
  };
};

const MARKER_FILE = ".ui-whatsynaptor-marker.json";
const BACKUP_DIR_NAME = "ui-whatsynaptor-backup";
const RUNTIME_CONFIG_FILE = "ui-whatsynaptor-runtime-config.js";
const RUNTIME_SCRIPT_FILE = "ui-whatsynaptor-runtime.js";
const RUNTIME_INJECT_START = "<!-- ui-whatsynaptor-runtime:start -->";
const RUNTIME_INJECT_END = "<!-- ui-whatsynaptor-runtime:end -->";

async function isWahaInstalled(pluginRoot: string): Promise<boolean> {
  const wahaPluginManifest = path.resolve(pluginRoot, "../waha/openclaw.plugin.json");
  return await pathExists(wahaPluginManifest);
}

async function detectOpenClawRoot(pluginRoot: string): Promise<string | null> {
  const candidates = new Set<string>();

  candidates.add(path.resolve(pluginRoot, "../.."));
  candidates.add("/opt/openclaw/app");
  candidates.add("/usr/lib/node_modules/openclaw");
  candidates.add("/usr/local/lib/node_modules/openclaw");

  try {
    const require = createRequire(import.meta.url);
    const pkgPath = require.resolve("openclaw/package.json");
    candidates.add(path.dirname(pkgPath));
  } catch {
    // no-op
  }

  for (const root of candidates) {
    if (await pathExists(path.join(root, "dist/control-ui"))) {
      return root;
    }
  }

  return null;
}

async function pathExists(filePath: string): Promise<boolean> {
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}

async function ensureDir(dirPath: string): Promise<void> {
  await fs.mkdir(dirPath, { recursive: true });
}

async function clearDirectory(dirPath: string): Promise<void> {
  await ensureDir(dirPath);
  const entries = await fs.readdir(dirPath);
  await Promise.all(entries.map((entry) => fs.rm(path.join(dirPath, entry), { recursive: true, force: true })));
}

async function copyDirectory(from: string, to: string): Promise<void> {
  await ensureDir(path.dirname(to));
  await fs.cp(from, to, { recursive: true, force: true });
}

async function readMarker(targetDir: string): Promise<{ patchedBy?: string } | null> {
  const markerPath = path.join(targetDir, MARKER_FILE);
  if (!(await pathExists(markerPath))) return null;
  try {
    return JSON.parse(await fs.readFile(markerPath, "utf8")) as { patchedBy?: string };
  } catch {
    return null;
  }
}

async function writeMarker(targetDir: string): Promise<void> {
  const markerPath = path.join(targetDir, MARKER_FILE);
  await fs.writeFile(
    markerPath,
    JSON.stringify({ patchedBy: "ui-whatsynaptor", patchedAt: new Date().toISOString() }, null, 2) + "\n",
    "utf8",
  );
}

function buildRuntimeScript(): string {
  return `"use strict";
(function () {
  const cfg = window.__UIPATCHER_CONFIG__ || {};

  function ensureRuntimeStyle(config) {
    if (document.getElementById("ui-whatsynaptor-runtime-style")) return;
    const style = document.createElement("style");
    style.id = "ui-whatsynaptor-runtime-style";
    const watermarkUrl = String(config.watermarkUrl || "").replace(/"/g, '\\\\"');
    const opacity = Number.isFinite(config.watermarkOpacity) ? String(config.watermarkOpacity) : "0.22";
    const width = String(config.watermarkWidth || "220px").replace(/"/g, '\\\\"');
    style.textContent = [
      ":root {",
      '  --ui-whatsynaptor-watermark-url: url("' + watermarkUrl + '");',
      "  --ui-whatsynaptor-watermark-opacity: " + opacity + ";",
      "  --ui-whatsynaptor-watermark-width: " + width + ";",
      "}",
      ".card.chat { position: relative; }",
      ".card.chat::after {",
      "  content: '';",
      "  position: absolute;",
      "  right: 12px;",
      "  bottom: 10px;",
      "  width: var(--ui-whatsynaptor-watermark-width);",
      "  max-width: 40%;",
      "  aspect-ratio: 1 / 1;",
      "  background-image: var(--ui-whatsynaptor-watermark-url);",
      "  background-repeat: no-repeat;",
      "  background-position: center;",
      "  background-size: contain;",
      "  opacity: var(--ui-whatsynaptor-watermark-opacity);",
      "  pointer-events: none;",
      "  z-index: 0;",
      "}",
      ".card.chat > * { position: relative; z-index: 1; }"
    ].join("\\n");
    document.head.appendChild(style);
  }

  function boot() {
    ensureRuntimeStyle(cfg);
  }

  boot();
  const observer = new MutationObserver(boot);
  observer.observe(document.documentElement, { subtree: true, childList: true });
})();
`;
}

async function applyRuntimeFiles(options: UiPatcherOptions, targetDir: string): Promise<void> {
  const { logger, runtimeConfig } = options;
  const indexPath = path.join(targetDir, "index.html");
  if (!(await pathExists(indexPath))) return;

  const configPayload = {
    watermarkUrl: runtimeConfig.watermarkUrl,
    watermarkOpacity: runtimeConfig.watermarkOpacity,
    watermarkWidth: runtimeConfig.watermarkWidth,
  };
  const configJs = `window.__UIPATCHER_CONFIG__ = ${JSON.stringify(configPayload, null, 2)};\n`;
  const runtimeJs = buildRuntimeScript();

  await fs.writeFile(path.join(targetDir, RUNTIME_CONFIG_FILE), configJs, "utf8");
  await fs.writeFile(path.join(targetDir, RUNTIME_SCRIPT_FILE), runtimeJs, "utf8");

  let indexHtml = await fs.readFile(indexPath, "utf8");
  const blockRegex = new RegExp(`${RUNTIME_INJECT_START}[\\s\\S]*?${RUNTIME_INJECT_END}\\n?`, "g");
  indexHtml = indexHtml.replace(blockRegex, "");
  const injectBlock = [
    RUNTIME_INJECT_START,
    `    <script src="./${RUNTIME_CONFIG_FILE}"></script>`,
    `    <script type="module" src="./${RUNTIME_SCRIPT_FILE}"></script>`,
    RUNTIME_INJECT_END,
  ].join("\n");

  if (indexHtml.includes("</body>")) {
    indexHtml = indexHtml.replace("</body>", `${injectBlock}\n  </body>`);
  } else {
    indexHtml += `\n${injectBlock}\n`;
  }

  await fs.writeFile(indexPath, indexHtml, "utf8");
  logger.info("[ui-whatsynaptor] Applied runtime config + watermark script.");
}

async function restorePatchedUiIfPresent(
  logger: UiPatcherLogger,
  targetDir: string,
  backupDir: string,
): Promise<void> {
  const marker = await readMarker(targetDir);
  if (marker?.patchedBy !== "ui-whatsynaptor") return;
  if (!(await pathExists(backupDir))) {
    logger.warn("[ui-whatsynaptor] WAHA detected; patch marker exists but no backup found to restore.");
    return;
  }
  logger.info("[ui-whatsynaptor] WAHA detected; restoring original dist/control-ui.");
  await clearDirectory(targetDir);
  await copyDirectory(backupDir, targetDir);
  const markerPath = path.join(targetDir, MARKER_FILE);
  if (await pathExists(markerPath)) await fs.rm(markerPath, { force: true });
}

export async function applyUiPatch(options: UiPatcherOptions): Promise<void> {
  const { logger, pluginRoot, stateDir } = options;
  const repoRoot = await detectOpenClawRoot(pluginRoot);
  if (!repoRoot) {
    logger.warn("[ui-whatsynaptor] Could not detect OpenClaw install root with dist/control-ui.");
    return;
  }
  const targetDir = path.join(repoRoot, "dist/control-ui");
  const backupRoot = path.join(stateDir, BACKUP_DIR_NAME);
  const backupDir = path.join(backupRoot, "control-ui");

  logger.info("[ui-whatsynaptor] Legacy dist/control-ui replacement patcher is deprecated; skipping apply.");
  if (await isWahaInstalled(pluginRoot)) {
    await restorePatchedUiIfPresent(logger, targetDir, backupDir);
  }
  return;

  if (!(await pathExists(targetDir))) {
    logger.warn(`[ui-whatsynaptor] Target UI directory not found: ${targetDir}`);
    return;
  }

  const marker = await readMarker(targetDir);
  if (marker?.patchedBy === "ui-whatsynaptor") {
    logger.info("[ui-whatsynaptor] Control UI already patched.");
    return;
  }

  if (!(await pathExists(backupDir))) {
    await ensureDir(backupRoot);
    logger.info("[ui-whatsynaptor] Backing up original dist/control-ui.");
    await copyDirectory(targetDir, backupDir);
  }

  logger.info("[ui-whatsynaptor] Applying patched dist/control-ui.");
  await clearDirectory(targetDir);
  await copyDirectory(sourceDir, targetDir);
  await applyRuntimeFiles(options, targetDir);
  await writeMarker(targetDir);
}

export async function restoreUiPatch(options: UiPatcherOptions): Promise<void> {
  const { logger, pluginRoot, stateDir, autoRestoreOnStop } = options;
  if (!autoRestoreOnStop) return;

  const repoRoot = await detectOpenClawRoot(pluginRoot);
  if (!repoRoot) return;

  const targetDir = path.join(repoRoot, "dist/control-ui");
  const backupDir = path.join(path.join(stateDir, BACKUP_DIR_NAME), "control-ui");
  if (!(await pathExists(backupDir))) return;

  logger.info("[ui-whatsynaptor] Restoring original dist/control-ui from backup.");
  await clearDirectory(targetDir);
  await copyDirectory(backupDir, targetDir);

  const markerPath = path.join(targetDir, MARKER_FILE);
  if (await pathExists(markerPath)) {
    await fs.rm(markerPath, { force: true });
    logger.info("[ui-whatsynaptor] Removed patch marker after restore.");
  }
}
