// Utilities to extract DocuSeal-style signature placeholders from PDFs
// and estimate reasonable positions when exact text coordinates are unavailable.

import { PDFDocument } from 'pdf-lib';
import pako from 'pako';
import { execFile } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { PDFiumLibrary } from '@hyzyla/pdfium';

const execFileAsync = promisify(execFile);

export type PlaceholderMatch = {
  fullMatch: string;
  label: string;
  role: string;
  type: string;
  // DocuSeal placeholders declare dimensions in pixels (px)
  // Example: {{Signature;role=Employeur;type=signature;height=60;width=150}}
  width: number; // px
  height: number; // px
  startIndex: number;
  endIndex: number;
};

export type EstimatedPosition = {
  role: string;
  label: string;
  page: number; // 1-indexed
  x: number; // POURCENTAGES (%) from left
  y: number; // POURCENTAGES (%) from top
  width: number; // POURCENTAGES (%)
  height: number; // POURCENTAGES (%)
};

export type ExtractedPosition = {
  role: string;
  label: string;
  page: number; // 1-indexed
  x: number; // POURCENTAGES (%) from left
  y: number; // POURCENTAGES (%) from top
  width: number; // POURCENTAGES (%)
  height: number; // POURCENTAGES (%)
  text: string; // Le texte du placeholder trouvé
};

const PLACEHOLDER_REGEX = /\{\{([^;]+);role=([^;]+);type=([^;]+);height=(\d+);width=(\d+)\}\}/g;

/**
 * Count PDF pages by scanning for '/Type /Page' markers in raw bytes.
 * This is heuristic but robust enough for most PDFs without full parsing.
 */
export function countPdfPagesFromBytes(bytes: Uint8Array | Buffer): number {
  try {
    const text = bufferToLatin1String(bytes);
    // Count '/Type /Page' but not '/Type /Pages'
    const matches = text.match(/\/Type\s*\/Page(?!s)\b/g);
    if (matches && matches.length > 0) return matches.length;
  } catch {
    // ignore
  }
  // Default to 1 page if unknown
  return 1;
}

/**
 * Extract placeholders using external Node.js script with pdf-parse
 * Contourne les problèmes de compatibilité Webpack/Next.js avec pdfjs-dist
 */
export async function extractPlaceholdersWithPdfParse(bytes: Uint8Array | Buffer): Promise<{ placeholders: PlaceholderMatch[]; text: string; numPages?: number } > {
  let tempFile: string | null = null;
  
  try {
    console.log('[extractPlaceholdersWithPdfParse] Extraction du texte avec script externe...');
    
    // Écrire le PDF dans un fichier temporaire
    tempFile = path.join(os.tmpdir(), `pdf-extract-${Date.now()}.pdf`);
    fs.writeFileSync(tempFile, bytes);
    console.log('[extractPlaceholdersWithPdfParse] Fichier temporaire:', tempFile);
    
    // Chemin vers le script d'extraction
    const scriptPath = path.join(process.cwd(), 'scripts', 'extract-pdf-text.js');
    console.log('[extractPlaceholdersWithPdfParse] Script path:', scriptPath);
    
    // Exécuter le script avec le fichier PDF
    const { stdout, stderr } = await Promise.race([
      execFileAsync('node', [scriptPath, tempFile], {
        maxBuffer: 10 * 1024 * 1024 // 10MB max
      }),
      new Promise<never>((_, reject) => 
        setTimeout(() => reject(new Error('Script timeout après 30s')), 30000)
      )
    ]);

    if (stderr) {
      console.error('[extractPlaceholdersWithPdfParse] Stderr:', stderr);
    }

    console.log('[extractPlaceholdersWithPdfParse] Stdout length:', stdout.length);

    // Parser la réponse JSON
  const data = JSON.parse(stdout);
    
    if (data.error) {
      throw new Error(data.error);
    }

    const text = data.text;
    console.log('[extractPlaceholdersWithPdfParse] Texte extrait:', {
      textLength: text.length,
      numPages: data.numPages,
      preview: text.substring(0, 200)
    });
    
    console.log('[extractPlaceholdersWithPdfParse] Texte extrait:', {
      textLength: text.length,
      textPreview: text.substring(0, 500),
      containsPlaceholder: text.includes('{{'),
      containsSignature: text.includes('Signature'),
    });
    
    if (!text.includes('{{')) {
      console.warn('[extractPlaceholdersWithPdfParse] Aucun placeholder {{ trouvé dans le texte');
      return { placeholders: [], text, numPages: data.numPages };
    }
    
    const placeholders: PlaceholderMatch[] = [];
    
    // Chercher les placeholders
    PLACEHOLDER_REGEX.lastIndex = 0;
    let match: RegExpExecArray | null;
    
    while ((match = PLACEHOLDER_REGEX.exec(text)) !== null) {
      console.log('[extractPlaceholdersWithPdfParse] ✅ Placeholder trouvé:', match[0]);
      placeholders.push({
        fullMatch: match[0],
        label: match[1].trim(),
        role: match[2].trim(),
        type: match[3].trim(),
        height: parseInt(match[4], 10),
        width: parseInt(match[5], 10),
        startIndex: match.index,
        endIndex: match.index + match[0].length,
      });
    }
    
  console.log('[extractPlaceholdersWithPdfParse] Total trouvé:', placeholders.length);
  return { placeholders, text, numPages: data.numPages };
    
  } catch (error) {
    console.error('[extractPlaceholdersWithPdfParse] Erreur:', error);
  return { placeholders: [], text: '', numPages: undefined };
  } finally {
    // Nettoyer le fichier temporaire
    if (tempFile && fs.existsSync(tempFile)) {
      try {
        fs.unlinkSync(tempFile);
        console.log('[extractPlaceholdersWithPdfParse] Fichier temporaire supprimé');
      } catch (e) {
        console.error('[extractPlaceholdersWithPdfParse] Erreur lors de la suppression du fichier temporaire:', e);
      }
    }
  }
}

/**
 * Extract placeholders from PDF bytes by decompressing FlateDecode streams
 */
export function extractPlaceholdersFromPdfBuffer(bytes: Uint8Array | Buffer): PlaceholderMatch[] {
  const text = bufferToLatin1String(bytes);
  
  // Décompresser les streams FlateDecode
  let decompressedText = text;
  
  // Trouver tous les streams compressés
  const streamRegex = /\/Filter\s*\/FlateDecode[^>]*>>[\s\n]*stream[\s\n]+([\s\S]*?)[\s\n]+endstream/g;
  let streamMatch: RegExpExecArray | null;
  
  console.log('[extractPlaceholdersFromPdfBuffer] Recherche de streams FlateDecode...');
  let streamCount = 0;
  
  while ((streamMatch = streamRegex.exec(text)) !== null) {
    streamCount++;
    try {
      // Extraire les données du stream
      const compressedData = streamMatch[1];
      
      // Convertir en Uint8Array
      const bytes = new Uint8Array(compressedData.length);
      for (let i = 0; i < compressedData.length; i++) {
        bytes[i] = compressedData.charCodeAt(i);
      }
      
      // Décompresser avec pako (zlib)
      const decompressed = pako.inflate(bytes, { to: 'string' });
      
      // Ajouter le contenu décompressé
      decompressedText += '\n' + decompressed;
      
      console.log(`[extractPlaceholdersFromPdfBuffer] Stream ${streamCount} décompressé:`, {
        compressedSize: compressedData.length,
        decompressedSize: decompressed.length,
        preview: decompressed.substring(0, 200),
        containsPlaceholder: decompressed.includes('{{'),
      });
    } catch (err) {
      console.warn(`[extractPlaceholdersFromPdfBuffer] Erreur décompression stream ${streamCount}:`, err);
    }
  }
  
  console.log('[extractPlaceholdersFromPdfBuffer] Texte final:', {
    textLength: decompressedText.length,
    streamCount,
    containsPlaceholder: decompressedText.includes('{{'),
    containsSignature: decompressedText.includes('Signature'),
    placeholderIndex: decompressedText.indexOf('{{'),
    contextAroundPlaceholder: decompressedText.indexOf('{{') >= 0 
      ? decompressedText.substring(
          Math.max(0, decompressedText.indexOf('{{') - 100),
          Math.min(decompressedText.length, decompressedText.indexOf('{{') + 300)
        )
      : 'N/A',
  });
  
  // Le texte peut être encodé en hexadécimal dans les instructions PDF
  // Cherchons les séquences <...> et décodons-les
  console.log('[extractPlaceholdersFromPdfBuffer] Décodage des séquences hexadécimales...');
  const hexRegex = /<([0-9A-Fa-f\s]+)>/g;
  let hexMatch: RegExpExecArray | null;
  let hexDecodeCount = 0;
  
  while ((hexMatch = hexRegex.exec(decompressedText)) !== null) {
    const hexString = hexMatch[1].replace(/\s/g, '');
    try {
      let decoded = '';
      // Essayer décodage UTF-16 (4 hex digits par caractère)
      for (let i = 0; i < hexString.length; i += 4) {
        const hex = hexString.substr(i, 4);
        const charCode = parseInt(hex, 16);
        if (charCode > 0 && charCode < 65536) {
          decoded += String.fromCharCode(charCode);
        }
      }
      if (decoded) {
        decompressedText += '\n' + decoded;
        hexDecodeCount++;
        if (decoded.includes('{{') || decoded.includes('Signature')) {
          console.log('[extractPlaceholdersFromPdfBuffer] ✅ Texte hex décodé:', decoded.substring(0, 200));
        }
      }
    } catch (err) {
      // Ignore decoding errors
    }
  }
  
  console.log('[extractPlaceholdersFromPdfBuffer] Après décodage hex:', {
    hexDecodeCount,
    newTextLength: decompressedText.length,
    containsPlaceholder: decompressedText.includes('{{'),
    containsSignature: decompressedText.includes('Signature'),
  });
  
  const placeholders: PlaceholderMatch[] = [];

  // Chercher les placeholders dans le texte décodé
  PLACEHOLDER_REGEX.lastIndex = 0;
  let match: RegExpExecArray | null;
  while ((match = PLACEHOLDER_REGEX.exec(decompressedText)) !== null) {
    console.log('[extractPlaceholdersFromPdfBuffer] ✅ Match trouvé:', match[0]);
    placeholders.push({
      fullMatch: match[0],
      label: match[1].trim(),
      role: match[2].trim(),
      type: match[3].trim(),
      height: parseInt(match[4], 10),
      width: parseInt(match[5], 10),
      startIndex: match.index,
      endIndex: match.index + match[0].length,
    });
  }

  console.log('[extractPlaceholdersFromPdfBuffer] Total trouvé:', placeholders.length);
  return placeholders;
}

/**
 * Estimate reasonable positions (in PERCENTAGES) for placeholders when exact coordinates are unknown.
 * Places fields near the bottom margin, left/right by role.
 * MISE À JOUR : Retourne des POURCENTAGES au lieu de millimètres
 */
export function estimatePositionsFromPlaceholders(
  placeholders: PlaceholderMatch[],
  pageCount: number
): EstimatedPosition[] {
  const MARGIN_X_PERCENT = 9.5; // ~20mm sur 210mm = 9.5%
  const MARGIN_BOTTOM_PERCENT = 10; // ~30mm sur 297mm = 10%

  // Prefer placing on the last page by default
  const defaultPage = Math.max(1, pageCount);
  const SPACING_PERCENT = 5; // Espacement vertical entre signatures

  return placeholders.map((ph, index) => {
    // DocuSeal: width/height sont en pixels. Convertir en points (1px = 0.75pt) puis en %.
    // Hypothèse A4 en points (595 x 842)
    const A4_WIDTH_PT = 595;
    const A4_HEIGHT_PT = 842;
    const widthPt = (ph.width || 150) * 0.75;
    const heightPt = (ph.height || 60) * 0.75;
    const widthPercent = (widthPt / A4_WIDTH_PT) * 100;
    const heightPercent = (heightPt / A4_HEIGHT_PT) * 100;

    // Calculer Y en fonction de l'ordre (du haut vers le bas avec espacement)
    const baseY = 100 - MARGIN_BOTTOM_PERCENT - heightPercent;
    const yPercent = baseY - (index * (heightPercent + SPACING_PERCENT));

    // Role-based horizontal placement: employer left, employee right
  const roleLc = ph.role.toLowerCase();
    // IMPORTANT: Vérifier "employeur" AVANT "employe" car "employeur" contient "employe"
    const isEmployer = roleLc.includes('employeur');
    const isEmployee = !isEmployer && (roleLc.includes('salari') || roleLc.includes('employé') || roleLc.includes('employe'));

    const xPercent = isEmployee
      ? 100 - MARGIN_X_PERCENT - widthPercent // À droite pour salarié
      : MARGIN_X_PERCENT; // À gauche pour employeur

    return {
      role: ph.role,
      label: ph.label,
      page: defaultPage,
      x: xPercent,
      y: yPercent,
      width: widthPercent,
      height: heightPercent,
    };
  });
}

/**
 * Estimation améliorée basée sur le TEXTE complet extrait avec pdf-parse.
 * - Détermine la page à partir de l'index de ligne du placeholder
 * - Calcule Y en fonction de la ligne au sein de la page
 * - Convertit width/height px → pt → % (A4 par défaut)
 */
export function estimatePositionsFromPlaceholdersUsingText(
  placeholders: PlaceholderMatch[],
  text: string,
  pageCount: number
): EstimatedPosition[] {
  const lines = text.split(/\r?\n/);
  const totalLines = Math.max(1, lines.length);
  const A4_WIDTH_PT = 595;
  const A4_HEIGHT_PT = 842;

  // Détection explicite des débuts de page si le texte contient "Page X / N" en début de ligne
  const pageMarkers: { page: number; offset: number; lineIndex: number }[] = [];
  const pageHeaderRegex = /^Page\s+(\d+)\s*\/\s*(\d+)\s*$/m;
  // Construire les offsets de ligne et détecter les pages au passage
  const lineOffsets: number[] = new Array(totalLines);
  let offset = 0;
  for (let i = 0; i < totalLines; i++) {
    lineOffsets[i] = offset;
    const ln = lines[i];
    // Si une ligne ressemble à "Page X / N", enregistrer comme marqueur de page
    const m = ln.match(/^Page\s+(\d+)\s*\/\s*(\d+)\s*$/);
    if (m) {
      const p = parseInt(m[1], 10);
      pageMarkers.push({ page: p, offset, lineIndex: i });
    }
    // +1 pour le saut de ligne (approx)
    offset += ln.length + 1;
  }

  const findLineIndex = (pos: number): number => {
    // Recherche linéaire suffisante pour de petits documents; peut être optimisée via binaire
    let idx = 0;
    for (let i = 0; i < totalLines; i++) {
      if (lineOffsets[i] <= pos) idx = i; else break;
    }
    return idx;
  };

  const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(max, v));
  const TOP_MARGIN_PERCENT = 5;
  const BOTTOM_MARGIN_PERCENT = 5;
  const LEFT_MARGIN_PERCENT = 5;
  const RIGHT_MARGIN_PERCENT = 5;

  return placeholders.map((ph) => {
    const widthPt = (ph.width || 150) * 0.75;
    const heightPt = (ph.height || 60) * 0.75;
    const widthPercent = (widthPt / A4_WIDTH_PT) * 100;
    const heightPercent = (heightPt / A4_HEIGHT_PT) * 100;

    // Localiser la première occurrence du placeholder dans le texte global
    let idx = text.indexOf(ph.fullMatch);
    if (idx === -1) idx = ph.startIndex;

    const lineIdx = findLineIndex(idx);
    // Déterminer la page soit via marqueurs "Page X / N", soit via découpage uniforme
    let page: number;
    if (pageMarkers.length > 0) {
      // Trouver le dernier marqueur dont l'offset <= idx
      let chosen = pageMarkers[0];
      for (const pm of pageMarkers) {
        if (pm.offset <= idx) chosen = pm; else break;
      }
      page = chosen.page;
    } else {
      const linesPerPage = Math.max(1, Math.floor(totalLines / Math.max(1, pageCount)));
      page = Math.floor(lineIdx / linesPerPage) + 1;
    }
    page = clamp(page, 1, Math.max(1, pageCount));

    // Calcul du relInPage :
    let relInPage: number;
    if (pageMarkers.length > 0) {
      // Limites de page via marqueurs
      const thisMarkerIdx = pageMarkers.findIndex((pm) => pm.page === page);
      const startLine = thisMarkerIdx >= 0 ? pageMarkers[thisMarkerIdx].lineIndex : 0;
      const endLine = thisMarkerIdx + 1 < pageMarkers.length ? pageMarkers[thisMarkerIdx + 1].lineIndex : totalLines - 1;
      const span = Math.max(1, endLine - startLine);
      const within = clamp(lineIdx - startLine, 0, span);
      relInPage = within / span;
    } else {
      const linesPerPage = Math.max(1, Math.floor(totalLines / Math.max(1, pageCount)));
      const firstLineOfPage = (page - 1) * linesPerPage;
      const withinPageLine = Math.max(0, lineIdx - firstLineOfPage);
      relInPage = withinPageLine / linesPerPage; // 0 en haut, 1 en bas
    }

    const usableHeight = 100 - TOP_MARGIN_PERCENT - BOTTOM_MARGIN_PERCENT - heightPercent;
    const yPercent = clamp(TOP_MARGIN_PERCENT + relInPage * usableHeight, 0, 100 - heightPercent);

    // X basé sur la position du texte dans la ligne
    const posInLine = Math.max(0, idx - lineOffsets[lineIdx]);
    const lineLen = Math.max(1, lines[lineIdx]?.length || 1);
    const usableWidth = Math.max(0, 100 - LEFT_MARGIN_PERCENT - RIGHT_MARGIN_PERCENT - widthPercent);
    const xPercent = clamp(LEFT_MARGIN_PERCENT + (posInLine / lineLen) * usableWidth, 0, 100 - widthPercent);

    return {
      role: ph.role,
      label: ph.label,
      page,
      x: xPercent,
      y: yPercent,
      width: widthPercent,
      height: heightPercent,
    };
  });
}

function bufferToLatin1String(bytes: Uint8Array | Buffer): string {
  if (typeof Buffer !== 'undefined' && (bytes as Buffer).toString) {
    return (bytes as Buffer).toString('latin1');
  }
  // Fallback for environments without Node Buffer
  let result = '';
  const chunk = 8192;
  for (let i = 0; i < bytes.length; i += chunk) {
    const slice = bytes.slice(i, i + chunk);
    result += Array.from(slice as any, (b: number) => String.fromCharCode(b)).join('');
  }
  return result;
}

/**
 * Extraction précise des positions de placeholders avec pdf-lib
 * Cette fonction extrait les positions exactes des placeholders dans le PDF
 * en analysant le contenu textuel de chaque page.
 * 
 * RETOURNE LES POSITIONS EN POURCENTAGES pour être indépendant de la résolution
 */
export async function extractPrecisePositionsFromPdf(
  pdfBytes: Uint8Array | Buffer
): Promise<ExtractedPosition[]> {
  try {
    const pdfDoc = await PDFDocument.load(pdfBytes);
    const pages = pdfDoc.getPages();
    const positions: ExtractedPosition[] = [];

    for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
      const page = pages[pageIndex];
      const { width: pageWidthPt, height: pageHeightPt } = page.getSize();
      
      // Extraire le texte de la page
      const pageContent = await extractPageTextContent(pdfDoc, pageIndex);
      
      console.log(`[PLACEHOLDER] Page ${pageIndex + 1} - Contenu extrait:`, {
        hasContent: !!pageContent,
        contentLength: pageContent?.length || 0,
        contentPreview: pageContent?.substring(0, 200),
      });
      
      if (!pageContent) {
        console.warn(`[PLACEHOLDER] Page ${pageIndex + 1} - Aucun contenu extrait`);
        continue;
      }

      // Chercher les placeholders dans le contenu de la page
      PLACEHOLDER_REGEX.lastIndex = 0;
      let match: RegExpExecArray | null;
      
      console.log(`[PLACEHOLDER] Page ${pageIndex + 1} - Recherche de placeholders avec regex:`, PLACEHOLDER_REGEX);
      
      let matchCount = 0;
      while ((match = PLACEHOLDER_REGEX.exec(pageContent)) !== null) {
        matchCount++;
        const label = match[1].trim();
        const role = match[2].trim();
        const type = match[3].trim();
        // DocuSeal dimensions en pixels
        const heightPx = parseInt(match[4], 10);
        const widthPx = parseInt(match[5], 10);
        const fullText = match[0];

        // Chercher la position du texte dans la page
        const textPosition = findTextPositionInPage(pageContent, fullText);
        
        if (textPosition) {
          // Convertir la position PDF (points, origine bas-gauche) en % (origine haut-gauche)
          const xPercent = (textPosition.x / pageWidthPt) * 100;
          const yPercent = ((pageHeightPt - textPosition.y) / pageHeightPt) * 100;

          // Convertir dimensions px -> pt puis en %
          const widthPt = widthPx * 0.75;
          const heightPt = heightPx * 0.75;
          const widthPercent = (widthPt / pageWidthPt) * 100;
          const heightPercent = (heightPt / pageHeightPt) * 100;

          positions.push({
            role,
            label,
            page: pageIndex + 1, // 1-indexed
            x: xPercent,
            y: yPercent,
            width: widthPercent,
            height: heightPercent,
            text: fullText,
          });

          console.log(`[PLACEHOLDER] Trouvé sur page ${pageIndex + 1}: ${label} (${role})`);
          console.log(`  Position: x=${xPercent.toFixed(1)}%, y=${yPercent.toFixed(1)}%, w=${widthPercent.toFixed(1)}%, h=${heightPercent.toFixed(1)}%`);
        } else {
          console.warn(`[PLACEHOLDER] Position non trouvée pour: ${label} sur page ${pageIndex + 1}`);
        }
      }
      
      if (matchCount === 0) {
        console.log(`[PLACEHOLDER] Page ${pageIndex + 1} - Aucun placeholder trouvé dans le contenu`);
      } else {
        console.log(`[PLACEHOLDER] Page ${pageIndex + 1} - ${matchCount} placeholder(s) trouvé(s)`);
      }
    }

    console.log(`[PLACEHOLDER] Total trouvé: ${positions.length} positions précises`);
    return positions;

  } catch (error) {
    console.error('[PLACEHOLDER] Erreur lors de l\'extraction précise:', error);
    // Fallback sur l'ancienne méthode
    return [];
  }
}

/**
 * Extrait le contenu textuel brut d'une page PDF en décompressant les streams
 */
async function extractPageTextContent(pdfDoc: PDFDocument, pageIndex: number): Promise<string | null> {
  try {
    const page = pdfDoc.getPages()[pageIndex];
    const { Contents } = page.node.normalizedEntries();
    
    if (!Contents) {
      console.log(`[extractPageTextContent] Page ${pageIndex}: Pas de Contents`);
      return null;
    }

    let contentStream: any;
    
    // Contents peut être un array ou un objet unique
    if (Array.isArray(Contents)) {
      // Combiner tous les streams
      let combined = '';
      for (const ref of Contents) {
        const stream = pdfDoc.context.lookup(ref);
        if (stream && (stream as any).contents) {
          combined += new TextDecoder('utf-8', { fatal: false }).decode((stream as any).contents);
        }
      }
      return combined || null;
    } else {
      // Stream unique
      contentStream = pdfDoc.context.lookup(Contents);
      if (contentStream && (contentStream as any).contents) {
        const text = new TextDecoder('utf-8', { fatal: false }).decode((contentStream as any).contents);
        console.log(`[extractPageTextContent] Page ${pageIndex}: ${text.length} caractères extraits`);
        return text;
      }
    }

    return null;
  } catch (error) {
    console.error(`[PLACEHOLDER] Erreur extraction contenu page ${pageIndex}:`, error);
    return null;
  }
}

/**
 * Trouve la position d'un texte dans le contenu d'une page PDF
 * en analysant les opérateurs de positionnement de texte (Tm, Td, etc.)
 */
function findTextPositionInPage(pageContent: string, searchText: string): { x: number; y: number } | null {
  try {
    // D'abord chercher le texte exact
    let textIndex = pageContent.indexOf(searchText);
    
    // Si non trouvé, essayer de chercher une version échappée ou encodée
    if (textIndex === -1) {
      // Parfois les accolades sont encodées ou le texte est découpé
      const simplifiedSearch = searchText.replace(/[{}]/g, '');
      textIndex = pageContent.indexOf(simplifiedSearch);
    }
    
    if (textIndex === -1) {
      console.warn('[PLACEHOLDER] Texte non trouvé dans le contenu de la page:', searchText.substring(0, 50));
      return null;
    }

    // Remonter dans le contenu pour trouver le dernier opérateur de positionnement
    const beforeText = pageContent.substring(Math.max(0, textIndex - 2000), textIndex);
    
    // Chercher le dernier opérateur Tm (matrice de transformation de texte)
    // Format: a b c d e f Tm où e et f sont les coordonnées x et y
    const tmMatches = beforeText.matchAll(/([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+Tm/g);
    const tmArray = Array.from(tmMatches);
    
    if (tmArray.length > 0) {
      const lastTm = tmArray[tmArray.length - 1];
      const x = parseFloat(lastTm[5]); // Position X (paramètre e)
      const y = parseFloat(lastTm[6]); // Position Y (paramètre f)
      
      console.log('[PLACEHOLDER] Position trouvée via Tm:', { x, y, text: searchText.substring(0, 30) });
      return { x, y };
    }

    // Fallback: chercher l'opérateur Td (déplacement de texte)
    const tdMatches = beforeText.matchAll(/([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+Td/g);
    const tdArray = Array.from(tdMatches);
    
    if (tdArray.length > 0) {
      const lastTd = tdArray[tdArray.length - 1];
      const x = parseFloat(lastTd[1]);
      const y = parseFloat(lastTd[2]);
      
      console.log('[PLACEHOLDER] Position trouvée via Td:', { x, y, text: searchText.substring(0, 30) });
      return { x, y };
    }

    // Dernier fallback: chercher BT (début de bloc texte) avec position
    const btMatch = beforeText.match(/BT[\s\S]*?([+-]?\d+\.?\d*)\s+([+-]?\d+\.?\d*)\s+Td/);
    if (btMatch) {
      const x = parseFloat(btMatch[1]);
      const y = parseFloat(btMatch[2]);
      
      console.log('[PLACEHOLDER] Position trouvée via BT+Td:', { x, y, text: searchText.substring(0, 30) });
      return { x, y };
    }

    console.warn('[PLACEHOLDER] Aucun opérateur de position trouvé pour:', searchText.substring(0, 50));
    return null;

  } catch (error) {
    console.error('[PLACEHOLDER] Erreur recherche position texte:', error);
    return null;
  }
}

/**
 * Extraction de placeholders avec Pdfium (comme DocuSeal)
 * Cette méthode utilise Pdfium pour extraire le texte des PDFs Chromium
 * RETOURNE LES POSITIONS EN POURCENTAGES
 */
export async function extractPlaceholdersWithPdfium(
  pdfBuffer: Buffer | Uint8Array
): Promise<ExtractedPosition[]> {
  let library: any = null;
  let document: any = null;

  try {
    console.log('[PDFIUM] Initialisation de Pdfium...');
    
    // Initialiser la bibliothèque Pdfium
    library = await PDFiumLibrary.init();
    
    // Charger le document depuis le buffer
    document = await library.loadDocument(pdfBuffer);
    const pageCount = document.getPageCount();
    
    console.log(`[PDFIUM] Document chargé: ${pageCount} page(s)`);

    const positions: ExtractedPosition[] = [];

    // Parcourir toutes les pages
    for (const page of document.pages()) {
      console.log(`[PDFIUM] Extraction texte page ${page.number}...`);
      
      // Extraire le texte de la page avec Pdfium
      const pageText = page.getText();
      
      console.log(`[PDFIUM] Page ${page.number} - Texte extrait:`, {
        length: pageText.length,
        preview: pageText.substring(0, 200),
        containsPlaceholder: pageText.includes('{{'),
      });

  // Obtenir les dimensions de la page en points (1/72 inch)
  const { width: pageWidthPt, height: pageHeightPt } = page.getSize();

      // Chercher les placeholders dans le texte extrait
      PLACEHOLDER_REGEX.lastIndex = 0;
      let match: RegExpExecArray | null;
      let matchCount = 0;
      const pageMatches: Array<{
        label: string;
        role: string;
        type: string;
        heightPx: number;
        widthPx: number;
        fullText: string;
        textIndex: number;
      }> = [];

      // Collecter tous les matches de la page
      while ((match = PLACEHOLDER_REGEX.exec(pageText)) !== null) {
        matchCount++;
        const label = match[1].trim();
        const role = match[2].trim();
    const type = match[3].trim();
    // DocuSeal dimensions en pixels
    const heightPx = parseInt(match[4], 10);
    const widthPx = parseInt(match[5], 10);
        const fullText = match[0];
        const textIndex = match.index;

        console.log(`[PDFIUM] Placeholder trouvé: ${label} (${role}) à index ${textIndex}`);

        pageMatches.push({
          label,
          role,
          type,
          heightPx,
          widthPx,
          fullText,
          textIndex,
        });
      }

      // Trier par ordre d'apparition dans le texte
      pageMatches.sort((a, b) => a.textIndex - b.textIndex);

      // Convertir en positions avec estimation basée sur le TEXTE (meilleure que l'ordre seul)
      // Approche: approximer la ligne du placeholder à partir de textIndex puis convertir en pourcentage vertical
      const rawLines = pageText.split(/\r?\n/);
      const lineStarts: number[] = [];
      let acc = 0;
      rawLines.forEach((ln: string) => {
        lineStarts.push(acc);
        acc += ln.length + 1; // +1 pour le saut de ligne
      });

      const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(max, v));
  const LEFT_MARGIN_PERCENT = 5;
  const RIGHT_MARGIN_PERCENT = 5;
      const TOP_MARGIN_PERCENT = 5;
      const BOTTOM_MARGIN_PERCENT = 5;

      pageMatches.forEach((pm, index) => {
        // Convertir px -> points (1px = 0.75pt) puis en %
        const widthPt = pm.widthPx * 0.75;
        const heightPt = pm.heightPx * 0.75;
        const widthPercent = (widthPt / pageWidthPt) * 100;
        const heightPercent = (heightPt / pageHeightPt) * 100;

        // Trouver la ligne du texte la plus proche du textIndex
        let lineIdx = 0;
        for (let i = 0; i < lineStarts.length; i++) {
          if (lineStarts[i] <= pm.textIndex) lineIdx = i; else break;
        }
        const totalLines = Math.max(1, rawLines.length);
        const relLine = lineIdx / totalLines; // 0 en haut, 1 en bas

        // Y: mapper la ligne au pourcentage vertical (avec marges et en tenant compte de la hauteur de l'overlay)
        const usableHeight = 100 - TOP_MARGIN_PERCENT - BOTTOM_MARGIN_PERCENT - heightPercent;
        const yPercent = clamp(TOP_MARGIN_PERCENT + relLine * usableHeight, 0, 100 - heightPercent);

        // X: basé sur la position dans la ligne (début du placeholder)
        const posInLine = Math.max(0, pm.textIndex - (lineStarts[lineIdx] ?? 0));
        const lineLen = Math.max(1, rawLines[lineIdx]?.length || 1);
        const usableWidth = Math.max(0, 100 - LEFT_MARGIN_PERCENT - RIGHT_MARGIN_PERCENT - widthPercent);
        const xPercent = clamp(LEFT_MARGIN_PERCENT + (posInLine / lineLen) * usableWidth, 0, 100 - widthPercent);

        positions.push({
          role: pm.role,
          label: pm.label,
          page: page.number,
          x: xPercent,
          y: yPercent,
          width: widthPercent,
          height: heightPercent,
          text: pm.fullText,
        });

        console.log(`[PDFIUM] Position calculée (texte) #${index + 1}: x=${xPercent.toFixed(1)}%, y=${yPercent.toFixed(1)}% (line ${lineIdx + 1}/${totalLines})`);
      });

      console.log(`[PDFIUM] Page ${page.number} - ${matchCount} placeholder(s) trouvé(s)`);
    }

    console.log(`[PDFIUM] Total trouvé: ${positions.length} placeholder(s)`);
    return positions;

  } catch (error) {
    console.error('[PDFIUM] Erreur lors de l\'extraction:', error);
    return [];
  } finally {
    // Libérer la mémoire
    if (document) {
      try {
        document.destroy();
        console.log('[PDFIUM] Document détruit');
      } catch (e) {
        console.error('[PDFIUM] Erreur destruction document:', e);
      }
    }
    if (library) {
      try {
        library.destroy();
        console.log('[PDFIUM] Bibliothèque détruite');
      } catch (e) {
        console.error('[PDFIUM] Erreur destruction bibliothèque:', e);
      }
    }
  }
}
