/**
 * GULPFILE.JS
 *
 * This file is required for the 'Critical CSS Generator' Drupal module.
 * It defines the Gulp tasks that generate critical CSS files.
 *
 * It is designed to be triggered by the Drupal module, which passes
 * a '--url' argument (e.g., --url=https://www.my-live-site.com).
 */

// Import required packages
const gulp = require('gulp');
const fs = require('fs');
const path = require('path');
const puppeteer = require('puppeteer');
// NOTE: 'critical' is now ESM-only with top-level await.
// Avoid require('critical'); dynamically import it within tasks instead.
// 'argv' will contain our parsed command-line arguments (like --url)
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const argv = yargs(hideBin(process.argv)).argv;

// --- CONFIGURATION ---
// IMPORTANT: Change these values to match your theme!

// 1. Path where CSS files should be saved, relative to this gulpfile.js
//    Your theme's 'css' folder is a good choice.
const themeAssetPath = './css/critical';

const drupalDocroot = path.resolve(__dirname, '../../../..');

// 1c. CSS sources to feed into Critical directly. Supplying these prevents
//     Critical from crawling <link> tags (and failing on dynamic URLs).
//     Use globs relative to the base (drupalDocroot) where suitable.
const colorsCssAbs = path.join(drupalDocroot, 'sites/default/files/toc_master_components_bg_col_css/colors.css');
const criticalCssSources = [
  // Theme global CSS (relative to theme cwd).
  '../wingsuit/*.css',
  '../wingsuit/styles/*.css',
  './css/**/*.css',
  // Dynamic background colours CSS generated by the module (absolute path).
  colorsCssAbs,
];

const homepageAdditionsPath = path.join(__dirname, 'homepage-additions.css');

console.log(`Critical base: ${drupalDocroot}`);
console.log(`Including colors CSS: ${colorsCssAbs}`);

// 2. The dimensions to scan.
const dimensions = [
  { width: 375, height: 812 }, // Mobile (iPhone X)
  { width: 1920, height: 1440 }, // Desktop
];

// 3. The Base URL.
//    This code dynamically gets the URL from the '--url' argument
//    passed by the Drupal module.
//    If you run 'gulp' manually, it falls back to your local site.
//    !! CHANGE 'http://d9-londonnorthwesternrailway.lndo.site' to your local URL.
const baseUrl = (argv && argv.url) ? argv.url : 'https://www.londonnorthwesternrailway.co.uk';

console.log(`Using Base URL: ${baseUrl}`);

// --- GULP TASKS ---

// Shared Puppeteer launcher for Penthouse inside Docker/CI
function resolveChromeExecutable() {
  const candidates = [
    process.env.PUPPETEER_EXECUTABLE_PATH,
    '/usr/bin/chromium-browser',
    '/usr/bin/chromium',
    '/usr/bin/google-chrome',
    '/usr/bin/google-chrome-stable',
  ].filter(Boolean);
  for (const p of candidates) {
    try {
      if (fs.existsSync(p)) return p;
    } catch (_) {}
  }
  return undefined;
}

function minifyCssToSingleLine(css) {
  if (!css) return '';
  return css
    .replace(/\/\*[\s\S]*?\*\//g, '')
    .replace(/\s+/g, ' ')
    .replace(/\s*([:;{},>])\s*/g, '$1')
    .trim();
}

const chromeExecutable = resolveChromeExecutable();
const launchOpts = {
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--ignore-certificate-errors',
    // Helps in Docker where /dev/shm is small
    '--disable-dev-shm-usage',
  ],
};
if (chromeExecutable) {
  launchOpts.executablePath = chromeExecutable;
}
if (launchOpts.executablePath) {
  console.log(`Using Chromium executable: ${launchOpts.executablePath}`);
} else {
  console.log('No system Chromium found; Puppeteer will use its bundled binary (may be wrong arch).');
}

// Keep a single Puppeteer browser alive for all tasks, but recreate it if the
// process crashes or disconnects. This avoids the hanging process while still
// preventing "WebSocket is not open" errors when a browser dies mid-run.
let sharedBrowserPromise;
async function getSharedBrowser() {
  if (sharedBrowserPromise) {
    try {
      const existing = await sharedBrowserPromise;
      if (existing && existing.isConnected()) {
        return existing;
      }
    } catch (_) {
      // fall through to relaunch
    }
  }

  sharedBrowserPromise = puppeteer.launch(launchOpts);
  const browser = await sharedBrowserPromise;
  browser.on('disconnected', () => {
    sharedBrowserPromise = null;
  });
  return browser;
}

async function closeSharedBrowser() {
  if (!sharedBrowserPromise) return;
  try {
    const browser = await sharedBrowserPromise;
    if (browser && browser.isConnected()) {
      await browser.close();
    }
  }
  catch (e) {
    console.warn('Warning: failed to close Puppeteer browser', e);
  }
}

const penthouseOpts = {
  puppeteer: {
    getBrowser: () => getSharedBrowser(),
  },
};

// Retry helper to handle occasional Chromium socket drops when tasks run in parallel.
async function runWithRetry(taskName, fn, retries = 1) {
  let attempt = 0;
  // eslint-disable-next-line no-constant-condition
  while (true) {
    try {
      return await fn();
    }
    catch (err) {
      const message = err && err.message ? err.message : String(err);
      const canRetry = attempt < retries && message.includes('WebSocket is not open');
      if (!canRetry) {
        throw err;
      }
      console.warn(`[${taskName}] WebSocket closed; restarting browser and retrying (attempt ${attempt + 2})`);
      await closeSharedBrowser();
      attempt += 1;
    }
  }
}

/**
 * Task: Homepage (Fallback)
 * This is the main fallback CSS for your site.
 * It runs on the homepage.
 * Generates: default-critical.css
 *
 * The Critical CSS module will use this file for all pages
 * that don't have a more-specific file.
 */
gulp.task('critical:homepage', async () => {
  const { generate } = await import('critical');
  await runWithRetry('critical:homepage', () => generate({
    src: baseUrl, // The homepage URL
    target: { css: `${themeAssetPath}/front.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
    // You can add 'penthouse' options here if needed
    // See: https://github.com/penthouse/penthouse#options
  }));

  try {
    const frontCssPath = path.join(__dirname, themeAssetPath, 'front.css');

    if (fs.existsSync(homepageAdditionsPath)) {
      const additionsCss = fs.readFileSync(homepageAdditionsPath, 'utf8');
      const additionsMin = minifyCssToSingleLine(additionsCss);
      if (additionsMin) {
        fs.appendFileSync(frontCssPath, ` ${additionsMin}`);
      }
    } else {
      console.warn(`[critical] homepage additions file not found at ${homepageAdditionsPath}; skipping append.`);
    }
  } catch (e) {
    console.warn('[critical] Failed to append homepage additions CSS to front.css', e && e.message ? e.message : e);
  }
});

/**
 * Task: Article Content Type
 * !! CHANGE '/path-to-real-article' to a real article path.
 * Generates: article.css
 *
 * In Drupal, you'll configure the Critical CSS module to use
 * this 'article.css' file for the 'Article' content type.
 */
gulp.task('critical:article', async () => {
  // !! IMPORTANT: Change this to a real, representative article on your site.
  const articlePath = '/blog/my-first-article'; // e.g., /node/123

  const { generate } = await import('critical');
  return runWithRetry('critical:article', () => generate({
    src: `${baseUrl}${articlePath}`, // e.g., https://www.mysite.com/blog/my-first-article
    target: { css: `${themeAssetPath}/article.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Basic Page Content Type
 * !! CHANGE '/path-to-real-page' to a real basic page path.
 * Generates: page.css
 *
 * In Drupal, you'll configure the Critical CSS module to use
 * this 'page.css' file for the 'Basic page' content type.
 */
gulp.task('critical:page', async () => {
  // !! IMPORTANT: Change this to a real, representative page on your site.
  const pagePath = '/about-us'; // e.g., /node/456

  const { generate } = await import('critical');
  return runWithRetry('critical:page', () => generate({
    src: `${baseUrl}${pagePath}`, // e.g., https://www.mysite.com/about-us
    target: { css: `${themeAssetPath}/page.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Routes page
 * Generates: route.css
 */
gulp.task('critical:routes', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:routes', () => generate({
    src: `${baseUrl}/train-times/lichfield-to-birmingham`,
    target: { css: `${themeAssetPath}/route.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Stations page
 * Generates: station.css
 */
gulp.task('critical:stations', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:stations', () => generate({
    src: `${baseUrl}/stations/birmingham-international`,
    target: { css: `${themeAssetPath}/station.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Destinations page
 * Generates: destination.css
 */
gulp.task('critical:destinations', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:destinations', () => generate({
    src: `${baseUrl}/destinations/trains-to-birmingham`,
    target: { css: `${themeAssetPath}/destination.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Live departures/arrivals
 * Generates: travel-information-journey-planning-live-departure-arrival-boards.css
 */
gulp.task('critical:live-departures-arrivals', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:live-departures-arrivals', () => generate({
    src: `${baseUrl}/travel-information/journey-planning/live-departure-arrival-boards`,
    target: { css: `${themeAssetPath}/travel-information-journey-planning-live-departure-arrival-boards.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Tickets & discounts
 * Generates: tickets-discounts.css
 */
gulp.task('critical:tickets-discounts', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:tickets-discounts', () => generate({
    src: `${baseUrl}/tickets-discounts`,
    target: { css: `${themeAssetPath}/tickets-discounts.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Off-peak train tickets
 * Generates: tickets-discounts-ticket-types-off-peak-train-tickets.css
 */
gulp.task('critical:off-peak-train-tickets', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:off-peak-train-tickets', () => generate({
    src: `${baseUrl}/tickets-discounts/ticket-types/off-peak-train-tickets`,
    target: { css: `${themeAssetPath}/tickets-discounts-ticket-types-off-peak-train-tickets.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Contact us
 * Generates: contact-us.css
 */
gulp.task('critical:contact-us', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:contact-us', () => generate({
    src: `${baseUrl}/contact-us`,
    target: { css: `${themeAssetPath}/contact-us.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Customer relations
 * Generates: contact-us-customer-relations.css
 */
gulp.task('critical:customer-relations', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:customer-relations', () => generate({
    src: `${baseUrl}/contact-us/customer-relations`,
    target: { css: `${themeAssetPath}/contact-us-customer-relations.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Service disruption
 * Generates: travel-information-journey-planning-service-disruption.css
 */
gulp.task('critical:service-disruption', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:service-disruption', () => generate({
    src: `${baseUrl}/travel-information/journey-planning/service-disruption`,
    target: { css: `${themeAssetPath}/travel-information-journey-planning-service-disruption.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Browse our careers
 * Generates: about-us-careers-browse-our-careers.css
 */
gulp.task('critical:browse-our-careers', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:browse-our-careers', () => generate({
    src: `${baseUrl}/about-us/careers/browse-our-careers`,
    target: { css: `${themeAssetPath}/about-us-careers-browse-our-careers.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Train driver jobs
 * Generates: about-us-careers-browse-our-careers-train-driver-jobs.css
 */
gulp.task('critical:train-driver-jobs', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:train-driver-jobs', () => generate({
    src: `${baseUrl}/about-us/careers/browse-our-careers/train-driver-jobs`,
    target: { css: `${themeAssetPath}/about-us-careers-browse-our-careers-train-driver-jobs.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Task: Train driver jobs
 * Generates: about-us-careers-browse-our-careers-train-driver-jobs.css
 */
gulp.task('critical:offers', async () => {
  const { generate } = await import('critical');
  return runWithRetry('critical:offers', () => generate({
    src: `${baseUrl}/live-travel-updates`,
    target: { css: `${themeAssetPath}/live-travel-updates.css` },
    dimensions: dimensions,
    penthouse: penthouseOpts,
  }));
});

/**
 * Extra tasks from provided URL list
 * Each task name is derived from the URL path with slashes replaced by hyphens.
 * Each output CSS filename mirrors that hyphenated path.
 */
function registerCriticalTaskFromPath(urlPath) {
  const taskSlug = urlPath.replace(/^\//, '').replace(/\//g, '-');
  const taskName = `critical:${taskSlug}`;
  gulp.task(taskName, async () => {
    const { generate } = await import('critical');
    return runWithRetry(taskName, () => generate({
      src: `${baseUrl}${urlPath}`,
      target: { css: `${themeAssetPath}/${taskSlug}.css` },
      dimensions: dimensions,
      penthouse: penthouseOpts,
    }));
  });
  return taskName;
}

// Load extra URL paths from deployment_config/critical_css_extra_urls.txt
// Format: one URL path per line, no quotes. Example: /live-travel-updates
function loadExtraPathsFromFile() {
  try {
    // Wodby container mount: /deployment_config
    const configPath = '/deployment_config/critical_css_extra_urls.txt';
    if (!fs.existsSync(configPath)) {
      console.warn(`[critical] Extra URLs file not found at ${configPath}. Skipping extra tasks.`);
      return [];
    }

    const content = fs.readFileSync(configPath, 'utf8');
    const lines = content.split(/\r?\n/);
    const paths = [];

    for (const raw of lines) {
      const line = (raw || '').trim();
      if (!line) continue; // ignore empty lines
      if (line.startsWith('#')) continue; // allow comments

      // If the line looks like a full URL, extract the pathname and warn.
      if (/^https?:\/\//i.test(line)) {
        try {
          const u = new URL(line);
          console.warn(`[critical] Extra URL line is a full URL; using path '${u.pathname}' instead: ${line}`);
          paths.push(u.pathname);
          continue;
        }
        catch (_) {
          console.warn(`[critical] Invalid URL format in extra URLs file, skipping: ${line}`);
          continue;
        }
      }

      // Lines should not be quoted; log and strip simple surrounding quotes.
      let cleaned = line;
      if ((cleaned[0] === '"' && cleaned[cleaned.length - 1] === '"') || (cleaned[0] === '\'' && cleaned[cleaned.length - 1] === '\'')) {
        console.warn(`[critical] Quotes found around path in extra URLs file; stripping quotes: ${cleaned}`);
        cleaned = cleaned.slice(1, -1).trim();
      }

      // Ensure path starts with '/'. If not, log and fix.
      if (!cleaned.startsWith('/')) {
        console.warn(`[critical] Path does not start with '/'; prefixing automatically: ${cleaned}`);
        cleaned = '/' + cleaned;
      }

      // Basic sanity: avoid whitespace
      if (/\s/.test(cleaned)) {
        console.warn(`[critical] Whitespace found in path; skipping: ${cleaned}`);
        continue;
      }

      paths.push(cleaned);
    }

    return paths;
  }
  catch (e) {
    console.warn('[critical] Failed to read extra URLs file; skipping extra tasks.', e && e.message ? e.message : e);
    return [];
  }
}

const extraPaths = loadExtraPathsFromFile();
const extraTaskNames = extraPaths.map(registerCriticalTaskFromPath);

if (extraTaskNames.length > 0) {
  gulp.task('critical:extra', gulp.series(...extraTaskNames));
} else {
  gulp.task('critical:extra', (done) => {
    console.log('[critical] No extra paths to process.');
    done();
  });
}

// Add more tasks for other templates (e.g., 'critical:news', 'critical:event')
// just by copying the 'critical:article' task and changing the paths.

gulp.task(
  'critical:all',
  gulp.series(
    // Run sequentially to reduce browser churn in containerized environments.
    'critical:homepage',
    'critical:article',
    'critical:page',
    'critical:routes',
    'critical:stations',
    'critical:destinations',
    'critical:live-departures-arrivals',
    'critical:tickets-discounts',
    'critical:off-peak-train-tickets',
    'critical:contact-us',
    'critical:customer-relations',
    'critical:service-disruption',
    'critical:browse-our-careers',
    'critical:train-driver-jobs',
    'critical:extra',
    async function cleanup() {
      await closeSharedBrowser();
    }
  )
);

gulp.task('default', async () => {
  // Use gulp.series to run critical:all, which in turn runs the parallel tasks.
  await gulp.series('critical:all')();
});
