#!/usr/bin/env node
/**
 * Auto-close browser dialogs and CSS modals.
 *
 * Runs as a background script that sets up listeners BEFORE navigation,
 * so it catches modals that appear on page load.
 *
 * Handles:
 * - Browser dialogs (alert, confirm, prompt, beforeunload)
 * - Framework modals (Bootstrap, Tailwind, shadcn, Angular Material, jQuery UI, SweetAlert)
 * - Cookie consent banners, newsletter popups, age gates
 *
 * Usage: on_Snapshot__15_modalcloser.daemon.bg.js --url=<url>
 * Output: JSONL with modal close stats (no files created)
 * Termination: Send SIGTERM to exit cleanly
 *
 * Environment variables:
 *     MODALCLOSER_ENABLED: Enable/disable (default: true)
 *     MODALCLOSER_TIMEOUT: Delay before auto-closing dialogs in ms (default: 1250)
 *     MODALCLOSER_POLL_INTERVAL: How often to check for CSS modals in ms (default: 500)
 */


// Cleanup can SIGTERM the process immediately after spawn; remember early
// signals and replay them to the hook-specific cleanup handler once it exists.
let __abxEarlyShutdownSignal = null;
function __abxRememberEarlyShutdown(signal) {
  if (__abxEarlyShutdownSignal === null) {
    __abxEarlyShutdownSignal = signal;
  }
}
function __abxInstallShutdownHandler(handler) {
  process.removeAllListeners("SIGTERM");
  process.removeAllListeners("SIGINT");
  process.on("SIGTERM", () => handler("SIGTERM"));
  process.on("SIGINT", () => handler("SIGINT"));
  if (__abxEarlyShutdownSignal !== null) {
    const signal = __abxEarlyShutdownSignal;
    __abxEarlyShutdownSignal = null;
    setImmediate(() => handler(signal));
  }
}
process.on("SIGTERM", () => __abxRememberEarlyShutdown("SIGTERM"));
process.on("SIGINT", () => __abxRememberEarlyShutdown("SIGINT"));

const fs = require("fs");
const path = require("path");
const { loadConfig } = require("../base/utils.js");

const PLUGIN_DIR = path.basename(__dirname);
const hookConfig = loadConfig();
const SNAP_DIR = path.resolve((hookConfig.SNAP_DIR || ".").trim());
const OUTPUT_DIR = path.join(SNAP_DIR, PLUGIN_DIR);
if (!fs.existsSync(OUTPUT_DIR)) {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
process.chdir(OUTPUT_DIR);

// Import generic helpers from base/utils.js
const {
  ensureNodeModuleResolution,
  getEnvBool,
  getEnvInt,
  parseArgs,
  emitArchiveResultRecord,
} = require("../base/utils.js");
ensureNodeModuleResolution(module);

// Import chrome-specific utilities from chrome_utils.js
const { connectToPage } = require("../chrome/chrome_utils.js");

function resolvePuppeteer() {
  for (const moduleName of ["puppeteer-core", "puppeteer"]) {
    try {
      return require(moduleName);
    } catch (error) {}
  }
  throw new Error(
    "Missing puppeteer dependency (need puppeteer-core or puppeteer)"
  );
}

// Check if modalcloser is enabled BEFORE requiring puppeteer
if (!getEnvBool("MODALCLOSER_ENABLED", true)) {
  console.error("Skipping modalcloser (MODALCLOSER_ENABLED=False)");
  emitArchiveResultRecord("skipped", "MODALCLOSER_ENABLED=False");
  process.exit(0);
}

const puppeteer = resolvePuppeteer();

const PLUGIN_NAME = "modalcloser";
const CHROME_SESSION_DIR = path.join(SNAP_DIR, "chrome");

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function formatClosedCount(count) {
  return `${count} modal${count === 1 ? "" : "s"} closed`;
}

let lastProgressLine = "";

function emitProgress(line) {
  if (line && line !== lastProgressLine) {
    lastProgressLine = line;
    console.log(line);
  }
}

/**
 * Close CSS modals using framework-specific dismiss methods.
 * Returns the number of modals closed.
 */
async function closeModals(page) {
  return page.evaluate(() => {
    let closed = 0;

    // Bootstrap 4/5 - use Bootstrap's modal API
    if (typeof bootstrap !== "undefined" && bootstrap.Modal) {
      document.querySelectorAll(".modal.show").forEach((el) => {
        try {
          const modal = bootstrap.Modal.getInstance(el);
          if (modal) {
            modal.hide();
            closed++;
          }
        } catch (e) {}
      });
    }

    // Bootstrap 3 / jQuery - use jQuery modal API
    if (typeof jQuery !== "undefined" && jQuery.fn && jQuery.fn.modal) {
      try {
        const $modals = jQuery(".modal.in, .modal.show");
        if ($modals.length > 0) {
          $modals.modal("hide");
          closed += $modals.length;
        }
      } catch (e) {}
    }

    // shadcn/Radix UI - fire escape key to dismiss
    document
      .querySelectorAll(
        '[data-radix-dialog-overlay], [data-state="open"][role="dialog"]'
      )
      .forEach((el) => {
        try {
          el.dispatchEvent(
            new KeyboardEvent("keydown", {
              key: "Escape",
              bubbles: true,
              cancelable: true,
            })
          );
          closed++;
        } catch (e) {}
      });

    // Angular Material - click backdrop to dismiss
    document.querySelectorAll(".cdk-overlay-backdrop").forEach((el) => {
      try {
        el.click();
        closed++;
      } catch (e) {}
    });

    // Tailwind / Headless UI - dispatch escape key
    document
      .querySelectorAll('[role="dialog"][aria-modal="true"]')
      .forEach((el) => {
        try {
          el.dispatchEvent(
            new KeyboardEvent("keydown", {
              key: "Escape",
              bubbles: true,
              cancelable: true,
            })
          );
          closed++;
        } catch (e) {}
      });

    // jQuery UI Dialog
    if (typeof jQuery !== "undefined" && jQuery.ui && jQuery.ui.dialog) {
      try {
        const $dialogs = jQuery(".ui-dialog-content");
        if ($dialogs.length > 0) {
          $dialogs.dialog("close");
          closed += $dialogs.length;
        }
      } catch (e) {}
    }

    // SweetAlert2
    if (typeof Swal !== "undefined" && Swal.close) {
      try {
        Swal.close();
        closed++;
      } catch (e) {}
    }

    // SweetAlert 1
    if (typeof swal !== "undefined" && swal.close) {
      try {
        swal.close();
        closed++;
      } catch (e) {}
    }

    // Generic fallback - hide unrecognized modals with CSS
    const genericSelectors = [
      // CookieYes (cky)
      ".cky-consent-container",
      ".cky-popup-center",
      ".cky-overlay",
      ".cky-modal",
      "#ckyPreferenceCenter",
      // OneTrust
      "#onetrust-consent-sdk",
      "#onetrust-banner-sdk",
      ".onetrust-pc-dark-filter",
      "#onetrust-pc-sdk",
      // CookieBot
      "#CybotCookiebotDialog",
      "#CybotCookiebotDialogBodyUnderlay",
      "#CookiebotWidget",
      // Quantcast / CMP
      ".qc-cmp-ui-container",
      "#qc-cmp2-container",
      ".qc-cmp2-summary-buttons",
      // TrustArc / TrustE
      "#truste-consent-track",
      ".truste-banner",
      "#truste-consent-content",
      // Osano
      ".osano-cm-window",
      ".osano-cm-dialog",
      // Klaro
      ".klaro .cookie-modal",
      ".klaro .cookie-notice",
      // Tarteaucitron
      "#tarteaucitronRoot",
      "#tarteaucitronAlertBig",
      // Complianz (WordPress)
      ".cmplz-cookiebanner",
      "#cmplz-cookiebanner-container",
      // GDPR Cookie Consent (WordPress)
      "#gdpr-cookie-consent-bar",
      ".gdpr-cookie-consent-popup",
      // Cookie Notice (WordPress)
      "#cookie-notice",
      ".cookie-notice-container",
      // EU Cookie Law
      ".eupopup",
      "#eu-cookie-law",
      // Didomi
      "#didomi-popup",
      "#didomi-host",
      ".didomi-popup-container",
      // Usercentrics
      "#usercentrics-root",
      ".uc-banner",
      // Axeptio
      "#axeptio_overlay",
      "#axeptio_btn",
      // iubenda
      "#iubenda-cs-banner",
      ".iubenda-cs-container",
      // Termly
      ".termly-consent-banner",
      "#termly-code-snippet-support",
      // Borlabs Cookie (WordPress)
      "#BorlabsCookieBox",
      ".BorlabsCookie",
      // CookieFirst
      ".cookiefirst-root",
      "#cookiefirst-root",
      // CookieScript
      "#cookiescript_injected",
      ".cookiescript_injected_wrapper",
      // Civic Cookie Control
      "#ccc",
      "#ccc-overlay",
      // Generic patterns
      "#cookie-consent",
      ".cookie-banner",
      ".cookie-notice",
      "#cookieConsent",
      ".cookie-consent",
      ".cookies-banner",
      '[class*="cookie"][class*="banner"]',
      '[class*="cookie"][class*="notice"]',
      '[class*="cookie"][class*="popup"]',
      '[class*="cookie"][class*="modal"]',
      '[class*="consent"][class*="banner"]',
      '[class*="consent"][class*="popup"]',
      '[class*="gdpr"]',
      '[class*="privacy"][class*="banner"]',
      // Modal overlays and backdrops
      '.modal-overlay:not([style*="display: none"])',
      '.modal-backdrop:not([style*="display: none"])',
      ".overlay-visible",
      // Popup overlays
      ".popup-overlay",
      ".newsletter-popup",
      ".age-gate",
      ".subscribe-popup",
      ".subscription-modal",
      // Generic modal patterns
      '[class*="modal"][class*="open"]:not(.modal-open)',
      '[class*="modal"][class*="show"][class*="overlay"]',
      '[class*="modal"][class*="visible"]',
      '[class*="dialog"][class*="open"]',
      '[class*="overlay"][class*="visible"]',
      // Interstitials
      ".interstitial",
      ".interstitial-wrapper",
      '[class*="interstitial"]',
    ];

    genericSelectors.forEach((selector) => {
      try {
        document.querySelectorAll(selector).forEach((el) => {
          // Skip if already hidden
          const style = window.getComputedStyle(el);
          if (style.display === "none" || style.visibility === "hidden") return;

          el.style.display = "none";
          el.style.visibility = "hidden";
          el.style.opacity = "0";
          el.style.pointerEvents = "none";
          closed++;
        });
      } catch (e) {}
    });

    // Remove body scroll lock (common pattern when modals are open)
    try {
      document.body.style.overflow = "";
      document.body.style.position = "";
      document.body.classList.remove(
        "modal-open",
        "overflow-hidden",
        "no-scroll",
        "scroll-locked"
      );
      document.documentElement.style.overflow = "";
      document.documentElement.classList.remove("overflow-hidden", "no-scroll");
    } catch (e) {}

    return closed;
  });
}

async function main() {
  const args = parseArgs();
  const url = args.url;

  if (!url) {
    console.error(
      "Usage: on_Snapshot__15_modalcloser.daemon.bg.js --url=<url>"
    );
    process.exit(1);
  }

  const dialogTimeout = getEnvInt("MODALCLOSER_TIMEOUT", 1250);
  const pollInterval = getEnvInt("MODALCLOSER_POLL_INTERVAL", 500);

  let browser = null;
  let dialogsClosed = 0;
  let cssModalsClosed = 0;
  let running = true;
  const connectTimeoutMs =
    getEnvInt("CHROME_TIMEOUT", getEnvInt("TIMEOUT", 15)) * 1000;

  // Handle SIGTERM/SIGINT for clean exit
  __abxInstallShutdownHandler(() => {
    running = false;
    const total = dialogsClosed + cssModalsClosed;
    console.error(
      `Modalcloser exiting: closed ${dialogsClosed} dialogs, ${cssModalsClosed} CSS modals`
    );

    const outputStr = `${total} modals closed`;
    const status = total > 0 ? "succeeded" : "noresults";

    emitArchiveResultRecord(status, outputStr);

    if (browser) browser.disconnect();
    process.exit(0);
  });

  try {
    const connection = await connectToPage({
      chromeSessionDir: CHROME_SESSION_DIR,
      timeoutMs: connectTimeoutMs,
      requireTargetId: true,
      puppeteer,
    });
    browser = connection.browser;
    const page = connection.page;
    emitProgress(formatClosedCount(0));

    // console.error(`Modalcloser listening on ${url}`);

    // Set up dialog handler (for JS alert/confirm/prompt/beforeunload)
    page.on("dialog", async (dialog) => {
      const type = dialog.type();
      const message = dialog.message().substring(0, 100);
      console.error(`Auto-closing dialog: ${type} - "${message}"`);

      // Small delay before accepting (some pages expect a brief pause)
      await sleep(dialogTimeout);
      try {
        await dialog.accept();
        dialogsClosed++;
        emitProgress(formatClosedCount(dialogsClosed + cssModalsClosed));
      } catch (e) {
        // Dialog may have been dismissed by page
      }
    });

    // Poll for CSS modals
    while (running) {
      try {
        const closed = await closeModals(page);
        if (closed > 0) {
          console.error(`Closed ${closed} CSS modals`);
          cssModalsClosed += closed;
          emitProgress(formatClosedCount(dialogsClosed + cssModalsClosed));
        }
      } catch (e) {
        // Page may have navigated or been closed
        if (!running) break;
      }
      await sleep(pollInterval);
    }
  } catch (e) {
    if (browser) browser.disconnect();
    console.error(`ERROR: ${e.name}: ${e.message}`);
    process.exit(1);
  }
}

main().catch((e) => {
  console.error(`Fatal error: ${e.message}`);
  process.exit(1);
});
