#!/usr/bin/env ts-node
/**
 * PDF Datasheet Generator (React-PDF)
 *
 * Renders PDFs via `@react-pdf/renderer`.
 *
 * Source of truth:
 * - All technical data + cross-section tables: Excel files in `data/excel/`
 * - Product description text: Fetched dynamically from Payload CMS
 */
// pg-pool in Node v24 leaves dangling promises when connection fails.
// Suppress the crash so the script can fall back to Excel-only mode.
process.on('unhandledRejection', () => {});

import * as fs from 'fs';
import * as path from 'path';

import * as XLSX from 'xlsx';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import { renderToBuffer } from '@react-pdf/renderer';
import * as React from 'react';

import type { ProductData as BaseProductData } from './pdf/model/types';
interface ProductData extends BaseProductData {
  voltageType: string;
}
import { buildDatasheetModel } from './pdf/model/build-datasheet-model';
import { loadImageAsPngDataUrl, loadQrAsPngDataUrl } from './pdf/react-pdf/assets';
import { generateFileName, normalizeValue, stripHtml } from './pdf/model/utils';
import { PDFDatasheet } from '../lib/pdf-datasheet';

const CONFIG = {
  outputDir: path.join(process.cwd(), 'public/datasheets'),
  chunkSize: 10,
} as const;

const EXCEL_FILES = [
  { path: path.join(process.cwd(), 'data/excel/high-voltage.xlsx'), voltageType: 'high-voltage' },
  {
    path: path.join(process.cwd(), 'data/excel/medium-voltage-KM.xlsx'),
    voltageType: 'medium-voltage',
  },
  { path: path.join(process.cwd(), 'data/excel/low-voltage-KM.xlsx'), voltageType: 'low-voltage' },
  { path: path.join(process.cwd(), 'data/excel/solar-cables.xlsx'), voltageType: 'solar' },
] as const;

type CmsProduct = {
  slug: string;
  title: string;
  sku: string;
  categories: string[];
  images: string[];
  descriptionHtml: string;
  applicationHtml: string;
};

type CmsIndex = Map<string, CmsProduct>; // key: normalized designation/title

function ensureOutputDir(): void {
  if (!fs.existsSync(CONFIG.outputDir)) {
    fs.mkdirSync(CONFIG.outputDir, { recursive: true });
  }
}

function normalizeExcelKey(value: string): string {
  return String(value || '')
    .toUpperCase()
    .replace(/-\d+$/g, '')
    .replace(/[^A-Z0-9]+/g, '');
}

async function buildCmsIndex(locale: 'en' | 'de'): Promise<CmsIndex> {
  const idx: CmsIndex = new Map();
  try {
    // Attempt to connect to DB, but don't fail the whole script if it fails
    const payload = await getPayload({ config: configPromise });
    const isDev = process.env.NODE_ENV === 'development';
    const result = await payload.find({
      collection: 'products',
      where: {
        ...(!isDev ? { _status: { equals: 'published' } } : {}),
      },
      locale: locale as any,
      pagination: false,
    });

    for (const doc of result.docs) {
      if (!doc.title) continue;

      const title = normalizeValue(String(doc.title));
      const sku = normalizeValue(String(doc.sku || ''));
      const categories = Array.isArray(doc.categories)
        ? doc.categories.map((c: any) => normalizeValue(String(c.category || c))).filter(Boolean)
        : [];

      const images = Array.isArray(doc.images)
        ? doc.images
            .map((i: any) => normalizeValue(String(typeof i === 'string' ? i : i.url)))
            .filter(Boolean)
        : [];

      const descriptionHtml = normalizeValue(String(doc.description || ''));
      const applicationHtml = '';

      const slug = doc.slug || '';
      idx.set(normalizeExcelKey(title), {
        slug,
        title,
        sku,
        categories,
        images,
        descriptionHtml,
        applicationHtml,
      });
    }
  } catch (error) {
    console.warn(
      `[Payload] Warning: Could not fetch CMS index (${locale}). Using Excel data only.`,
      error instanceof Error ? error.message : error,
    );
  }

  return idx;
}

function findKeyByHeaderValue(headerRow: Record<string, unknown>, pattern: RegExp): string | null {
  for (const [k, v] of Object.entries(headerRow || {})) {
    const text = normalizeValue(String(v ?? ''));
    if (!text) continue;
    if (pattern.test(text)) return k;
  }
  return null;
}

function readExcelRows(filePath: string): Array<Record<string, unknown>> {
  if (!fs.existsSync(filePath)) {
    console.warn(`[Excel] Warning: File not found: ${filePath}`);
    return [];
  }
  try {
    const data = fs.readFileSync(filePath);
    const workbook = XLSX.read(data, {
      type: 'buffer',
      cellDates: false,
      cellNF: false,
      cellText: false,
    });
    const sheetName = workbook.SheetNames[0];
    if (!sheetName) return [];
    const sheet = workbook.Sheets[sheetName];
    if (!sheet) return [];

    return XLSX.utils.sheet_to_json(sheet, {
      defval: '',
      raw: false,
      blankrows: false,
    }) as Array<Record<string, unknown>>;
  } catch (err) {
    console.error(`[Excel] Failed to read ${filePath}:`, err);
    return [];
  }
}

function readDesignationsFromExcelFile(filePath: string): Map<string, string> {
  const rows = readExcelRows(filePath);
  if (!rows.length) return new Map();

  const headerRow = rows[0] || {};
  const partNumberKey =
    (Object.prototype.hasOwnProperty.call(headerRow, 'Part Number') ? 'Part Number' : null) ||
    findKeyByHeaderValue(headerRow, /^part\s*number$/i) ||
    '__EMPTY';

  const out = new Map<string, string>();
  for (const r of rows) {
    const pn = normalizeValue(String(r?.[partNumberKey] ?? ''));
    if (!pn || pn === 'Units' || pn === 'Part Number') continue;

    const key = normalizeExcelKey(pn);
    if (!key) continue;

    if (!out.has(key)) out.set(key, pn);
  }

  return out;
}

function loadAllExcelDesignations(): Map<string, { designation: string; voltageType: string }> {
  const out = new Map<string, { designation: string; voltageType: string }>();
  for (const file of EXCEL_FILES) {
    const m = readDesignationsFromExcelFile(file.path);
    Array.from(m.entries()).forEach(([k, v]) => {
      if (!out.has(k)) out.set(k, { designation: v, voltageType: file.voltageType });
    });
  }
  return out;
}

async function loadProductsFromExcelAndCms(locale: 'en' | 'de'): Promise<ProductData[]> {
  const cmsIndex = await buildCmsIndex(locale);
  const excelDesignations = loadAllExcelDesignations();

  const products: ProductData[] = [];
  let id = 1;

  Array.from(excelDesignations.entries()).forEach(([key, data]) => {
    const cmsItem = cmsIndex.get(key) || null;

    const title = cmsItem?.title || data.designation;
    const slug =
      cmsItem?.slug ||
      title
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, '');

    const descriptionHtml = cmsItem?.descriptionHtml || '';

    products.push({
      id: id++,
      name: title,
      shortDescriptionHtml: '',
      descriptionHtml,
      images: cmsItem?.images || [],
      featuredImage: (cmsItem?.images && cmsItem.images[0]) || null,
      sku: cmsItem?.sku || title,
      slug,
      translationKey: slug,
      locale,
      categories: (cmsItem?.categories || []).map((name) => ({ name })),
      attributes: [],
      voltageType: (() => {
        const cats = (cmsItem?.categories || []).map((c) => String(c));
        const isMV = cats.some((c) => /medium[-\s]?voltage|mittelspannung/i.test(c));
        if (isMV && data.voltageType === 'high-voltage') return 'medium-voltage';
        return data.voltageType;
      })(),
    });
  });

  products.sort(
    (a, b) => (a.slug || '').localeCompare(b.slug || '') || a.name.localeCompare(b.name),
  );

  return products.filter((p) => stripHtml(p.name));
}

async function processChunk(
  products: ProductData[],
  chunkIndex: number,
  totalChunks: number,
): Promise<void> {
  console.log(
    `\nProcessing chunk ${chunkIndex + 1}/${totalChunks} (${products.length} products)...`,
  );

  for (const product of products) {
    try {
      const locale = (product.locale || 'en') as 'en' | 'de';
      console.log(`[${product.id}] Starting: ${product.name} (${locale})`);

      const model = buildDatasheetModel({ product, locale });
      if (!model) {
        console.warn(`[${product.id}] Warning: buildDatasheetModel returned nothing`);
        continue;
      }

      // Load assets as Data URLs for React-PDF
      const [heroDataUrl, logoDataUrl] = await Promise.all([
        loadImageAsPngDataUrl(model.product.heroSrc),
        loadImageAsPngDataUrl('/logo-black.svg'),
      ]);

      const fileName = generateFileName(product, locale);
      const voltageType = (product as any).voltageType || 'other';
      const subfolder = path.join(CONFIG.outputDir, voltageType);

      if (!fs.existsSync(subfolder)) {
        fs.mkdirSync(subfolder, { recursive: true });
      }

      // Render using the unified component
      const element = (
        <PDFDatasheet
          product={
            {
              ...model.product,
              featuredImage: heroDataUrl,
              logoDataUrl,
            } as any
          }
          locale={locale}
          technicalItems={model.technicalItems}
          voltageTables={model.voltageTables}
          legendItems={model.legendItems}
          logoDataUrl={logoDataUrl}
        />
      );

      const buffer = await renderToBuffer(element);

      fs.writeFileSync(path.join(subfolder, fileName), buffer);

      console.log(`✓ ${locale.toUpperCase()}: ${voltageType}/${fileName}`);
      await new Promise((resolve) => setTimeout(resolve, 25));
    } catch (error) {
      console.error(`✗ Failed to process product ${product.id} (${product.name}):`, error);
    }
  }
}

async function processProductsInChunks(): Promise<void> {
  console.log('Starting PDF generation (React-PDF - Unified Component)');
  ensureOutputDir();

  const onlyLocale = normalizeValue(String(process.env.PDF_LOCALE || '')).toLowerCase();
  const locales: Array<'en' | 'de'> =
    onlyLocale === 'de' || onlyLocale === 'en' ? [onlyLocale] : ['en', 'de'];

  const allProducts: ProductData[] = [];
  for (const locale of locales) {
    const products = await loadProductsFromExcelAndCms(locale);
    allProducts.push(...products);
  }

  if (allProducts.length === 0) {
    console.log('No products found');
    return;
  }

  let products = allProducts;

  const match = normalizeValue(String(process.env.PDF_MATCH || '')).toLowerCase();
  if (match) {
    products = products.filter((p) => {
      const hay = [p.slug, p.translationKey, p.sku, p.name].filter(Boolean).join(' ').toLowerCase();
      return hay.includes(match);
    });
  }

  const limit = Number(process.env.PDF_LIMIT || '0');
  products = Number.isFinite(limit) && limit > 0 ? products.slice(0, limit) : products;

  const enProducts = products.filter((p) => (p.locale || 'en') === 'en');
  const deProducts = products.filter((p) => (p.locale || 'en') === 'de');
  console.log(`Found ${enProducts.length} EN + ${deProducts.length} DE products`);

  const totalChunks = Math.ceil(products.length / CONFIG.chunkSize);
  for (let i = 0; i < totalChunks; i++) {
    const chunk = products.slice(i * CONFIG.chunkSize, (i + 1) * CONFIG.chunkSize);
    await processChunk(chunk, i, totalChunks);
  }

  console.log('\n✅ PDF generation completed!');
  console.log(`Generated ${enProducts.length} EN + ${deProducts.length} DE PDFs`);
  console.log(`Output: ${CONFIG.outputDir}`);
}

async function main(): Promise<void> {
  const start = Date.now();
  try {
    await processProductsInChunks();
    console.log(`\nTime: ${((Date.now() - start) / 1000).toFixed(2)}s`);
  } catch (error) {
    console.error('Fatal error:', error);
    process.exit(1);
  }
}

main().catch(console.error);

export { main as generatePDFDatasheets };
