#!/usr/bin/env node
/*
 * Responsive overflow guard for Flask routes.
 *
 * Usage:
 *   scripts/check_responsive_overflow.sh --base-url http://127.0.0.1:5003
 *   scripts/check_responsive_overflow.sh --route /daily_sales --route /edm
 */

const fs = require('fs');
const os = require('os');
const path = require('path');
const Module = require('module');

const DEFAULT_BASE_URL = 'http://127.0.0.1:5003';
const DEFAULT_ROUTES = [
  '/',
  '/edm',
  '/festival',
  '/mothers_day',
  '/valentine_520',
  '/labor_day',
  '/sales_analysis',
  '/daily_sales',
  '/monthly_summary_analysis',
  '/growth_analysis',
  '/ai_recommend',
  '/ai_history',
  '/ai_intelligence',
  '/auto_import',
  '/vendor-stockout/',
  '/vendor-stockout/import',
  '/vendor-stockout/list',
  '/vendor-stockout/vendor-management',
  '/vendor-stockout/send-email',
  '/vendor-stockout/history',
  '/settings',
  '/system_settings',
  '/ai_automation_smoke',
  '/logs',
  '/user_management',
  '/change_password',
  '/login_history',
  '/pchome_crawler',
  '/price_comparison',
  '/trends',
  '/notification_templates',
  '/brand_assets',
  '/market_intel',
  '/market_intel/campaigns',
  '/market_intel/products',
  '/market_intel/matches',
  '/market_intel/opportunities',
  '/code-review/',
  '/cicd',
  '/observability/overview',
  '/observability/agent_orchestration',
  '/observability/business_intel',
  '/observability/host_health',
  '/observability/ai_calls',
  '/observability/budget',
  '/observability/promotion_review',
  '/observability/rag_queries',
  '/observability/quality_trend',
  '/observability/ppt_audit_history',
];

const DEFAULT_VIEWPORTS = [
  { name: 'desktop-1440', width: 1440, height: 950 },
  { name: 'tablet-630', width: 630, height: 968 },
  { name: 'mobile-390', width: 390, height: 844 },
];

const LOCAL_SCROLL_SELECTORS = [
  '.table-responsive',
  '.chart-responsive',
  '.obs-table-shell',
  '.agent-table-shell',
  '.biz-table-shell',
  '.runtime-table-shell',
  '.calls-table-shell',
  '.gov-table-shell',
  '.gate-table-shell',
  '.rag-table-shell',
  '.qa-table-shell',
  '.quality-table-shell',
  '.ppt-table-shell',
  '[class*="-table-shell"]',
  '.obs-chart-frame',
  '.chart-frame',
  '.chart-container',
  '.daily-calendar',
  '.campaign-switcher',
  '.campaign-filterbar',
  '.campaign-table-wrap',
  '.dashboard-table-wrap',
  '.dashboard-segmented',
  '.momo-nav',
  '.momo-scroll',
].join(',');

function parseArgs(argv) {
  const options = {
    baseUrl: DEFAULT_BASE_URL,
    routes: [],
    viewports: DEFAULT_VIEWPORTS,
    timeoutMs: 30000,
    waitUntil: 'commit',
    settleMs: 350,
    maxOverflow: 1,
    screenshotDir: '',
    screenshotAll: false,
    json: false,
  };

  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i];
    if (arg === '--base-url') {
      options.baseUrl = argv[++i];
    } else if (arg === '--route') {
      options.routes.push(argv[++i]);
    } else if (arg === '--routes') {
      options.routes.push(...argv[++i].split(',').map((item) => item.trim()).filter(Boolean));
    } else if (arg === '--viewport') {
      const [name, size] = argv[++i].split('=');
      const [width, height] = size.split('x').map((value) => parseInt(value, 10));
      options.viewports.push({ name, width, height });
    } else if (arg === '--clear-default-viewports') {
      options.viewports = [];
    } else if (arg === '--timeout') {
      options.timeoutMs = parseInt(argv[++i], 10) * 1000;
    } else if (arg === '--wait-until') {
      options.waitUntil = argv[++i];
    } else if (arg === '--settle-ms') {
      options.settleMs = parseInt(argv[++i], 10);
    } else if (arg === '--max-overflow') {
      options.maxOverflow = parseInt(argv[++i], 10);
    } else if (arg === '--screenshot-dir') {
      options.screenshotDir = argv[++i];
    } else if (arg === '--screenshot-all') {
      options.screenshotAll = true;
    } else if (arg === '--json') {
      options.json = true;
    } else if (arg === '--help' || arg === '-h') {
      printHelp();
      process.exit(0);
    } else {
      throw new Error(`Unknown argument: ${arg}`);
    }
  }

  if (options.routes.length === 0) {
    options.routes = DEFAULT_ROUTES;
  }
  if (options.viewports.length === 0) {
    throw new Error('At least one viewport is required.');
  }
  return options;
}

function printHelp() {
  console.log(`Responsive overflow guard

Options:
  --base-url URL              Base URL, default ${DEFAULT_BASE_URL}
  --route PATH                Add one route, can be repeated
  --routes A,B,C              Add comma-separated routes
  --viewport name=WxH         Add a viewport
  --clear-default-viewports   Use only custom --viewport entries
  --timeout SEC               Navigation timeout, default 30
  --wait-until EVENT          Playwright navigation event, default commit
  --settle-ms MS              Fixed post-DOM layout settle wait, default 350
  --max-overflow PX           Allowed body overflow, default 1
  --screenshot-dir DIR        Save failure screenshots
  --screenshot-all            Save screenshots for passing routes too
  --json                      Print JSON summary
`);
}

function requirePlaywright() {
  try {
    return require('playwright');
  } catch (error) {
    const candidates = [
      process.env.PLAYWRIGHT_NODE_MODULE_DIR,
      process.env.NODE_PATH,
      path.join(os.homedir(), '.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules'),
    ].filter(Boolean);

    for (const candidate of candidates) {
      const packageJson = path.join(candidate, 'playwright', 'package.json');
      if (fs.existsSync(packageJson)) {
        const scopedRequire = Module.createRequire(packageJson);
        return scopedRequire('playwright');
      }
    }

    throw new Error(
      'Cannot load playwright. Set NODE_PATH or PLAYWRIGHT_NODE_MODULE_DIR to a node_modules directory containing playwright.'
    );
  }
}

function findChromeExecutable() {
  const candidates = [
    process.env.RESPONSIVE_CHROME_PATH,
    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
    '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
  ].filter(Boolean);
  return candidates.find((candidate) => fs.existsSync(candidate)) || '';
}

function routeToUrl(baseUrl, route) {
  return new URL(route, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`).toString();
}

function safeName(input) {
  return input.replace(/[^a-z0-9]+/gi, '_').replace(/^_+|_+$/g, '').toLowerCase() || 'root';
}

async function createViewportPage(context, viewport) {
  const page = await context.newPage();
  await page.setViewportSize({ width: viewport.width, height: viewport.height });
  return page;
}

async function closePage(page) {
  if (!page) {
    return;
  }
  await Promise.race([
    page.close().catch(() => {}),
    new Promise((resolve) => setTimeout(resolve, 1000)),
  ]);
}

async function closeWithTimeout(target, timeoutMs = 1500) {
  if (!target || typeof target.close !== 'function') {
    return;
  }
  await Promise.race([
    target.close().catch(() => {}),
    new Promise((resolve) => setTimeout(resolve, timeoutMs)),
  ]);
}

async function collectMetrics(page, maxOverflow) {
  return page.evaluate(
    ({ localScrollSelectors, maxOverflowPx }) => {
      const root = document.documentElement;
      const body = document.body;
      const width = window.innerWidth;
      const rootOverflow = Math.max(0, root.scrollWidth - width);
      const bodyOverflow = Math.max(0, body.scrollWidth - width);
      const overflow = Math.max(rootOverflow, bodyOverflow);
      const localScroll = Array.from(document.querySelectorAll(localScrollSelectors))
        .map((el) => ({
          selector: el.className ? `.${String(el.className).trim().split(/\s+/).join('.')}` : el.tagName.toLowerCase(),
          clientWidth: el.clientWidth,
          scrollWidth: el.scrollWidth,
        }))
        .filter((item) => item.scrollWidth > item.clientWidth + maxOverflowPx);

      const offenders = [];
      for (const el of document.querySelectorAll('body *')) {
        const rect = el.getBoundingClientRect();
        if (rect.width <= 0 || rect.right <= width + maxOverflowPx) {
          continue;
        }
        if (el.closest(localScrollSelectors)) {
          continue;
        }
        offenders.push({
          tag: el.tagName.toLowerCase(),
          id: el.id || '',
          className: String(el.className || '').slice(0, 120),
          right: Math.round(rect.right),
          width: Math.round(rect.width),
          text: String(el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 90),
        });
        if (offenders.length >= 8) {
          break;
        }
      }

      return {
        finalUrl: location.href,
        title: document.title,
        innerWidth: width,
        rootScrollWidth: root.scrollWidth,
        bodyScrollWidth: body.scrollWidth,
        overflow,
        localScroll,
        offenders,
      };
    },
    { localScrollSelectors: LOCAL_SCROLL_SELECTORS, maxOverflowPx: maxOverflow }
  );
}

function formatResult(result) {
  const status = result.passed ? 'PASS' : 'FAIL';
  const overflow = result.metrics ? `${result.metrics.overflow}px` : 'n/a';
  const localScroll = result.metrics ? result.metrics.localScroll.length : 0;
  return `${status} ${result.viewport} ${result.route} overflow=${overflow} local_scroll=${localScroll}${result.error ? ` error=${result.error}` : ''}`;
}

function printResult(result) {
  console.log(formatResult(result));
  if (!result.passed && result.metrics && result.metrics.offenders.length) {
    for (const offender of result.metrics.offenders) {
      console.log(`  offender ${offender.tag}.${offender.className} right=${offender.right} text="${offender.text}"`);
    }
  }
}

async function main() {
  const options = parseArgs(process.argv.slice(2));
  const { chromium } = requirePlaywright();
  const chromePath = findChromeExecutable();
  const launchOptions = { headless: true };
  if (chromePath) {
    launchOptions.executablePath = chromePath;
  }

  if (options.screenshotDir) {
    fs.mkdirSync(options.screenshotDir, { recursive: true });
  }

  const browser = await chromium.launch(launchOptions);
  const context = await browser.newContext({ ignoreHTTPSErrors: true });
  const results = [];
  const pages = new Map();

  try {
    for (const viewport of options.viewports) {
      const page = await createViewportPage(context, viewport);
      pages.set(viewport.name, page);
    }

    for (const route of options.routes) {
      for (const viewport of options.viewports) {
        const page = pages.get(viewport.name);
        const url = routeToUrl(options.baseUrl, route);
        let status = 0;
        let error = '';
        let metrics = null;
        let resetPage = false;

        try {
          const response = await page.goto(url, { waitUntil: options.waitUntil, timeout: options.timeoutMs });
          await page.waitForSelector('body', { timeout: Math.min(options.timeoutMs, 5000) }).catch(() => {});
          await page.evaluate(() => document.fonts && document.fonts.ready).catch(() => {});
          if (options.settleMs > 0) {
            await page.waitForTimeout(options.settleMs);
          }
          status = response ? response.status() : 0;
          metrics = await collectMetrics(page, options.maxOverflow);
          if (new URL(metrics.finalUrl).pathname === '/login') {
            error = 'redirected to /login; run local QA with DISABLE_LOGIN=true or log in first';
          } else if (status >= 400) {
            error = `HTTP ${status}`;
          } else if (metrics.overflow > options.maxOverflow) {
            error = `body horizontal overflow ${metrics.overflow}px`;
          } else if (metrics.offenders.length > 0) {
            error = `visual overflow offenders ${metrics.offenders.length}`;
          }
        } catch (err) {
          error = err.message || String(err);
          resetPage = true;
        }

        const passed = !error;
        const result = { route, viewport: viewport.name, status, passed, error, metrics };
        results.push(result);
        if (!options.json) {
          printResult(result);
        }

        if ((options.screenshotAll || !passed) && options.screenshotDir) {
          const file = `${safeName(route)}_${safeName(viewport.name)}.png`;
          await page.screenshot({ path: path.join(options.screenshotDir, file), fullPage: false });
        }

        if (resetPage) {
          await closePage(page);
          pages.set(viewport.name, await createViewportPage(context, viewport));
        }
      }
    }
  } finally {
    await Promise.all(Array.from(pages.values()).map((page) => closePage(page)));
    await closeWithTimeout(context);
    await closeWithTimeout(browser);
  }

  if (options.json) {
    console.log(JSON.stringify(results, null, 2));
  }

  const failed = results.filter((result) => !result.passed);
  if (failed.length > 0) {
    process.exitCode = 1;
  }
}

main().catch((error) => {
  console.error(error.message || error);
  process.exit(1);
});
