import {
  useRef,
  useEffect,
  useCallback,
  useState,
  useMemo,
  RefObject,
  MouseEvent,
} from "react";
import { useBuilder } from "../../contexts/builder/BuilderContext";
import {
  useCanvasSettings,
  DEFAULT_SETTINGS,
} from "../../contexts/CanvasSettingsContext";
import { useCanvasSetting } from "../../hooks/useCanvasSettings";
import { useCanvasDrop } from "../../hooks/useCanvasDrop";
import { useCanvasInteraction } from "../../hooks/useCanvasInteraction";
import { useKeyboardShortcuts } from "../../hooks/useKeyboardShortcuts";
import { getEditorFeatureFlags, getPdfBuilderData } from "../../utils/editorFeatures";
import {
  Element,
  BaseElement,
  CompanyInfoElementProperties,
  DynamicTextElement,
  DocumentTypeElement,
  RectangleElement,
  CircleElement,
  TextElement,
  LineElement,
  ImageElement,
  ProductTableElement,
  CustomerInfoElement,
  CompanyInfoElement,
  WoocommerceOrderDateElement,
  WoocommerceInvoiceNumberElement,
  CompanyLogoElement,
  MentionsElement,
  BuilderState,
} from "../../types/elements";
import { wooCommerceManager } from "../../utils/WooCommerceElementsManager";
import { debugWarn, debugError, debugLog } from "../../utils/debug";

// Fonctions utilitaires pour la gestion mémoire des images
const estimateImageMemorySize = (img: HTMLImageElement): number => {
  // Estimation basée sur les dimensions et le nombre de canaux (RGBA = 4 octets par pixel)
  const bytesPerPixel = 4;
  return img.naturalWidth * img.naturalHeight * bytesPerPixel;
};

// Fonction utilitaire pour dessiner des rectangles avec coins arrondis
const roundedRect = (
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  width: number,
  height: number,
  radius: number,
) => {
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.lineTo(x + width - radius, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  ctx.lineTo(x + width, y + height - radius);
  ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  ctx.lineTo(x + radius, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  ctx.lineTo(x, y + radius);
  ctx.quadraticCurveTo(x, y, x + radius, y);
  ctx.closePath();
};

const cleanupImageCache = (
  imageCache: RefObject<
    Map<string, { image: HTMLImageElement; size: number; lastUsed: number }>
  >,
) => {
  const cache = imageCache.current;
  if (cache.size <= 100) return; // Max 100 images

  // Trier par date d'utilisation et supprimer les plus anciennes
  const entries = Array.from(cache.entries()).sort(
    ([, a], [, b]) => a.lastUsed - b.lastUsed,
  );
  const toRemove = entries.slice(0, Math.ceil(cache.size * 0.2)); // Supprimer 20%

  toRemove.forEach(([url]) => cache.delete(url));
};
import { CanvasMonitoringDashboard } from "../../utils/CanvasMonitoringDashboard";
import { ContextMenu, ContextMenuItem } from "../ui/ContextMenu";

// Fonctions utilitaires de dessin (déplacées en dehors du composant pour éviter les avertissements React Compiler)

// Fonction pour normaliser les couleurs (simple identité pour le web)
const normalizeColor = (color: string): string => color;

// Fonction pour normaliser les poids de police
const normalizeFontWeight = (weight: string | number): string => {
  if (typeof weight === "number") return weight.toString();
  if (weight === "bold") return "700";
  if (weight === "normal") return "400";
  return weight;
};

// Constantes communes pour les valeurs par défaut
const DEFAULT_FONT = {
  family: "Arial",
  size: 12,
  weight: "normal",
  style: "normal",
} as const;

const DEFAULT_COLORS = {
  background: "#ffffff",
  border: "#000000",
  text: "#000000",
} as const;

// Fonction helper pour configurer les polices
const createFontConfig = (
  props: any,
  baseSize: number = DEFAULT_FONT.size,
) => ({
  family: props.fontFamily || DEFAULT_FONT.family,
  size: props.fontSize || baseSize,
  weight: props.fontWeight || DEFAULT_FONT.weight,
  style: props.fontStyle || DEFAULT_FONT.style,
});

// Fonction helper pour configurer les couleurs avec normalisation
const createColorConfig = (
  props: any,
  defaults?: { background?: string; border?: string; text?: string },
) => {
  const defaultValues = defaults || DEFAULT_COLORS;
  return {
    background: normalizeColor(
      props.backgroundColor || defaultValues.background,
    ),
    border: normalizeColor(props.borderColor || defaultValues.border),
    text: normalizeColor(props.textColor || defaultValues.text),
  };
};

// Fonction helper pour configurer le padding
const getPadding = (props: any) => ({
  top: props.padding?.top || props.paddingTop || 0,
  right: props.padding?.right || props.paddingRight || 0,
  bottom: props.padding?.bottom || props.paddingBottom || 0,
  left: props.padding?.left || props.paddingLeft || 0,
});

// Fonction helper pour calculer la position X selon l'alignement
const calculateTextX = (
  element: Element,
  textAlign: string | undefined,
  padding: ReturnType<typeof getPadding>,
) => {
  switch (textAlign) {
    case "center":
      return element.width / 2;
    case "right":
      return element.width - padding.right;
    default:
      return padding.left;
  }
};

// Fonction helper pour appliquer les bordures avec style
const applyBorder = (
  ctx: CanvasRenderingContext2D,
  element: Element,
  borderProps: any,
) => {
  if (!borderProps || borderProps.width <= 0) return;

  ctx.strokeStyle = borderProps.color || "#000000";
  ctx.lineWidth = borderProps.width;

  // Appliquer le style de ligne
  switch (borderProps.style) {
    case "dashed":
      ctx.setLineDash([5, 5]);
      break;
    case "dotted":
      ctx.setLineDash([2, 2]);
      break;
    default:
      ctx.setLineDash([]);
  }

  ctx.strokeRect(0, 0, element.width, element.height);
  ctx.setLineDash([]); // Reset
};

// Fonction helper pour configurer le contexte de rendu de base
const setupRenderContext = (
  ctx: CanvasRenderingContext2D,
  fontConfig: ReturnType<typeof createFontConfig>,
  colorConfig: ReturnType<typeof createColorConfig>,
  textAlign: string = "left",
  verticalAlign: string = "top",
) => {
  ctx.fillStyle = colorConfig.text;
  ctx.font = `${fontConfig.style} ${fontConfig.weight} ${fontConfig.size}px ${fontConfig.family}`;
  ctx.textAlign = textAlign as CanvasTextAlign;

  // Définir le textBaseline selon l'alignement vertical
  switch (verticalAlign) {
    case "middle":
      ctx.textBaseline = "middle";
      break;
    case "bottom":
      ctx.textBaseline = "bottom";
      break;
    default: // top
      ctx.textBaseline = "top";
  }
};

// Fonction helper pour configurer les couleurs des shapes
const createShapeColors = (props: any) => ({
  background: normalizeColor(
    props.backgroundColor || props.fillColor || "#ffffff",
  ),
  border: normalizeColor(props.borderColor || props.strokeColor || "#000000"),
});

// Fonction helper pour appliquer les couleurs et styles de shape
const applyShapeStyle = (
  ctx: CanvasRenderingContext2D,
  colors: ReturnType<typeof createShapeColors>,
  borderWidth: number = 1,
) => {
  ctx.fillStyle = colors.background;
  ctx.strokeStyle = colors.border;
  ctx.lineWidth = borderWidth;
};

// Fonction helper pour calculer la position X selon l'alignement du texte
// Fonction helper pour normaliser padding (number ou object) en nombre
const normalizePaddingToNumber = (
  padding:
    | number
    | { top?: number; right?: number; bottom?: number; left?: number }
    | undefined,
  defaultValue: number = 12,
): number => {
  if (padding === undefined || padding === null) return defaultValue;
  if (typeof padding === "number") return padding;
  if (typeof padding === "object") {
    // Si c'est un objet, retourner la valeur moyenne ou la première disponible
    const values = [
      padding.top,
      padding.right,
      padding.bottom,
      padding.left,
    ].filter((v): v is number => v !== undefined);
    return values.length > 0 ? Math.min(...values) : defaultValue;
  }
  return defaultValue;
};

// Fonction helper pour normaliser l'opacité (cohérent avec PHP)
// Si opacity > 1, c'est un pourcentage (0-100), sinon c'est déjà 0-1
const normalizeOpacity = (opacity: number | undefined): number => {
  if (opacity === undefined || opacity === null) return 1;
  if (opacity > 1) {
    return opacity / 100; // Convertir pourcentage en décimal
  }
  return opacity;
};

const calculateTextAlignX = (
  element: Element,
  align: string = "left",
  padding: number = 0,
) => {
  switch (align) {
    case "center":
      return element.width / 2;
    case "right":
      return element.width - padding;
    default:
      return padding;
  }
};

// Fonction helper pour calculer la position Y selon l'alignement vertical
const calculateTextY = (
  element: Element,
  verticalAlign: string = "top",
  fontSize: number = 12,
  padding: number = 0,
) => {
  switch (verticalAlign) {
    case "middle":
      return element.height / 2;
    case "bottom":
      return element.height - padding;
    default: // top
      return padding || fontSize;
  }
};

// Fonction helper pour calculer Y avec padding
const calculateTextYWithPadding = (
  element: Element,
  paddingConfig: ReturnType<typeof getPadding>,
  verticalAlign: string = "top",
  fontSize: number = 0,
) => {
  const centerY = element.height / 2;

  switch (verticalAlign) {
    case "middle":
      // Pour un groupe ou un seul élément, centrer en tenant compte de la hauteur
      if (fontSize > 0) {
        // Pour un groupe (label+date), centrer le groupe entier
        return centerY - fontSize / 2;
      }
      // Pour un seul élément sans hauteur spécifiée
      return centerY;
    case "bottom":
      // Positionner le bas du texte ou du groupe (textBaseline: "top" donc soustraire fontSize)
      if (fontSize > 0) {
        // Avec fontSize: s'assurer que le bas du texte/groupe reste visible
        return element.height - paddingConfig.bottom - fontSize;
      }
      // Sans fontSize: positionner au bas (peut sortir du conteneur)
      return element.height - paddingConfig.bottom;
    default: // top
      // Toujours positionner en haut, peu importe fontSize
      return paddingConfig.top || 10;
  }
};

// ✅ Nouvelle fonction pour estimer les dimensions minimales basées sur le contenu
const _calculateMinDimensions = (
  element: Element,
): { minWidth: number; minHeight: number } => {
  const defaultMinWidth = 40; // Minimum très réduit
  const defaultMinHeight = 20;

  switch (element.type) {
    // Éléments texte -> basé sur la longueur du texte
    case "text":
    case "dynamic_text": {
      const textEl = element as TextElement;
      const fontSize = textEl.fontSize || 12;
      const text = textEl.text || "Text";

      // Estimer la hauteur avec interlignage
      const lines = text.split("\n").length;
      const lineHeight = fontSize + fontSize * 0.4; // 40% d'interlignage
      const estimatedHeight = Math.ceil(lines * lineHeight + 8); // 8px padding

      // Estimer la largeur
      const avgCharWidth = fontSize * 0.6; // Approximation basée sur la police
      const longestLine = text
        .split("\n")
        .reduce((max, line) => Math.max(max, line.length), 0);
      const estimatedWidth = Math.ceil(
        Math.max(longestLine * avgCharWidth + 16, 40),
      );

      return {
        minWidth: Math.max(estimatedWidth, 40),
        minHeight: Math.max(estimatedHeight, 20),
      };
    }

    case "document_type": {
      const docEl = element as DocumentTypeElement; // NOSONAR — assertion requise pour accéder aux props spécifiques
      const fontSize = docEl.fontSize || 18;
      const text = docEl.documentType || "DOCUMENT";
      const estimatedWidth = Math.ceil(text.length * fontSize * 0.5 + 20);
      return {
        minWidth: Math.max(estimatedWidth, 80),
        minHeight: Math.max(fontSize + 12, 30),
      };
    }

    // Images et formes carrées -> minimum carré
    case "company_logo":
    case "image":
    case "rectangle":
    case "circle":
      return {
        minWidth: 40,
        minHeight: 40,
      };

    // Lignes -> largeur minimale importante, hauteur faible
    case "line":
      return {
        minWidth: 50,
        minHeight: 2,
      };

    // Tables et infos entreprise -> même taille minimale
    case "product_table":
    case "company_info":
      return {
        minWidth: 150,
        minHeight: 80,
      };

    // Infos clientèle -> bloc de texte
    case "customer_info":
      return {
        minWidth: 120,
        minHeight: 50,
      };

    // Défaut
    default:
      return {
        minWidth: defaultMinWidth,
        minHeight: defaultMinHeight,
      };
  }
};

// ✅ Fonction pour ajouter un chemin de clipping (limite le contenu à la boîte de l'élément)
const applyClippingPath = (ctx: CanvasRenderingContext2D, element: Element) => {
  ctx.beginPath();

  // Créer un rectangle de clipping avec arrondi optionnel
  const borderRadius = (element as any).borderRadius || 0;

  if (borderRadius && borderRadius > 0) {
    // Chemin avec coins arrondis
    const radius = Math.min(
      borderRadius,
      element.width / 2,
      element.height / 2,
    );
    ctx.moveTo(radius, 0);
    ctx.lineTo(element.width - radius, 0);
    ctx.quadraticCurveTo(element.width, 0, element.width, radius);
    ctx.lineTo(element.width, element.height - radius);
    ctx.quadraticCurveTo(
      element.width,
      element.height,
      element.width - radius,
      element.height,
    );
    ctx.lineTo(radius, element.height);
    ctx.quadraticCurveTo(0, element.height, 0, element.height - radius);
    ctx.lineTo(0, radius);
    ctx.quadraticCurveTo(0, 0, radius, 0);
  } else {
    // Rectangle simple
    ctx.rect(0, 0, element.width, element.height);
  }

  ctx.clip();
};

const drawRectangle = (ctx: CanvasRenderingContext2D, element: Element) => {
  const props = element as RectangleElement;
  const colors = createShapeColors(props);

  applyShapeStyle(ctx, colors, props.borderWidth);

  if (props.borderRadius && props.borderRadius > 0) {
    roundedRect(ctx, 0, 0, element.width, element.height, props.borderRadius);
    ctx.fill();
    ctx.stroke();
  } else {
    ctx.fillRect(0, 0, element.width, element.height);
    ctx.strokeRect(0, 0, element.width, element.height);
  }
};

const drawCircle = (ctx: CanvasRenderingContext2D, element: Element) => {
  const props = element as CircleElement;
  const colors = createShapeColors(props);

  applyShapeStyle(ctx, colors, props.borderWidth);

  const centerX = element.width / 2;
  const centerY = element.height / 2;
  const radius = Math.min(centerX, centerY);

  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
  ctx.fill();
  ctx.stroke();
};

const drawText = (ctx: CanvasRenderingContext2D, element: Element) => { // NOSONAR — complexité inhérente au rendu de texte
  const props = element as TextElement;
  const fontConfig = createFontConfig(props, 16);
  const colorConfig = createColorConfig(props);

  setupRenderContext(
    ctx,
    fontConfig,
    colorConfig,
    props.textAlign,
    props.verticalAlign,
  );

  // ✅ NEW: Ajouter du padding
  const padding = normalizePaddingToNumber(props.padding, 12);

  const x = calculateTextAlignX(element, props.textAlign, padding);
  const y = calculateTextY(
    element,
    props.verticalAlign,
    fontConfig.size,
    padding,
  );

  const text = props.text || "Text";

  // Gérer les lignes séparées par \n
  const lines = text.split("\n");
  let currentY = y;

  lines.forEach((line: string, index: number) => {
    ctx.fillText(line, x, currentY);

    // Espacement entre les lignes
    if (index < lines.length - 1) {
      currentY += fontConfig.size * 1.1;
    }
  });
};

const drawLine = (ctx: CanvasRenderingContext2D, element: Element) => {
  const props = element as LineElement;
  const colors = createShapeColors(props);

  ctx.strokeStyle = colors.border;
  ctx.lineWidth = props.borderWidth || 2;

  ctx.beginPath();
  ctx.moveTo(0, element.height / 2);
  ctx.lineTo(element.width, element.height / 2);
  ctx.stroke();
};

// Fonction pour dessiner une image avec gestion optimisée du cache
const drawImage = (
  ctx: CanvasRenderingContext2D,
  element: Element,
  imageCache: RefObject<
    Map<string, { image: HTMLImageElement; size: number; lastUsed: number }>
  >,
) => {
  const props = element as ImageElement;
  const imageUrl = props.src || "";

  if (!imageUrl) {
    // Placeholder pour image manquante
    drawImagePlaceholder(ctx, element);
    return;
  }

  const cachedImage = imageCache.current.get(imageUrl);
  if (!cachedImage) {
    loadAndCacheImage(imageUrl, imageCache);
    drawImagePlaceholder(ctx, element);
    return;
  }

  // Mettre à jour la date d'utilisation
  cachedImage.lastUsed = Date.now();

  const img = cachedImage.image;
  if (img.complete && img.naturalHeight !== 0) {
    drawImageWithObjectFit(ctx, img, element, props.fit || "cover");
  } else {
    drawImagePlaceholder(ctx, element);
  }
};

// Fonction helper pour dessiner un placeholder d'image
const drawImagePlaceholder = (
  ctx: CanvasRenderingContext2D,
  element: Element,
) => {
  const colors = createColorConfig(
    {},
    { background: "#f0f0f0", border: "#cccccc", text: "#999999" },
  );

  ctx.fillStyle = colors.background;
  ctx.fillRect(0, 0, element.width, element.height);
  ctx.strokeStyle = colors.border;
  ctx.lineWidth = 1;
  ctx.strokeRect(0, 0, element.width, element.height);

  ctx.fillStyle = colors.text;
  ctx.font = "14px Arial";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText("Image", element.width / 2, element.height / 2);
};

// Fonction helper pour charger et mettre en cache une image
const loadAndCacheImage = (
  imageUrl: string,
  imageCache: RefObject<
    Map<string, { image: HTMLImageElement; size: number; lastUsed: number }>
  >,
) => {
  const img = document.createElement("img");
  img.crossOrigin = "anonymous";
  img.src = imageUrl;

  img.onload = () => {
    const size = estimateImageMemorySize(img);
    imageCache.current.set(imageUrl, {
      image: img,
      size,
      lastUsed: Date.now(),
    });
    cleanupImageCache(imageCache);
  };

  img.onerror = () => {
    debugWarn(`[Canvas] Failed to load image: ${imageUrl}`);
  };
};

// Fonction helper pour dessiner une image avec object-fit
const drawImageWithObjectFit = (
  ctx: CanvasRenderingContext2D,
  img: HTMLImageElement,
  element: Element,
  objectFit: string,
) => {
  let drawX = 0,
    drawY = 0,
    drawWidth = element.width,
    drawHeight = element.height;
  let sourceX = 0,
    sourceY = 0,
    sourceWidth = img.naturalWidth,
    sourceHeight = img.naturalHeight;

  switch (objectFit) {
    case "contain": {
      const containRatio = Math.min(
        element.width / img.naturalWidth,
        element.height / img.naturalHeight,
      );
      drawWidth = img.naturalWidth * containRatio;
      drawHeight = img.naturalHeight * containRatio;
      drawX = (element.width - drawWidth) / 2;
      drawY = (element.height - drawHeight) / 2;
      break;
    }

    case "cover": {
      const coverRatio = Math.max(
        element.width / img.naturalWidth,
        element.height / img.naturalHeight,
      );
      sourceWidth = element.width / coverRatio;
      sourceHeight = element.height / coverRatio;
      sourceX = (img.naturalWidth - sourceWidth) / 2;
      sourceY = (img.naturalHeight - sourceHeight) / 2;
      break;
    }

    case "scale-down":
      if (
        img.naturalWidth > element.width ||
        img.naturalHeight > element.height
      ) {
        const scaleDownRatio = Math.min(
          element.width / img.naturalWidth,
          element.height / img.naturalHeight,
        );
        drawWidth = img.naturalWidth * scaleDownRatio;
        drawHeight = img.naturalHeight * scaleDownRatio;
        drawX = (element.width - drawWidth) / 2;
        drawY = (element.height - drawHeight) / 2;
      }
      break;
    // "fill" utilise les dimensions par défaut
  }

  ctx.drawImage(
    img,
    sourceX,
    sourceY,
    sourceWidth,
    sourceHeight,
    drawX,
    drawY,
    drawWidth,
    drawHeight,
  );
};

// ─── Helpers de rendu partagés ────────────────────────────────────────────────

type TextAlignH = "left" | "center" | "right";

/** Retourne la position X du texte dans une colonne selon son alignement. */
const getColumnTextX = (
  align: TextAlignH,
  colX: number,
  colWidth: number,
  elementWidth: number,
): number => {
  if (align === "right") return colX + colWidth * (elementWidth - 16) - 4;
  if (align === "center") return colX + (colWidth * (elementWidth - 16)) / 2;
  return colX;
};

/** Retourne la position X pour aligner du texte dans un élément (left/center/right). */
const getAlignedX = (align: string, elementWidth: number): number => {
  if (align === "center") return elementWidth / 2;
  if (align === "right") return elementWidth - 10;
  return 10;
};

/** Retourne le motif de tirets pour ctx.setLineDash selon le style de séparateur. */
const getLineDashPattern = (style: string): number[] => {
  if (style === "dashed") return [5, 5];
  if (style === "dotted") return [2, 2];
  return [];
};

// ─────────────────────────────────────────────────────────────────────────────

// ─── Helpers pour drawProductTable ───────────────────────────────────────────

type ProductTableColumn = { key: string; label: string; align: TextAlignH; x: number; width: number };
type ProductImageCacheRef = RefObject<Map<string, { image: HTMLImageElement; size: number; lastUsed: number }>>;

const drawCachedImageCell = (
  ctx: CanvasRenderingContext2D,
  cachedImage: { image: HTMLImageElement; size: number; lastUsed: number },
  bounds: { x: number; y: number; w: number; h: number },
  fontSize: number,
  fontFamily: string,
): void => {
  const { x: cellX, y: cellY, w: cellWidth, h: cellHeight } = bounds;
  cachedImage.lastUsed = Date.now();
  const img = cachedImage.image;
  if (img.complete && img.naturalHeight !== 0) {
    const imgRatio = img.naturalWidth / img.naturalHeight;
    const cellRatio = cellWidth / cellHeight;
    let drawWidth = cellWidth, drawHeight = cellHeight, drawX = cellX, drawY = cellY;
    if (imgRatio > cellRatio) { drawHeight = cellWidth / imgRatio; drawY = cellY + (cellHeight - drawHeight) / 2; }
    else { drawWidth = cellHeight * imgRatio; drawX = cellX + (cellWidth - drawWidth) / 2; }
    try { ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight); }
    catch (e) { debugWarn("[Canvas ProductTable] Error drawing image:", e); }
  } else {
    ctx.fillStyle = normalizeColor("#999999");
    ctx.font = `${fontSize}px ${fontFamily}`;
    ctx.textAlign = "center";
    ctx.fillText("⏳", cellX + cellWidth / 2, cellY + cellHeight / 2);
  }
};

const drawProductImageCell = (
  ctx: CanvasRenderingContext2D,
  imageUrl: string,
  cellBounds: { col: ProductTableColumn; rowY: number; rowHeight: number; elementWidth: number },
  textStyle: { fontSize: number; fontFamily: string; textColor: string },
  imgState: { cache: ProductImageCacheRef | undefined; onLoad: ((fn: (prev: number) => number) => void) | undefined },
): void => {
  const { col, rowY, rowHeight, elementWidth } = cellBounds;
  const cellWidth = col.width * (elementWidth - 16);
  const cellHeight = rowHeight - 2;
  const cellX = col.x;
  const cellY = rowY + 1;

  ctx.fillStyle = normalizeColor("#f0f0f0");
  ctx.fillRect(cellX, cellY, cellWidth, cellHeight);

  if (imageUrl && imgState.cache) {
    const cachedImage = imgState.cache.current.get(imageUrl);
    if (cachedImage) {
      drawCachedImageCell(ctx, cachedImage, { x: cellX, y: cellY, w: cellWidth, h: cellHeight }, textStyle.fontSize, textStyle.fontFamily);
    } else {
      const img = document.createElement("img");
      img.crossOrigin = "anonymous";
      img.src = imageUrl;
      img.onload = () => {
        const size = estimateImageMemorySize(img);
        imgState.cache.current.set(imageUrl, { image: img, size, lastUsed: Date.now() });
        cleanupImageCache(imgState.cache);
        if (imgState.onLoad) imgState.onLoad((prev) => prev + 1);
      };
      img.onerror = () => { debugWarn(`[Canvas ProductTable] Failed to load image: ${imageUrl}`); };
      ctx.fillStyle = normalizeColor("#999999");
      ctx.font = `${textStyle.fontSize}px ${textStyle.fontFamily}`;
      ctx.textAlign = "center";
      ctx.fillText("⏳", cellX + cellWidth / 2, cellY + cellHeight / 2);
    }
  } else if (!imageUrl) {
    ctx.fillStyle = normalizeColor("#999999");
    ctx.font = `${textStyle.fontSize}px ${textStyle.fontFamily}`;
    ctx.textAlign = "center";
    ctx.fillText("📷", cellX + cellWidth / 2, cellY + cellHeight / 2);
  }
  ctx.fillStyle = textStyle.textColor;
};

const drawProductTextCell = (
  ctx: CanvasRenderingContext2D,
  text: string,
  colKey: string,
  pos: { textX: number; rowY: number; rowHeight: number },
  widths: { col: number; element: number },
): void => {
  const maxWidth = widths.col * (widths.element - 16) - 8;
  let displayText = text;
  if (ctx.measureText(text).width > maxWidth && colKey === "name") {
    let truncated = text;
    while (ctx.measureText(truncated + "...").width > maxWidth && truncated.length > 0) {
      truncated = truncated.slice(0, -1);
    }
    displayText = truncated + "...";
  }
  ctx.fillText(displayText, pos.textX, pos.rowY + pos.rowHeight / 2);
};

// ─────────────────────────────────────────────────────────────────────────────

// Fonctions de rendu WooCommerce avec données fictives ou réelles selon le mode
const drawProductTable = ( // NOSONAR — complexité inhérente au rendu de tableau
  ctx: CanvasRenderingContext2D,
  element: Element,
  state: BuilderState,
  imageCache?: RefObject<
    Map<string, { image: HTMLImageElement; size: number; lastUsed: number }>
  >,
  setImageLoadCount?: (fn: (prev: number) => number) => void,
) => { // NOSONAR — complexité inhérente au rendu du tableau produit
  const props = element as ProductTableElement;

  // ✅ BUGFIX-020: Validate element has minimum size for rendering
  const minWidth = 100;
  const minHeight = 50;

  if (element.width < minWidth || element.height < minHeight) {
    // Element too small, draw placeholder
    ctx.fillStyle = normalizeColor("#f0f0f0");
    ctx.fillRect(0, 0, element.width, element.height);
    ctx.fillStyle = normalizeColor("#999999");
    ctx.font = "12px Arial";
    ctx.textAlign = "center";
    ctx.fillText("Trop petit", element.width / 2, element.height / 2);
    return;
  }

  const showHeaders = props.showHeaders !== false;
  const showBorders = props.showBorders !== false;
  const showAlternatingRows = props.showAlternatingRows !== false;
  const fontSize = props.fontSize || 11;
  const fontFamily = props.fontFamily || "Arial";
  const fontWeight = props.fontWeight || "normal";
  const fontStyle = props.fontStyle || "normal";
  const showSku = props.showSku !== false;
  const showDescription = props.showDescription !== false;
  const showShipping = props.showShipping !== false;
  const showTax = props.showTax !== false;
  const showGlobalDiscount = props.showGlobalDiscount !== false;
  const textColor = normalizeColor(props.textColor || "#000000");
  const borderRadius = props.borderRadius || 0;

  // ✅ NEW: Colonne visibilité basées sur showImage et autres propriétés
  const showImage = props.showImage !== false;
  const showName = true; // Toujours afficher le nom du produit
  const showQuantity = props.showQuantity !== false;
  const showPrice = props.showPrice !== false;
  const showTotal = props.showTotal !== false;

  // ✅ NEW: Use real WooCommerce data in preview mode
  let products: Array<{
    sku: string;
    name: string;
    description: string;
    qty: number;
    price: number;
    discount: number;
    total: number;
    image?: string;
  }>;

  if (state.previewMode === "command") {
    // Get real order items from WooCommerce
    const orderItems = wooCommerceManager.getOrderItems();
    const orderFees = wooCommerceManager.getOrderFees();

    // Combine products and fees into one array
    products = [
      ...orderItems.map((item) => ({
        sku: item.sku,
        name: item.name,
        description: item.description,
        qty: item.qty,
        price: item.price,
        discount: item.discount,
        total: item.total,
      })),
      ...orderFees.map((fee) => ({
        sku: "FEE",
        name: fee.name,
        description: "",
        qty: 1,
        price: fee.total,
        discount: 0,
        total: fee.total,
      })),
    ];
  } else {
    // Use props.products in editor mode
    products = (props.products || []).map((p: any) => ({
      sku: p.sku || "N/A",
      name: p.name,
      description: p.description || "",
      qty: p.quantity,
      price: p.price,
      discount: 0, // Les remises sont dans totals.discount
      total: p.total,
    }));
  }

  // 🔴 FIX: Si pas de produits, utiliser les données fictives par défaut
  if (!products || products.length === 0) {
    products = [
      {
        sku: "TSHIRT-001",
        name: "T-shirt Premium Bio",
        description: "T-shirt en coton biologique, coupe slim",
        qty: 2,
        price: 29.99,
        discount: 0,
        total: 59.98,
      },
      {
        sku: "JEAN-045",
        name: "Jean Slim Fit Noir",
        description: "Jean stretch confort, taille haute",
        qty: 1,
        price: 89.99,
        discount: 10,
        total: 79.99,
      },
      {
        sku: "SHOES-089",
        name: "Chaussures Running Pro",
        description: "Chaussures de running avec semelle amortissante",
        qty: 1,
        price: 129.99,
        discount: 0,
        total: 129.99,
      },
      {
        sku: "HOODIE-112",
        name: "Sweat à Capuche",
        description: "Sweat molletonné, capuche ajustable",
        qty: 1,
        price: 49.99,
        discount: 5,
        total: 44.99,
      },
      {
        sku: "SERVICE-FEE",
        name: "Frais de service",
        description: "Frais supplémentaires pour emballage premium",
        qty: 1,
        price: 15,
        discount: 0,
        total: 15,
      },
    ];
  }

  // Fees are now included in products array for preview mode
  const fees = state.previewMode === "command" ? [] : props.fees || [];

  const currency = "€";

  // ✅ Use real WooCommerce totals in preview mode
  let subtotal: number;
  let shippingCost: number;
  let taxAmount: number;
  let globalDiscount: number;
  let totalFees = 0;
  let taxRate = 0; // Pour l'affichage du %

  if (state.previewMode === "command") {
    // Get real order totals from WooCommerce
    const orderTotals = wooCommerceManager.getOrderTotals();
    // Calculate subtotal from products (includes fees already)
    subtotal = products.reduce(
      (sum: number, p: any) => sum + (Number(p.total) || 0),
      0,
    );
    shippingCost = Number(orderTotals.shipping) || 0;
    taxAmount = Number(orderTotals.tax) || 0;
    globalDiscount = Number(orderTotals.discount) || 0;
    // Calculate tax rate for display
    if (subtotal > 0 && taxAmount > 0) {
      taxRate = (taxAmount / subtotal) * 100;
    }
    // Fees already included in products array
  } else {
    // ✅ CALCUL CORRECT DES TOTALS - Pas de hardcoding
    // 1) Calculer le sous-total à partir des produits
    subtotal = products.reduce(
      (sum: number, p: any) => sum + (p.total || 0),
      0,
    );

    // 2) Ajouter les frais supplémentaires si présents
    totalFees = fees.reduce((sum: number, f: any) => sum + (f.total || 0), 0);

    // 3) Lire les valeurs depuis les propriétés de l'élément OU utiliser les valeurs de totals comme fallback
    shippingCost =
      (props.shippingCost as any) ||
      (props.totals?.shippingCost as any) ||
      10; // Valeur fictive: 10€
    taxRate = (props.taxRate as any) || (props.totals?.taxRate as any) || 5; // Valeur fictive: 5%
    globalDiscount =
      (props.globalDiscount as any) || (props.totals?.discount as any) || 20; // Valeur fictive: 20€

    // Calculate tax from rate
    taxAmount =
      showTax && taxRate > 0 ? ((subtotal + shippingCost) * taxRate) / 100 : 0;
  }

  // 4) APPLIQUER LES FLAGS ACTIFS - Mettre à zéro les éléments désactivés
  if (!showShipping) {
    shippingCost = 0;
  }
  if (!showTax) {
    taxAmount = 0; // Si TVA non affichée, ne pas l'appliquer
  }
  if (!showGlobalDiscount) {
    globalDiscount = 0; // Si remise non affichée, ne pas l'appliquer
  }

  // 5) Calculer le TOTAL FINAL: subtotal + frais de port + TVA + frais supplémentaires - remise
  const finalTotal =
    subtotal + shippingCost + taxAmount + totalFees - globalDiscount;

  // Configuration des colonnes
  interface TableColumn {
    key: string;
    label: string;
    width: number;
    align: "left" | "center" | "right";
    x: number;
  }

  // ✅ NEW: Définir les largeurs de base pour TOUTES les colonnes
  // avant de les normaliser ensemble
  const columnDefs: Array<{
    key: string;
    label: string;
    width: number;
    align: "left" | "center" | "right";
    show: boolean;
  }> = [
    {
      key: "image",
      label: "Img",
      width: 0.08,
      align: "center",
      show: showImage,
    },
    {
      key: "name",
      label: "Produit",
      width: (() => {
        if (showSku && showDescription) return 0.35;
        if (showSku || showDescription) return 0.45;
        return 0.55;
      })(),
      align: "left",
      show: showName,
    },
    {
      key: "sku",
      label: "SKU",
      width: 0.15,
      align: "left",
      show: showSku,
    },
    {
      key: "description",
      label: "Description",
      width: 0.25,
      align: "left",
      show: showDescription,
    },
    {
      key: "qty",
      label: "Qté",
      width: 0.08,
      align: "center",
      show: showQuantity,
    },
    {
      key: "price",
      label: "Prix",
      width: 0.12,
      align: "right",
      show: showPrice,
    },
    {
      key: "total",
      label: "Total",
      width: 0.12,
      align: "right",
      show: showTotal,
    },
  ];

  // ✅ NEW: Ajouter seulement les colonnes affichées
  const columns: TableColumn[] = [];
  for (const colDef of columnDefs) {
    if (colDef.show) {
      columns.push({
        key: colDef.key,
        label: colDef.label,
        width: colDef.width,
        align: colDef.align,
        x: 0,
      });
    }
  }

  // ✅ FIX: Colonne image avec largeur FIXE (en pixels)
  const IMAGE_COLUMN_WIDTH_PX = 70; // Largeur fixe pour images

  // Séparer les colonnes image et autres
  const imageColumnIndex = columns.findIndex((col) => col.key === "image");
  const hasImageColumn = imageColumnIndex !== -1;
  const tableWidthPixels = element.width - 16;
  const availableWidthPixels = hasImageColumn
    ? tableWidthPixels - IMAGE_COLUMN_WIDTH_PX
    : tableWidthPixels;

  // Normaliser seulement les colonnes non-image
  const nonImageColumns = columns.filter((_, i) => i !== imageColumnIndex);
  const totalWidthNonImage = nonImageColumns.reduce(
    (sum, col) => sum + col.width,
    0,
  );

  // Recalculer les largeurs proportionnelles pour les colonnes non-image
  nonImageColumns.forEach((col) => {
    col.width =
      (col.width / totalWidthNonImage) *
      (availableWidthPixels / tableWidthPixels);
  });

  // Fixer la largeur de la colonne image
  if (hasImageColumn) {
    columns[imageColumnIndex].width = IMAGE_COLUMN_WIDTH_PX / tableWidthPixels;
  }

  // Calcul des positions X des colonnes
  let currentX = 8;
  columns.forEach((col) => {
    col.x = currentX;
    currentX += col.width * (element.width - 16);
  });

  // ✅ Appliquer l'alignement vertical seulement (plus simple et moins risqué)
  const verticalAlign = props.verticalAlign || "top";

  // Calculer la hauteur totale du tableau pour l'alignement vertical
  const rowHeight = showDescription ? 50 : 35;
  const headerHeight = showHeaders ? 35 : 0;
  const productsCount = products.length;
  const tableHeight = headerHeight + productsCount * (rowHeight + 4) + 60; // +60 pour les totaux

  // Offset vertical seulement
  let offsetY = 0;

  // Alignement vertical - déplace le point d'origine vertical du tableau
  if (verticalAlign === "middle") {
    offsetY = Math.max(0, (element.height - tableHeight) / 2);
  } else if (verticalAlign === "bottom") {
    offsetY = Math.max(0, element.height - tableHeight - 10);
  }

  // Fond
  ctx.fillStyle = normalizeColor(props.backgroundColor || "#ffffff");
  ctx.fillRect(0, 0, element.width, element.height);

  // Bordure extérieure
  if (showBorders) {
    ctx.strokeStyle = props.borderColor || "#d1d5db";
    ctx.lineWidth = props.borderWidth || 1;
    if (borderRadius > 0) {
      roundedRect(ctx, 0, 0, element.width, element.height, borderRadius);
      ctx.stroke();
    } else {
      ctx.strokeRect(0, 0, element.width, element.height);
    }
  }

  ctx.textAlign = "left";
  let currentY = (showHeaders ? 25 : 15) + offsetY;

  // En-têtes avec style professionnel
  if (showHeaders) {
    ctx.fillStyle = normalizeColor(props.headerBackgroundColor || "#f9fafb");
    // Utiliser roundedRect si borderRadius > 0, sinon fillRect normal
    if (borderRadius > 0) {
      roundedRect(ctx, 1, 1 + offsetY, element.width - 2, 32, borderRadius);
      ctx.fill();
    } else {
      ctx.fillRect(1, 1 + offsetY, element.width - 2, 32);
    }

    ctx.fillStyle = normalizeColor(props.headerTextColor || "#374151");
    ctx.font = `${fontStyle} ${fontWeight} ${fontSize + 1}px ${fontFamily}`;
    ctx.textBaseline = "top";

    columns.forEach((col) => {
      ctx.textAlign = col.align as CanvasTextAlign;
      const textX = getColumnTextX(col.align, col.x, col.width, element.width);
      ctx.fillText(col.label, textX, 10 + offsetY); // Ajusté pour centrer dans la hauteur plus grande
    });

    // Ligne de séparation sous les en-têtes
    ctx.strokeStyle = normalizeColor("#e5e7eb");
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(4, 34 + offsetY); // Ajusté pour la nouvelle hauteur
    ctx.lineTo(element.width - 4, 34 + offsetY);
    ctx.stroke();

    currentY = 42 + offsetY; // Ajusté pour la nouvelle hauteur d'entête
  } else {
    currentY = 15 + offsetY;
  }

  // Produits avec alternance de couleurs
  ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
  ctx.textBaseline = "middle";

  products.forEach((product, index) => {
    // Calcul de la position Y absolue pour cette ligne
    const rowY = currentY + index * (rowHeight + 4);

    // Fond alterné pour les lignes (sans bordures)
    if (showAlternatingRows && index % 2 === 1) {
      ctx.fillStyle = normalizeColor(props.alternateRowColor || "#f9fafb");
      // Utiliser roundedRect si borderRadius > 0
      if (borderRadius > 0) {
        roundedRect(ctx, 1, rowY, element.width - 2, rowHeight, borderRadius);
        ctx.fill();
      } else {
        ctx.fillRect(1, rowY, element.width - 2, rowHeight);
      }
    }

    ctx.fillStyle = textColor; // Utiliser la couleur du texte depuis les propriétés

    columns.forEach((col) => {
      ctx.textAlign = col.align as CanvasTextAlign;
      const textX = getColumnTextX(col.align, col.x, col.width, element.width);

      let text = "";
      let isImage = false;
      let imageUrl = "";

      switch (col.key) {
        case "image":
          isImage = true;
          imageUrl = product.image || "";
          break;
        case "name":
          text = product.name;
          break;
        case "sku":
          text = product.sku;
          break;
        case "description":
          text = product.description;
          break;
        case "qty":
          text = product.qty.toString();
          break;
        case "price":
          text = `${product.price.toFixed(2)}${currency}`;
          break;
        case "discount":
          text =
            product.discount > 0
              ? `${product.discount.toFixed(2)}${currency}`
              : "-";
          break;
        case "total":
          text = `${product.total.toFixed(2)}${currency}`;
          break;
      }

      // ✅ NEW: Rendre l'image ou un placeholder
      if (isImage) {
        drawProductImageCell(ctx, imageUrl, { col, rowY, rowHeight, elementWidth: element.width }, { fontSize, fontFamily, textColor }, { cache: imageCache, onLoad: setImageLoadCount });
      } else {
        drawProductTextCell(ctx, text, col.key, { textX, rowY, rowHeight }, { col: col.width, element: element.width });
      }
    });
  });
  currentY = 55 + products.length * (rowHeight + 4) + 8;

  // Section des totaux

  // Ligne de séparation avant les totaux
  ctx.strokeStyle = normalizeColor("#d1d5db");
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(element.width - 200, currentY);
  ctx.lineTo(element.width - 8, currentY);
  ctx.stroke();

  currentY += 20;

  // Affichage des totaux
  ctx.font = `bold ${fontSize}px Arial`;
  ctx.fillStyle = textColor;
  ctx.textAlign = "left";

  const totalsY = currentY;
  ctx.fillText("Sous-total:", element.width - 200, totalsY);
  ctx.textAlign = "right";
  ctx.fillText(`${subtotal.toFixed(2)}${currency}`, element.width - 8, totalsY);

  currentY += 18;

  // Remise globale
  if (globalDiscount > 0 && showGlobalDiscount) {
    ctx.textAlign = "left";
    ctx.fillStyle = normalizeColor("#059669"); // Vert pour la remise
    ctx.fillText("Remise:", element.width - 200, currentY);
    ctx.textAlign = "right";
    ctx.fillText(
      `-${globalDiscount.toFixed(2)}${currency}`,
      element.width - 8,
      currentY,
    );
    currentY += 18;
  }

  // Frais de port
  if (shippingCost > 0 && showShipping) {
    ctx.textAlign = "left";
    ctx.fillStyle = textColor;
    ctx.fillText("Frais de port:", element.width - 200, currentY);
    ctx.textAlign = "right";
    ctx.fillText(
      `+${shippingCost.toFixed(2)}${currency}`,
      element.width - 8,
      currentY,
    );
    currentY += 18;
  }

  // Taxes
  if (taxAmount > 0 && showTax) {
    ctx.textAlign = "left";
    ctx.fillStyle = textColor;
    const taxLabel = taxRate > 0 ? `TVA (${taxRate.toFixed(1)}%):` : "TVA:";
    ctx.fillText(taxLabel, element.width - 200, currentY);
    ctx.textAlign = "right";
    ctx.fillText(
      `+${taxAmount.toFixed(2)}${currency}`,
      element.width - 8,
      currentY,
    );
    currentY += 18;
  }

  currentY += 8;
  ctx.strokeStyle = textColor;
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(element.width - 200, currentY - 5);
  ctx.lineTo(element.width - 8, currentY - 5);
  ctx.stroke();

  currentY += 8;
  ctx.font = `${fontStyle} bold ${fontSize + 2}px ${fontFamily}`;
  ctx.fillStyle = textColor;
  ctx.textAlign = "left";
  ctx.fillText("TOTAL:", element.width - 200, currentY);
  ctx.textAlign = "right";
  ctx.fillText(
    `${finalTotal.toFixed(2)}${currency}`,
    element.width - 8,
    currentY,
  );
};

// Fonctions de rendu WooCommerce avec données fictives ou réelles selon le mode
const drawCustomerInfo = ( // NOSONAR — complexité inhérente au rendu des infos client
  ctx: CanvasRenderingContext2D,
  element: Element,
  state: BuilderState,
) => { // NOSONAR — complexité inhérente au rendu des infos client
  const props = element as CustomerInfoElement;
  const fontSize = props.fontSize || 12;
  const fontFamily = props.fontFamily || "Arial";
  const fontWeight = props.fontWeight || "normal";
  const fontStyle = props.fontStyle || "normal";
  // Propriétés de police pour l'en-tête
  const headerFontSize = props.headerFontSize || fontSize + 2;
  const headerFontFamily = props.headerFontFamily || fontFamily;
  const headerFontWeight = props.headerFontWeight || fontWeight;
  const headerFontStyle = props.headerFontStyle || fontStyle;
  // Propriétés de police pour le corps du texte
  const bodyFontSize = props.bodyFontSize || fontSize;
  const bodyFontFamily = props.bodyFontFamily || fontFamily;
  const bodyFontWeight = props.bodyFontWeight || fontWeight;
  const bodyFontStyle = props.bodyFontStyle || fontStyle;
  const layout = props.layout || "vertical";
  const showHeaders = props.showHeaders !== false;
  const showBorders = props.showBorders !== false;
  const showFullName = props.showFullName !== false;
  const showAddress = props.showAddress !== false;
  const showEmail = props.showEmail !== false;
  const showPhone = props.showPhone !== false;
  const showPaymentMethod = props.showPaymentMethod !== false;
  const showTransactionId = props.showTransactionId !== false;
  // Alignement vertical
  const verticalAlign = props.verticalAlign || "top";

  // Fond
  if (props.showBackground !== false) {
    ctx.fillStyle = normalizeColor(props.backgroundColor || "#ffffff");
    ctx.fillRect(0, 0, element.width, element.height);
  }

  // Bordures
  if (showBorders) {
    ctx.strokeStyle = normalizeColor(props.borderColor || "#e5e7eb");
    ctx.lineWidth = props.borderWidth || 1;
    ctx.strokeRect(0, 0, element.width, element.height);
  }

  ctx.fillStyle = normalizeColor(props.textColor || "#000000");
  ctx.font = `${headerFontStyle} ${headerFontWeight} ${headerFontSize}px ${headerFontFamily}`;

  // Appliquer l'alignement horizontal
  const textAlign = props.textAlign || "left";
  ctx.textAlign = textAlign as CanvasTextAlign;
  ctx.textBaseline = "top";

  // Construire le contenu et calculer la hauteur totale
  let customerData: {
    name: string;
    address: string;
    email: string;
    phone: string;
  };

  if (state.previewMode === "command") {
    customerData = wooCommerceManager.getCustomerInfo();
  } else {
    customerData = {
      name: "Marie Dupont",
      address: "15 rue des Lilas, 75001 Paris\nFrance",
      email: "marie.dupont@email.com",
      phone: "+33 6 12 34 56 78",
    };
  }

  // Construire les lignes de texte selon le layout
  const lines: string[] = [];

  if (layout === "vertical") {
    if (showFullName) {
      lines.push(customerData.name);
    }
    if (showAddress) {
      // Split par \n pour supporter les adresses multi-lignes (ex: adresse postale\npays)
      const addressLines = customerData.address.split("\n");
      addressLines.forEach((line) => lines.push(line));
    }
    if (showEmail) {
      lines.push(customerData.email);
    }
    if (showPhone) {
      lines.push(customerData.phone);
    }
    if (showPaymentMethod) {
      lines.push("Paiement: Carte bancaire");
    }
    if (showTransactionId) {
      lines.push("ID: TXN123456789");
    }
  } else if (layout === "horizontal") {
    // Mode horizontal : 2-3 lignes avec plusieurs infos par ligne
    let line1 = "";
    let line2 = "";
    let line3 = "";

    // Ligne 1: Nom + Email
    if (showFullName) line1 += customerData.name;
    if (showEmail) line1 += (line1 ? " | " : "") + customerData.email;

    // Ligne 2: Adresse + Téléphone (remplacer \n par , pour garder compact)
    if (showAddress) line2 += customerData.address.replaceAll("\n", ", ");
    if (showPhone) line2 += (line2 ? " | " : "") + customerData.phone;

    // Ligne 3: Paiement + Transaction ID
    if (showPaymentMethod) line3 += "Paiement: Carte bancaire";
    if (showTransactionId) line3 += (line3 ? " | " : "") + "ID: TXN123456789";

    if (line1) lines.push(line1);
    if (line2) lines.push(line2);
    if (line3) lines.push(line3);
  } else if (layout === "compact") {
    // Mode compact : nom en en-tête + tout le reste avec séparateurs, word wrap si trop long

    // Nom du client (si activé)
    if (showFullName) {
      lines.push(customerData.name);
    }

    // Reste des infos en mode compact
    let compactText = "";
    if (showAddress) {
      compactText += customerData.address.replaceAll("\n", ", "); // Remplacer \n par , pour mode compact
    }
    if (showEmail) {
      compactText += (compactText ? " • " : "") + customerData.email;
    }
    if (showPhone) {
      compactText += (compactText ? " • " : "") + customerData.phone;
    }
    if (showPaymentMethod) {
      compactText += (compactText ? " • " : "") + "Paiement: Carte bancaire";
    }
    if (showTransactionId) {
      compactText += (compactText ? " • " : "") + "ID: TXN123456789";
    }

    if (compactText) {
      // Word wrap si le texte dépasse
      const maxWidth = element.width - 24;
      ctx.font = `${bodyFontStyle} ${bodyFontWeight} ${bodyFontSize}px ${bodyFontFamily}`;

      const words = compactText.split(" ");
      let currentLine = "";

      for (const word of words) {
        const testLine = currentLine + (currentLine ? " " : "") + word;
        const metrics = ctx.measureText(testLine);

        if (metrics.width > maxWidth && currentLine) {
          // La ligne dépasse, pousser la ligne actuelle et commencer une nouvelle
          lines.push(currentLine);
          currentLine = word;
        } else {
          currentLine = testLine;
        }
      }

      // Ajouter la dernière ligne
      if (currentLine) {
        lines.push(currentLine);
      }
    }
  }

  // Calculer la hauteur du contenu basé sur les vraies tailles de police
  const lineHeightRatio = props.lineHeight || 1.1; // Utiliser le lineHeight configuré
  const _lineSpacing = props.lineSpacing || 0; // Espacement supplémentaire entre les lignes (défaut 0)
  // customer_info utilise padding automatique (pas customizable via UI)
  const paddingVerticalTop = 12; // Fixed value matching extract_padding() default
  const paddingVerticalBottom = 12; // Fixed value matching extract_padding() default
  const headerLineHeight = headerFontSize * lineHeightRatio + 4; // fontSize + margin-bottom
  const bodyLineHeight = bodyFontSize * lineHeightRatio; // ligne sans espacement additionnel pour customer_info
  const headerHeight = showHeaders ? headerLineHeight : 0;
  const contentHeight = lines.length * bodyLineHeight; // Chaque ligne = bodyFont * lineHeightRatio
  const totalContentHeight = headerHeight + contentHeight;
  const availableHeight =
    element.height - paddingVerticalTop - paddingVerticalBottom;

  // Calculer l'offset Y selon l'alignement vertical
  let startY: number;
  switch (verticalAlign) {
    case "middle":
      startY =
        paddingVerticalTop +
        Math.max(0, (availableHeight - totalContentHeight) / 2);
      break;
    case "bottom":
      startY =
        paddingVerticalTop + Math.max(0, availableHeight - totalContentHeight);
      break;
    default: // top
      startY = paddingVerticalTop;
  }

  let y = startY;

  // Calculer la position X selon l'alignement
  const getTextX = () => {
    switch (textAlign) {
      case "center":
        return element.width / 2;
      case "right":
        return element.width - 12;
      default: // left
        return 12;
    }
  };

  const textX = getTextX();

  // En-tête
  if (showHeaders) {
    ctx.fillStyle = normalizeColor(props.headerTextColor || "#111827");
    ctx.fillText("Informations Client", textX, y);
    y += headerLineHeight; // Utiliser la même valeur que le calcul de hauteur
    ctx.fillStyle = normalizeColor(props.textColor || "#000000");
  }

  ctx.font = `${bodyFontStyle} ${bodyFontWeight} ${bodyFontSize}px ${bodyFontFamily}`;

  // Dessiner les lignes avec support du lineHeight
  lines.forEach((lineText) => {
    ctx.fillText(lineText, textX, y);

    // Appliquer le line-height basé sur bodyFontSize * lineHeightRatio
    y += bodyFontSize * lineHeightRatio;
  });
};

// Constantes pour les thèmes company_info
const COMPANY_THEMES = {
  corporate: {
    backgroundColor: "#ffffff",
    borderColor: "#1f2937",
    textColor: "#374151",
    headerTextColor: "#111827",
  },
  modern: {
    backgroundColor: "#ffffff",
    borderColor: "#3b82f6",
    textColor: "#1e40af",
    headerTextColor: "#1e3a8a",
  },
  elegant: {
    backgroundColor: "#ffffff",
    borderColor: "#8b5cf6",
    textColor: "#6d28d9",
    headerTextColor: "#581c87",
  },
  minimal: {
    backgroundColor: "#ffffff",
    borderColor: "#e5e7eb",
    textColor: "#374151",
    headerTextColor: "#111827",
  },
  professional: {
    backgroundColor: "#ffffff",
    borderColor: "#059669",
    textColor: "#047857",
    headerTextColor: "#064e3b",
  },
} as const;

// Fonction helper pour récupérer les données d'entreprise
const _getCompanyData = (props: CompanyInfoElementProperties) => {
  const baseData = {
    name: props.companyName || "",
    address: props.companyAddress || "",
    city: props.companyCity || "",
    siret: props.companySiret || "",
    tva: props.companyTva || "",
    rcs: props.companyRcs || "",
    capital: props.companyCapital || "",
    email: props.companyEmail || "",
    phone: props.companyPhone || "",
  };

  // Remplacer par les données dynamiques du plugin si disponibles
  const pluginCompany = getPdfBuilderData().company;
  if (pluginCompany) {
    Object.keys(baseData).forEach((key) => {
      if (pluginCompany[key] && pluginCompany[key] !== "Non indiqué") {
        (baseData as any)[key] = pluginCompany[key];
      }
    });
  }

  return baseData;
};

// Fonction pour générer le texte formaté des informations d'entreprise
const _generateCompanyInfoText = (
  props: CompanyInfoElementProperties,
): string => {
  // Créer une fonction getCompanyData locale pour cette fonction
  const getCompanyDataLocal = () => {
    const baseData = {
      name: props.companyName || "Company Name",
      address: props.companyAddress || "123 Main Street",
      city: props.companyCity || "",
      siret: props.companySiret || "12345678901234",
      tva: props.companyTva || "FR12345678901",
      rcs: props.companyRcs || "RCS 123457890",
      capital: props.companyCapital || "€100,000",
      email: props.companyEmail || "info@company.com",
      phone: props.companyPhone || "+33123456789",
    };

    // Remplacer par les données dynamiques du plugin si disponibles
    const pluginCompany = getPdfBuilderData().company;
    if (pluginCompany) {
      Object.keys(baseData).forEach((key) => {
        if (pluginCompany[key] && pluginCompany[key] !== "Non indiqué") {
          (baseData as any)[key] = pluginCompany[key];
        }
      });
    }

    return baseData;
  };

  const companyData = getCompanyDataLocal();

  // Configuration d'affichage
  const displayConfig = {
    companyName: props.showCompanyName !== false,
    address: props.showAddress !== false,
    phone: props.showPhone !== false,
    email: props.showEmail !== false,
    siret: props.showSiret !== false,
    vat: props.showVat !== false,
    rcs: props.showRcs !== false,
    capital: props.showCapital !== false,
  };

  const lines: string[] = [];

  // Ajouter le nom de l'entreprise
  if (shouldDisplayValue(companyData.name, displayConfig.companyName)) {
    lines.push(companyData.name);
  }

  // Ajouter l'adresse
  if (shouldDisplayValue(companyData.address, displayConfig.address)) {
    lines.push(companyData.address);
    if (shouldDisplayValue(companyData.city, displayConfig.address)) {
      lines.push(companyData.city);
    }
  }

  // Ajouter les autres informations
  const infoFields = [
    [companyData.siret, displayConfig.siret, "SIRET"],
    [companyData.tva, displayConfig.vat, "TVA"],
    [companyData.rcs, displayConfig.rcs, "RCS"],
    [companyData.capital, displayConfig.capital, "Capital"],
    [companyData.email, displayConfig.email, "Email"],
    [companyData.phone, displayConfig.phone, "Téléphone"],
  ];

  infoFields.forEach(([value, show, _label]) => {
    if (shouldDisplayValue(value as string, show as boolean)) {
      lines.push(`${value}`);
    }
  });

  return lines.join("\n");
};

// Fonction helper pour vérifier si une valeur doit être affichée
const shouldDisplayValue = (value: string, showFlag: boolean) =>
  showFlag && value && value !== "Non indiqué";

// Fonction helper pour dessiner une ligne de texte company_info
const drawCompanyLine = (
  ctx: CanvasRenderingContext2D,
  text: string,
  x: number,
  y: number,
  fontSize: number,
  lineHeightRatio: number = 1.1,
) => {
  ctx.fillText(text, x, y);
  return y + fontSize * lineHeightRatio;
};

const drawCompanyInfo = ( // NOSONAR — complexité inhérente au rendu des infos entreprise
  ctx: CanvasRenderingContext2D,
  element: Element,
  _state: BuilderState,
) => { // NOSONAR — complexité inhérente au rendu des infos entreprise
  const props = element as CompanyInfoElement;

  // ✅ HELPER: Formater le numéro de téléphone (ajouter un point tous les 2 chiffres)
  const formatPhoneNumber = (phone: string): string => {
    if (!phone) return phone;
    const cleaned = phone.replaceAll(/\D/g, "");
    const matches = cleaned.match(/\d{2}/g);
    return matches ? matches.join(".") : phone;
  };

  // Récupérer les données de l'entreprise depuis l'élément canvas (pas depuis les options WordPress)
  const getCompanyData = () => { // NOSONAR — identique à getCompanyDataLocal par nécessité de scope
    const baseData = {
      name: props.companyName || "Company Name",
      address: props.companyAddress || "123 Main Street",
      city: props.companyCity || "",
      siret: props.companySiret || "12345678901234",
      tva: props.companyTva || "FR12345678901",
      rcs: props.companyRcs || "RCS 123457890",
      capital: props.companyCapital || "€100,000",
      email: props.companyEmail || "info@company.com",
      phone: props.companyPhone || "+33123456789",
    };

    // Remplacer par les données dynamiques du plugin si disponibles
    const pluginCompany = getPdfBuilderData().company;
    if (pluginCompany) {
      Object.keys(baseData).forEach((key) => {
        if (pluginCompany[key] && pluginCompany[key] !== "Non indiqué") {
          (baseData as any)[key] = pluginCompany[key];
        }
      });
    }

    return baseData;
  };

  // Configuration des polices
  const _fontSize = props.fontSize || 12;
  const fontConfig = {
    family: props.fontFamily || "Arial",
    weight: normalizeFontWeight(props.fontWeight || "normal"),
    style: props.fontStyle || "normal",
    headerSize: props.headerFontSize || 14,
    headerFamily: props.headerFontFamily || props.fontFamily || "Arial",
    headerWeight: normalizeFontWeight(props.headerFontWeight || "bold"),
    headerStyle: props.headerFontStyle || "normal",
    bodySize: props.bodyFontSize || 12,
    bodyFamily: props.bodyFontFamily || props.fontFamily || "Arial",
    bodyWeight: normalizeFontWeight(props.bodyFontWeight || "normal"),
    bodyStyle: props.bodyFontStyle || "normal",
  };

  // Configuration d'affichage
  const displayConfig = {
    background: props.showBackground !== false,
    borders: props.showBorders !== false,
    companyName: props.showCompanyName !== false,
    address: props.showAddress !== false,
    phone: props.showPhone !== false,
    email: props.showEmail !== false,
    siret: props.showSiret !== false,
    vat: props.showVat !== false,
    rcs: props.showRcs !== false,
    capital: props.showCapital !== false,
  };

  // Thème et couleurs
  const theme =
    COMPANY_THEMES[
      (props.theme || "corporate") as keyof typeof COMPANY_THEMES
    ] || COMPANY_THEMES.corporate;
  const colors = {
    background: normalizeColor(props.backgroundColor || theme.backgroundColor),
    border: normalizeColor(props.borderColor || theme.borderColor),
    text: normalizeColor(props.textColor || theme.textColor),
    headerText: normalizeColor(props.headerTextColor || theme.headerTextColor),
  };

  // Appliquer le fond et les bordures
  if (displayConfig.background) {
    ctx.fillStyle = colors.background;
    ctx.fillRect(0, 0, element.width, element.height);
  }

  if (displayConfig.borders) {
    ctx.strokeStyle = colors.border;
    ctx.lineWidth = props.borderWidth || 1;
    ctx.strokeRect(0, 0, element.width, element.height);
  }

  // Configuration du contexte
  ctx.fillStyle = colors.text;

  // Appliquer l'alignement horizontal
  const textAlign = props.textAlign || "left";
  ctx.textAlign = textAlign as CanvasTextAlign;
  ctx.textBaseline = "top";

  // Position de départ avec padding dynamique
  const paddingTopComp = props.paddingTop || 8; // Utiliser paddingTop personnalisable (défaut 8)
  const paddingHorizontal = props.paddingHorizontal || 12; // paddingHorizontal (défaut 12)
  const paddingBottomComp = props.paddingBottom || 12; // paddingBottom personnalisable (défaut 12)

  const getTextX = () => {
    switch (textAlign) {
      case "center":
        return element.width / 2;
      case "right":
        return element.width - paddingHorizontal;
      default: // left
        return paddingHorizontal;
    }
  };

  let x = getTextX();
  let y = paddingTopComp; // Utiliser paddingTop défini plus haut

  // Récupération des données d'entreprise
  const companyData = getCompanyData(); // ✅ Utilise la fonction locale (sans paramètre)

  // Récupérer le layout
  const layout = props.layout || "vertical";

  // Construire les lignes selon le layout
  const lines: Array<{ text: string; isHeader: boolean }> = [];

  if (layout === "vertical") {
    // Mode vertical : une info par ligne
    if (shouldDisplayValue(companyData.name, displayConfig.companyName)) {
      lines.push({ text: companyData.name, isHeader: true });
    }
    if (shouldDisplayValue(companyData.address, displayConfig.address)) {
      lines.push({
        text: companyData.address,
        isHeader: false,
      });
      if (shouldDisplayValue(companyData.city, displayConfig.address)) {
        lines.push({ text: companyData.city, isHeader: false });
      }
    }
    // Email et Téléphone AVANT les infos légales
    if (shouldDisplayValue(companyData.email, displayConfig.email)) {
      lines.push({ text: companyData.email, isHeader: false });
    }
    if (shouldDisplayValue(companyData.phone, displayConfig.phone)) {
      lines.push({
        text: formatPhoneNumber(companyData.phone),
        isHeader: false,
      });
    }
    // Infos légales après
    [
      [companyData.siret, displayConfig.siret, "SIRET"],
      [companyData.tva, displayConfig.vat, "TVA"],
      [companyData.rcs, displayConfig.rcs, "RCS"],
      [companyData.capital, displayConfig.capital, "Capital"],
    ].forEach(([value, show, label]) => {
      if (shouldDisplayValue(value as string, show as boolean)) {
        const displayText = `${label}: ${value as string}`;
        lines.push({ text: displayText, isHeader: false });
      }
    });
  } else if (layout === "horizontal") {
    // Mode horizontal : plusieurs infos par ligne
    if (shouldDisplayValue(companyData.name, displayConfig.companyName)) {
      lines.push({ text: companyData.name, isHeader: true });
    }

    // Ligne 1: Adresse complète
    let addressLine = "";
    if (shouldDisplayValue(companyData.address, displayConfig.address)) {
      addressLine += companyData.address;
      if (shouldDisplayValue(companyData.city, displayConfig.address)) {
        addressLine += ", " + companyData.city;
      }
    }
    if (addressLine)
      lines.push({
        text: addressLine,
        isHeader: false,
      });

    // Ligne 2: Contact (Email + Phone)
    let contactLine = "";
    if (shouldDisplayValue(companyData.email, displayConfig.email)) {
      contactLine += companyData.email;
    }
    if (shouldDisplayValue(companyData.phone, displayConfig.phone)) {
      contactLine +=
        (contactLine ? " | " : "") + formatPhoneNumber(companyData.phone);
    }
    if (contactLine) lines.push({ text: contactLine, isHeader: false });

    // Ligne 3: Infos légales (SIRET + RCS + TVA + Capital)
    let legalLine = "";
    if (shouldDisplayValue(companyData.siret, displayConfig.siret)) {
      legalLine += companyData.siret;
    }
    if (shouldDisplayValue(companyData.rcs, displayConfig.rcs)) {
      legalLine += (legalLine ? " | " : "") + companyData.rcs;
    }
    if (shouldDisplayValue(companyData.tva, displayConfig.vat)) {
      legalLine += (legalLine ? " | " : "") + companyData.tva;
    }
    if (shouldDisplayValue(companyData.capital, displayConfig.capital)) {
      legalLine += (legalLine ? " | " : "") + companyData.capital;
    }
    if (legalLine) lines.push({ text: legalLine, isHeader: false });
  } else if (layout === "compact") {
    // Mode compact : nom en en-tête + tout le reste avec séparateurs, word wrap si trop long

    // Nom de l'entreprise en en-tête (avec sa police configurable)
    if (shouldDisplayValue(companyData.name, displayConfig.companyName)) {
      lines.push({ text: companyData.name, isHeader: true });
    }

    // Reste des infos en mode compact
    let compactText = "";
    if (shouldDisplayValue(companyData.address, displayConfig.address)) {
      compactText += companyData.address; // Adresse complète
    }
    if (shouldDisplayValue(companyData.email, displayConfig.email)) {
      compactText += (compactText ? " • " : "") + companyData.email;
    }
    if (shouldDisplayValue(companyData.phone, displayConfig.phone)) {
      compactText +=
        (compactText ? " • " : "") + formatPhoneNumber(companyData.phone);
    }
    if (shouldDisplayValue(companyData.siret, displayConfig.siret)) {
      compactText += (compactText ? " • " : "") + companyData.siret;
    }
    if (shouldDisplayValue(companyData.tva, displayConfig.vat)) {
      compactText += (compactText ? " • " : "") + companyData.tva;
    }
    if (shouldDisplayValue(companyData.rcs, displayConfig.rcs)) {
      compactText += (compactText ? " • " : "") + companyData.rcs;
    }

    if (compactText) {
      // Word wrap si le texte dépasse
      const maxWidth = element.width - 24;
      ctx.font = `${fontConfig.bodyStyle} ${fontConfig.bodyWeight} ${fontConfig.bodySize}px ${fontConfig.bodyFamily}`;

      const words = compactText.split(" ");
      let currentLine = "";

      for (const word of words) {
        const testLine = currentLine + (currentLine ? " " : "") + word;
        const metrics = ctx.measureText(testLine);

        if (metrics.width > maxWidth && currentLine) {
          // La ligne dépasse, pousser la ligne actuelle et commencer une nouvelle
          lines.push({ text: currentLine, isHeader: false });
          currentLine = word;
        } else {
          currentLine = testLine;
        }
      }

      // Ajouter la dernière ligne
      if (currentLine) {
        lines.push({ text: currentLine, isHeader: false });
      }
    }
  }

  // Appliquer la police du corps par défaut
  ctx.font = `${fontConfig.bodyStyle} ${fontConfig.bodyWeight} ${fontConfig.bodySize}px ${fontConfig.bodyFamily}`;

  // Calculer la hauteur totale pour l'alignement vertical avec le lineHeight configuré
  const lineHeightRatio = props.lineHeight || 1.4; // Utiliser le lineHeight configuré (défaut 1.4 pour company_info)
  let totalHeight = 0;

  // Calculer la hauteur totale:
  // Chaque ligne a une hauteur = fontSize * lineHeightRatio (qui inclut déjà l'espacement vertical CSS)
  // Plus margin-bottom: 4px pour les headers (séparation avec le contenu suivant)
  lines.forEach((lineData, index) => {
    const config = lineData.isHeader
      ? {
          size: fontConfig.headerSize,
          weight: fontConfig.headerWeight,
          style: fontConfig.headerStyle,
          family: fontConfig.headerFamily,
        }
      : {
          size: fontConfig.bodySize,
          weight: fontConfig.bodyWeight,
          style: fontConfig.bodyStyle,
          family: fontConfig.bodyFamily,
        };
    // Hauteur de la ligne = fontSize * lineHeightRatio (inclut l'espacement CSS)
    totalHeight += config.size * lineHeightRatio;

    // Ajouter margin-bottom: 4px pour les headers (séparation avec le contenu suivant)
    // C'est le margin-bottom défini dans le style HTML des headers avec <strong>
    if (lineData.isHeader && index < lines.length - 1) {
      totalHeight += 4;
    }
  });

  // Ajuster la position Y selon l'alignement vertical
  const verticalAlign = props.verticalAlign || "top";
  const availableHeightComp =
    element.height - paddingTopComp - paddingBottomComp;

  if (verticalAlign === "middle") {
    y = paddingTopComp + Math.max(0, (availableHeightComp - totalHeight) / 2);
  } else if (verticalAlign === "bottom") {
    y = paddingTopComp + Math.max(0, availableHeightComp - totalHeight);
  } else {
    y = paddingTopComp;
  }

  // Dessiner toutes les lignes
  // L'espacement entre les lignes vient UNIQUEMENT du CSS line-height
  // Pas d'espacement supplémentaire additionné (comme lineSpacing)
  lines.forEach((lineData, index) => {
    const config = lineData.isHeader
      ? {
          size: fontConfig.headerSize,
          weight: fontConfig.headerWeight,
          style: fontConfig.headerStyle,
          family: fontConfig.headerFamily,
        }
      : {
          size: fontConfig.bodySize,
          weight: fontConfig.bodyWeight,
          style: fontConfig.bodyStyle,
          family: fontConfig.bodyFamily,
        };

    ctx.font = `${config.style} ${config.weight} ${config.size}px ${config.family}`;
    if (lineData.isHeader) ctx.fillStyle = colors.headerText;
    // Dessiner la ligne - drawCompanyLine ajoute (fontSize * lineHeightRatio) à y
    y = drawCompanyLine(ctx, lineData.text, x, y, config.size, lineHeightRatio);
    if (lineData.isHeader) ctx.fillStyle = colors.text;

    // Ajouter margin-bottom: 4px après les headers (séparation avec le contenu suivant)
    if (lineData.isHeader && index < lines.length - 1) {
      y += 4;
    }
    // Pour les autres lignes, l'espacement est déjà inclus dans le lineHeightRatio (CSS line-height)
  });
};

const drawWoocommerceOrderDate = ( // NOSONAR — complexité inhérente au rendu des dates
  ctx: CanvasRenderingContext2D,
  element: Element,
  state: BuilderState,
) => { // NOSONAR — complexité inhérente au rendu des dates
  const props = element as WoocommerceOrderDateElement;
  const fontConfig = createFontConfig(props, 12);
  const colorConfig = createColorConfig(props);
  const padding = getPadding(props);

  // Appliquer le fond
  if (props.showBackground !== false) {
    ctx.fillStyle = colorConfig.background;
    ctx.fillRect(0, 0, element.width, element.height);
  }

  // Appliquer la bordure
  applyBorder(ctx, element, props.border);

  // Récupérer et formater la date (utiliser getOrderDateRaw pour avoir la date ISO)
  const orderDate =
    state.previewMode === "command"
      ? wooCommerceManager.getOrderDateRaw()
      : wooCommerceManager.getOrderDateRaw() || new Date().toISOString();

  const displayDate = formatOrderDate(
    orderDate,
    props.dateFormat,
    props.showTime,
  );

  // Gestion du label
  const showLabel = props.showLabel !== false;
  const labelText = props.labelText || "Date de la facture :";
  const labelPosition = props.labelPosition || "left";
  const labelSpacing = props.labelSpacing || 8;

  // Config police du label
  const labelFontFamily = props.labelFontFamily || props.fontFamily || "Arial";
  const labelFontSize = props.labelFontSize || props.fontSize || 12;
  const labelFontWeight = props.labelFontWeight || "normal";
  const labelFontStyle = props.labelFontStyle || "normal";
  const labelColor = props.labelColor || props.color || colorConfig.text;

  if (showLabel) {
    // Mesurer le label
    ctx.font = `${labelFontStyle} ${labelFontWeight} ${labelFontSize}px ${labelFontFamily}`;
    const labelMetrics = ctx.measureText(labelText);
    const labelWidth = labelMetrics.width;
    const labelHeight = labelFontSize;

    // Mesurer la date
    ctx.font = `${fontConfig.style} ${fontConfig.weight} ${fontConfig.size}px ${fontConfig.family}`;
    const dateMetrics = ctx.measureText(displayDate);
    const dateWidth = dateMetrics.width;
    const dateHeight = fontConfig.size;

    // ===== ALIGNEMENT VERTICAL PROPRE =====
    // 1. Calculer la hauteur totale du contenu selon labelPosition
    let totalContentHeight = 0;
    if (labelPosition === "top" || labelPosition === "bottom") {
      // Empilage vertical : label + spacing + date
      totalContentHeight = labelHeight + labelSpacing + dateHeight;
    } else {
      // Côte à côte (left/right) : prendre le max des deux
      totalContentHeight = Math.max(labelHeight, dateHeight);
    }

    // 2. Calculer le Y de départ du groupe selon verticalAlign
    let groupStartY: number;
    const availableHeight = element.height - padding.top - padding.bottom;

    switch (props.verticalAlign) {
      case "middle":
        groupStartY = padding.top + (availableHeight - totalContentHeight) / 2;
        break;
      case "bottom":
        groupStartY = element.height - padding.bottom - totalContentHeight;
        break;
      default: // top
        groupStartY = padding.top;
        break;
    }

    // 3. Positionner les éléments relativement au groupStartY
    let labelX = padding.left;
    let labelY = groupStartY;
    let dateX = padding.left;
    let dateY = groupStartY;

    switch (labelPosition) {
      case "top":
        // Label au-dessus de la date
        labelX = calculateTextX(element, props.textAlign, padding);
        labelY = groupStartY;
        dateX = calculateTextX(element, props.textAlign, padding);
        dateY = groupStartY + labelHeight + labelSpacing;
        break;

      case "left": {
        // Label à gauche de la date
        const totalWidth = labelWidth + labelSpacing + dateWidth;
        if (props.textAlign === "center") {
          labelX = (element.width - totalWidth) / 2;
        } else if (props.textAlign === "right") {
          labelX = element.width - padding.right - totalWidth;
        } else {
          labelX = padding.left;
        }
        labelY = groupStartY;
        dateX = labelX + labelWidth + labelSpacing;
        dateY = groupStartY;
        break;
      }

      case "right": {
        // Label à droite de la date
        const totalWidthRight = dateWidth + labelSpacing + labelWidth;
        if (props.textAlign === "center") {
          dateX = (element.width - totalWidthRight) / 2;
        } else if (props.textAlign === "right") {
          dateX = element.width - padding.right - totalWidthRight;
        } else {
          dateX = padding.left;
        }
        dateY = groupStartY;
        labelX = dateX + dateWidth + labelSpacing;
        labelY = groupStartY;
        break;
      }

      case "bottom":
        // Label en-dessous de la date
        dateX = calculateTextX(element, props.textAlign, padding);
        dateY = groupStartY;
        labelX = calculateTextX(element, props.textAlign, padding);
        labelY = groupStartY + dateHeight + labelSpacing;
        break;
    }

    // Dessiner le label
    ctx.fillStyle = labelColor;
    ctx.font = `${labelFontStyle} ${labelFontWeight} ${labelFontSize}px ${labelFontFamily}`;
    ctx.textAlign =
      labelPosition === "left" || labelPosition === "right"
        ? "left"
        : (props.textAlign as CanvasTextAlign) || "left";
    ctx.textBaseline = "top";
    ctx.fillText(labelText, labelX, labelY);

    // Dessiner la date
    ctx.fillStyle = colorConfig.text;
    ctx.font = `${fontConfig.style} ${fontConfig.weight} ${fontConfig.size}px ${fontConfig.family}`;
    ctx.textAlign =
      labelPosition === "left" || labelPosition === "right"
        ? "left"
        : (props.textAlign as CanvasTextAlign) || "left";
    ctx.textBaseline = "top";
    ctx.fillText(displayDate, dateX, dateY);
  } else {
    // Sans label, affichage normal
    setupRenderContext(
      ctx,
      fontConfig,
      colorConfig,
      props.textAlign,
      props.verticalAlign,
    );
    const x = calculateTextX(element, props.textAlign, padding);
    const y = calculateTextYWithPadding(
      element,
      padding,
      props.verticalAlign,
      fontConfig.size,
    );
    ctx.fillText(displayDate, x, y);
  }
};

// Fonction helper pour formater les dates de commande
const formatOrderDate = (
  dateString: string,
  format: string = "d/m/Y",
  showTime: boolean = false,
): string => {
  try {
    const dateObj = new Date(dateString);
    if (Number.isNaN(dateObj.getTime())) return dateString;

    const day = String(dateObj.getDate()).padStart(2, "0");
    const dayNoZero = String(dateObj.getDate());
    const month = String(dateObj.getMonth() + 1).padStart(2, "0");
    const year = dateObj.getFullYear();

    // Noms des mois en français
    const monthNames = [
      "janvier",
      "février",
      "mars",
      "avril",
      "mai",
      "juin",
      "juillet",
      "août",
      "septembre",
      "octobre",
      "novembre",
      "décembre",
    ];
    const monthNamesShort = [
      "jan",
      "fév",
      "mar",
      "avr",
      "mai",
      "juin",
      "juil",
      "août",
      "sep",
      "oct",
      "nov",
      "déc",
    ];

    // Noms des jours en français
    const dayNames = [
      "dimanche",
      "lundi",
      "mardi",
      "mercredi",
      "jeudi",
      "vendredi",
      "samedi",
    ];
    const dayNamesShort = ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"];

    let formattedDate: string;
    switch (format) {
      case "m/d/Y":
        formattedDate = `${month}/${day}/${year}`;
        break;
      case "Y-m-d":
        formattedDate = `${year}-${month}-${day}`;
        break;
      case "d-m-Y":
        formattedDate = `${day}-${month}-${year}`;
        break;
      case "d.m.Y":
        formattedDate = `${day}.${month}.${year}`;
        break;
      case "j F Y":
        formattedDate = `${dayNoZero} ${monthNames[dateObj.getMonth()]} ${year}`;
        break;
      case "l j F Y":
        formattedDate = `${dayNames[dateObj.getDay()]} ${dayNoZero} ${monthNames[dateObj.getMonth()]} ${year}`;
        break;
      case "F j, Y":
        formattedDate = `${monthNames[dateObj.getMonth()]} ${dayNoZero}, ${year}`;
        break;
      case "D, M j, Y":
        formattedDate = `${dayNamesShort[dateObj.getDay()]}, ${monthNamesShort[dateObj.getMonth()]} ${dayNoZero}, ${year}`;
        break;
      default:
        formattedDate = `${day}/${month}/${year}`;
    }

    if (showTime) {
      const hours = String(dateObj.getHours()).padStart(2, "0");
      const minutes = String(dateObj.getMinutes()).padStart(2, "0");
      formattedDate += ` ${hours}:${minutes}`;
    }

    return formattedDate;
  } catch {
    return dateString;
  }
};

const drawWoocommerceInvoiceNumber = ( // NOSONAR — complexité inhérente au rendu du numéro
  ctx: CanvasRenderingContext2D,
  element: Element,
  _state: BuilderState,
) => { // NOSONAR — complexité inhérente au rendu du numéro de facture
  const props = element as WoocommerceInvoiceNumberElement;
  const fontConfig = createFontConfig(props, 12);
  const colorConfig = createColorConfig(props);
  const padding = getPadding(props);

  // Appliquer le fond
  if (props.showBackground !== false) {
    ctx.fillStyle = colorConfig.background;
    ctx.fillRect(0, 0, element.width, element.height);
  }

  // Appliquer la bordure
  applyBorder(ctx, element, props.border);

  // Récupérer et formater le numéro de facture
  const invoiceNumber =
    wooCommerceManager.getInvoiceNumber?.() || "INV-2024-00001";
  const displayText = `${props.prefix || ""}${invoiceNumber}${props.suffix || ""}`;

  // Gestion du label
  const showLabel = props.showLabel !== false;
  const labelText = props.labelText || "Numéro de facture :";
  const labelPosition = props.labelPosition || "left";
  const labelSpacing = props.labelSpacing || 8;

  // Config police du label
  const labelFontFamily = props.labelFontFamily || props.fontFamily || "Arial";
  const labelFontSize = props.labelFontSize || props.fontSize || 12;
  const labelFontWeight = props.labelFontWeight || "normal";
  const labelFontStyle = props.labelFontStyle || "normal";
  const labelColor = props.labelColor || props.color || colorConfig.text;

  if (showLabel) {
    // Mesurer le label
    ctx.font = `${labelFontStyle} ${labelFontWeight} ${labelFontSize}px ${labelFontFamily}`;
    const labelMetrics = ctx.measureText(labelText);
    const labelWidth = labelMetrics.width;
    const labelHeight = labelFontSize;

    // Mesurer le numéro
    ctx.font = `${fontConfig.style} ${fontConfig.weight} ${fontConfig.size}px ${fontConfig.family}`;
    const numberMetrics = ctx.measureText(displayText);
    const numberWidth = numberMetrics.width;
    const numberHeight = fontConfig.size;

    // ===== ALIGNEMENT VERTICAL PROPRE =====
    // 1. Calculer la hauteur totale du contenu selon labelPosition
    let totalContentHeight = 0;
    if (labelPosition === "top" || labelPosition === "bottom") {
      // Empilage vertical : label + spacing + number
      totalContentHeight = labelHeight + labelSpacing + numberHeight;
    } else {
      // Côte à côte (left/right) : prendre le max des deux
      totalContentHeight = Math.max(labelHeight, numberHeight);
    }

    // 2. Calculer le Y de départ du groupe selon verticalAlign
    let groupStartY: number;
    const availableHeight = element.height - padding.top - padding.bottom;

    switch (props.verticalAlign) {
      case "middle":
        groupStartY = padding.top + (availableHeight - totalContentHeight) / 2;
        break;
      case "bottom":
        groupStartY = element.height - padding.bottom - totalContentHeight;
        break;
      default: // top
        groupStartY = padding.top;
        break;
    }

    // 3. Positionner les éléments relativement au groupStartY
    let labelX = padding.left;
    let labelY = groupStartY;
    let numberX = padding.left;
    let numberY = groupStartY;

    switch (labelPosition) {
      case "top":
        // Label au-dessus du numéro
        labelX = calculateTextX(element, props.textAlign, padding);
        labelY = groupStartY;
        numberX = calculateTextX(element, props.textAlign, padding);
        numberY = groupStartY + labelHeight + labelSpacing;
        break;

      case "left": {
        // Label à gauche du numéro
        const totalWidth = labelWidth + labelSpacing + numberWidth;
        if (props.textAlign === "center") {
          labelX = (element.width - totalWidth) / 2;
        } else if (props.textAlign === "right") {
          labelX = element.width - padding.right - totalWidth;
        } else {
          labelX = padding.left;
        }
        labelY = groupStartY;
        numberX = labelX + labelWidth + labelSpacing;
        numberY = groupStartY;
        break;
      }

      case "right": {
        // Label à droite du numéro
        const totalWidthRight = numberWidth + labelSpacing + labelWidth;
        if (props.textAlign === "center") {
          numberX = (element.width - totalWidthRight) / 2;
        } else if (props.textAlign === "right") {
          numberX = element.width - padding.right - totalWidthRight;
        } else {
          numberX = padding.left;
        }
        numberY = groupStartY;
        labelX = numberX + numberWidth + labelSpacing;
        labelY = groupStartY;
        break;
      }

      case "bottom":
        // Label en-dessous du numéro
        numberX = calculateTextX(element, props.textAlign, padding);
        numberY = groupStartY;
        labelX = calculateTextX(element, props.textAlign, padding);
        labelY = groupStartY + numberHeight + labelSpacing;
        break;
    }

    // Dessiner le label
    ctx.fillStyle = labelColor;
    ctx.font = `${labelFontStyle} ${labelFontWeight} ${labelFontSize}px ${labelFontFamily}`;
    ctx.textAlign =
      labelPosition === "left" || labelPosition === "right"
        ? "left"
        : (props.textAlign as CanvasTextAlign) || "left";
    ctx.textBaseline = "top";
    ctx.fillText(labelText, labelX, labelY);

    // Dessiner le numéro
    ctx.fillStyle = colorConfig.text;
    ctx.font = `${fontConfig.style} ${fontConfig.weight} ${fontConfig.size}px ${fontConfig.family}`;
    ctx.textAlign =
      labelPosition === "left" || labelPosition === "right"
        ? "left"
        : (props.textAlign as CanvasTextAlign) || "left";
    ctx.textBaseline = "top";
    ctx.fillText(displayText, numberX, numberY);
  } else {
    // Sans label, affichage normal
    setupRenderContext(
      ctx,
      fontConfig,
      colorConfig,
      props.textAlign,
      props.verticalAlign,
    );
    const x = calculateTextX(element, props.textAlign, padding);
    const y = calculateTextYWithPadding(
      element,
      padding,
      props.verticalAlign,
      fontConfig.size,
    );
    ctx.fillText(displayText, x, y);
  }
};

const drawDocumentType = (
  ctx: CanvasRenderingContext2D,
  element: Element,
  _state: BuilderState,
) => {
  const props = element as DocumentTypeElement;
  const fontConfig = createFontConfig(props, 18);
  fontConfig.weight = props.fontWeight || "bold"; // Override default weight

  const colorConfig = createColorConfig(props);

  // Appliquer le fond
  if (props.showBackground !== false) {
    ctx.fillStyle = normalizeColor(props.backgroundColor || "#e5e7eb");
    ctx.fillRect(0, 0, element.width, element.height);
  }

  // Configurer le contexte
  setupRenderContext(
    ctx,
    fontConfig,
    colorConfig,
    props.textAlign,
    props.verticalAlign,
  );

  // Déterminer le type de document
  const documentType = props.documentType || "FACTURE";

  // Mapping des types techniques vers les labels lisibles
  const DOCUMENT_TYPE_LABELS = {
    FACTURE: "FACTURE",
    DEVIS: "DEVIS",
    BON_COMMANDE: "BON DE COMMANDE",
    AVOIR: "AVOIR",
    RELEVE: "RELEVE",
    CONTRAT: "CONTRAT",
  } as const;

  const displayText =
    DOCUMENT_TYPE_LABELS[documentType as keyof typeof DOCUMENT_TYPE_LABELS] ||
    documentType;

  // Calculer la position X selon l'alignement horizontal
  const x = getAlignedX(props.textAlign ?? "left", element.width);

  // Calculer la position Y selon l'alignement vertical
  const y = calculateTextY(element, props.verticalAlign, fontConfig.size, 5);

  ctx.fillText(displayText, x, y);
};

interface CanvasProps {
  width: number;
  height: number;
  className?: string;
}

// Flag global pour afficher les logs détaillés des éléments (debug)
// Debug flags - set to true to enable verbose logging

// Constantes pour le cache des images
const MAX_CACHE_ITEMS = 100; // Max 100 images in cache

export const Canvas = function Canvas({
  width,
  height,
  className,
}: CanvasProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const canvasWrapperRef = useRef<HTMLDivElement>(null);

  // ✅ Track derniers éléments rendus pour éviter double rendu
  const _lastRenderedElementsRef = useRef<string>("");
  const renderCountRef = useRef<number>(0);

  const { state, dispatch } = useBuilder();
  const canvasSettings = useCanvasSettings();

  debugLog("🎨 Canvas: Component initialized with props:", {
    width,
    height,
    className,
  });
  debugLog("📊 Canvas: Initial state:", {
    elements: state.elements.length,
    selection: state.selection.selectedElements.length,
    zoom: state.canvas.zoom,
  });

  debugLog(
    `[Canvas] Component initialized - Dimensions: ${width}x${height}, Settings loaded: ${!!canvasSettings}`,
  );

  // Force re-render when canvas settings change (commenté pour éviter les boucles)
  // const [, forceUpdate] = useState({});
  // useEffect(() => {
  //   forceUpdate({});
  // }, [canvasSettings.canvasBackgroundColor, canvasSettings.borderColor, canvasSettings.borderWidth, canvasSettings.shadowEnabled, canvasSettings.containerBackgroundColor]);

  // État pour le menu contextuel
  const [contextMenu, setContextMenu] = useState<{
    isVisible: boolean;
    position: { x: number; y: number };
    elementId?: string;
  }>({
    isVisible: false,
    position: { x: 0, y: 0 },
  });

  // ✅ STATE for image loading - force redraw when images load
  const [imageLoadCount, setImageLoadCount] = useState(0);

  // Récupérer la limite mémoire JavaScript depuis les paramètres
  const memoryLimitJs = useCanvasSetting("memory_limit_js", 256) as number; // En MB, défaut 256MB

  // ✅ LAZY LOADING: Récupérer le paramètre depuis les settings
  const lazyLoadingEnabled = canvasSettings.lazyLoadingEditor;

  // ✅ LAZY LOADING: État pour tracker les éléments visibles
  const [_visibleElements, _setVisibleElements] = useState<Set<string>>(
    new Set(),
  );
  const [viewportBounds, setViewportBounds] = useState({
    x: 0,
    y: 0,
    width: width,
    height: height,
  });

  // ✅ LAZY LOADING: Fonction pour déterminer si un élément est visible dans le viewport
  const isElementVisible = useCallback(
    (
      element: Element,
      _viewport: { x: number; y: number; width: number; height: number },
    ): boolean => {
      // Calculer les bounds de l'élément (simplifié - on pourrait améliorer avec rotation, etc.)
      const elementBounds = {
        x: element.x,
        y: element.y,
        width: element.width,
        height: element.height,
      };

      // Vérifier si l'élément intersecte le viewport (avec une marge de 100px)
      const margin = 100;
      return !(
        elementBounds.x + elementBounds.width < viewportBounds.x - margin ||
        elementBounds.x > viewportBounds.x + viewportBounds.width + margin ||
        elementBounds.y + elementBounds.height < viewportBounds.y - margin ||
        elementBounds.y > viewportBounds.y + viewportBounds.height + margin
      );
    },
    [viewportBounds],
  ); // ✅ LAZY LOADING: Filtrer les éléments visibles
  const visibleElementsList = useMemo(() => {
    if (!lazyLoadingEnabled) {
      return state.elements; // Tous les éléments si lazy loading désactivé
    }

    // Filtrer les éléments visibles tout en préservant l'ordre
    return state.elements.filter((element, index) => {
      // Toujours inclure les 5 premiers éléments pour éviter les sauts visuels
      if (index < 5) return true;
      // Vérifier la visibilité pour les autres
      return isElementVisible(element, viewportBounds);
    });
  }, [state.elements, lazyLoadingEnabled, viewportBounds, isElementVisible]);

  // Cache pour les images chargées avec métadonnées de mémoire
  const imageCache = useRef<
    Map<string, { image: HTMLImageElement; size: number; lastUsed: number }>
  >(new Map());

  // ✅ LAZY LOADING: Hook pour mettre à jour le viewport quand le canvas change
  useEffect(() => {
    if (!canvasRef.current) return;

    const updateViewport = () => {
      const canvas = canvasRef.current;
      if (!canvas) return;

      const rect = canvas.getBoundingClientRect();
      setViewportBounds({
        x: -rect.left,
        y: -rect.top,
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    // Mettre à jour initialement
    updateViewport();

    // Écouter les changements de scroll et resize avec passive: true
    window.addEventListener("scroll", updateViewport, { passive: true });
    window.addEventListener("resize", updateViewport, { passive: true });

    return () => {
      window.removeEventListener("scroll", updateViewport);
      window.removeEventListener("resize", updateViewport);
    };
  }, []);

  // ✅ CORRECTION 7: Tracker les URLs rendues pour détecter changements
  const renderedLogoUrlsRef = useRef<Map<string, string>>(new Map()); // elementId -> logoUrl

  // ✅ Flag: Track if we've done initial render check for images
  const initialImageCheckDoneRef = useRef(false);

  // Fonction pour estimer la taille mémoire d'une image (approximation)
  const estimateImageMemorySize = useCallback(
    (img: HTMLImageElement): number => {
      // Estimation basée sur : largeur * hauteur * 4 octets (RGBA) + overhead
      const pixelData = img.naturalWidth * img.naturalHeight * 4;
      const overhead = 1024; // Overhead approximatif par image
      return pixelData + overhead;
    },
    [],
  );

  // Fonction pour calculer l'usage mémoire total du cache
  const calculateCacheMemoryUsage = useCallback((): number => {
    let totalSize = 0;
    for (const [, data] of imageCache.current) {
      totalSize += data.size;
    }
    return totalSize / (1024 * 1024); // Convertir en MB
  }, []);

  // Fonction pour vérifier si la limite mémoire est dépassée
  const isMemoryLimitExceeded = useCallback((): boolean => {
    const currentUsage = calculateCacheMemoryUsage();
    const limit = memoryLimitJs;

    // Vérifier aussi la mémoire globale du navigateur si disponible
    if ("memory" in performance) {
      const perfMemory = (performance as any).memory;
      const browserMemoryUsage = perfMemory.usedJSHeapSize / (1024 * 1024); // MB
      const browserLimit = perfMemory.jsHeapSizeLimit / (1024 * 1024); // MB

      // Si le navigateur approche sa limite, être plus agressif
      if (browserMemoryUsage > browserLimit * 0.8) {
        debugWarn(
          `[Canvas Memory] Browser memory usage high: ${browserMemoryUsage.toFixed(
            1,
          )}MB / ${browserLimit.toFixed(1)}MB`,
        );
        return true;
      }
    }

    return currentUsage > limit * 0.8; // Déclencher le nettoyage à 80% de la limite
  }, [calculateCacheMemoryUsage, memoryLimitJs]);

  // ✅ CORRECTION 2: Fonction pour nettoyer le cache des images avec gestion mémoire
  const cleanupImageCache = useCallback(() => {
    const cache = imageCache.current;
    const currentMemoryUsage = calculateCacheMemoryUsage();
    const memoryLimit = memoryLimitJs;

    debugLog(
      `[Canvas Memory] Starting cache cleanup - Current usage: ${currentMemoryUsage.toFixed(
        2,
      )}MB, Limit: ${memoryLimit}MB, Items: ${cache.size}`,
    );

    // Nettoyer si limite dépassée ou trop d'éléments
    if (isMemoryLimitExceeded() || cache.size > MAX_CACHE_ITEMS) {
      // Trier par date d'utilisation (LRU - Least Recently Used)
      const entries = Array.from(cache.entries()).sort(
        ([, a], [, b]) => a.lastUsed - b.lastUsed,
      );

      // Calculer combien supprimer pour revenir sous 70% de la limite
      const targetMemoryUsage = memoryLimit * 0.7;
      let memoryToFree = Math.max(0, currentMemoryUsage - targetMemoryUsage);
      let itemsToRemove = Math.min(20, Math.ceil(cache.size * 0.2)); // Au moins 20% des éléments ou 20 éléments max

      let removed = 0;
      let memoryFreed = 0;

      for (const [url, data] of entries) {
        if (removed >= itemsToRemove && memoryFreed >= memoryToFree) break;

        cache.delete(url);
        memoryFreed += data.size / (1024 * 1024); // MB
        removed++;

        debugLog(
          `[Canvas Memory] Removed image from cache: ${url
            .split("/")
            .pop()}, Freed: ${(data.size / (1024 * 1024)).toFixed(2)}MB`,
        );
      }

      debugLog(
        `[Canvas Memory] Cache cleanup completed - Removed ${removed} items, Freed ${memoryFreed.toFixed(
          2,
        )}MB, New usage: ${(currentMemoryUsage - memoryFreed).toFixed(2)}MB`,
      );
    } else {
      debugLog(
        `[Canvas Memory] Cache cleanup not needed - Usage within limits`,
      );
    }
  }, [calculateCacheMemoryUsage, memoryLimitJs, isMemoryLimitExceeded]);

  // Fonction pour forcer un nettoyage manuel (utile pour le débogage)
  const forceCacheCleanup = useCallback(() => {
    cleanupImageCache();
  }, [cleanupImageCache]);

  // Exposer les fonctions de gestion mémoire globalement pour le débogage
  useEffect(() => {
    (globalThis as any).canvasMemoryDebug = {
      getCacheStats: () => ({
        itemCount: imageCache.current.size,
        memoryUsage: calculateCacheMemoryUsage(),
        memoryLimit: memoryLimitJs,
        items: Array.from(imageCache.current.entries()).map(([url, data]) => ({
          url: url.split("/").pop(),
          size: (data.size / (1024 * 1024)).toFixed(2) + "MB",
          lastUsed: new Date(data.lastUsed).toLocaleTimeString(),
        })),
      }),
      forceCleanup: forceCacheCleanup,
      getBrowserMemory: () => {
        if ("memory" in performance) {
          const perfMemory = (performance as any).memory;
          return {
            used: (perfMemory.usedJSHeapSize / (1024 * 1024)).toFixed(1) + "MB",
            total:
              (perfMemory.totalJSHeapSize / (1024 * 1024)).toFixed(1) + "MB",
            limit:
              (perfMemory.jsHeapSizeLimit / (1024 * 1024)).toFixed(1) + "MB",
          };
        }
        return { error: "Performance.memory not available" };
      },
    };

    return () => {
      delete (globalThis as any).canvasMemoryDebug;
    };
  }, [calculateCacheMemoryUsage, memoryLimitJs, forceCacheCleanup]); // Surveillance périodique de la mémoire globale du navigateur
  useEffect(() => {
    const memoryCheckInterval = setInterval(() => {
      if ("memory" in performance) {
        const perfMemory = (performance as any).memory;
        const browserMemoryUsage = perfMemory.usedJSHeapSize / (1024 * 1024); // MB
        const browserLimit = perfMemory.jsHeapSizeLimit / (1024 * 1024); // MB
        const _cacheMemoryUsage = calculateCacheMemoryUsage();

        // Log détaillé de la mémoire si activé
        if (canvasSettings.debugMode) {
          // Debug mode active — no additional action required here.
        }

        // Nettoyage d'urgence si mémoire critique
        if (browserMemoryUsage > browserLimit * 0.9) {
          debugWarn(
            `[Canvas Memory] Critical memory usage! Forcing cache cleanup...`,
          );
          cleanupImageCache();
        }
      }
    }, 10000); // Vérification toutes les 10 secondes

    return () => clearInterval(memoryCheckInterval);
  }, [
    calculateCacheMemoryUsage,
    memoryLimitJs,
    cleanupImageCache,
    canvasSettings.debugMode,
  ]);

  // Utiliser les hooks pour les interactions
  const {
    handleDrop,
    handleDragOver,
    handleDragLeave,
    handleDragEnter,
    isDragOver,
  } = useCanvasDrop({
    canvasRef: canvasWrapperRef as React.RefObject<HTMLElement>,
    canvasWidth: width,
    canvasHeight: height,
    elements: state.elements || [],
    dragEnabled: true,
  });

  const {
    handleCanvasClick,
    handleMouseDown,
    handleMouseMove,
    handleMouseUp,
    handleContextMenu,
    selectionState,
  } = useCanvasInteraction({
    canvasRef: canvasRef as React.RefObject<HTMLElement>,
    canvasWidth: width,
    canvasHeight: height,
  });

  // Hook pour les raccourcis clavier
  const _keyboardShortcutInfo = useKeyboardShortcuts();

  // Fonctions de rendu WooCommerce avec données fictives ou réelles selon le mode

  // Fonction helper pour dessiner un placeholder de logo
  const drawLogoPlaceholder = useCallback(
    (
      ctx: CanvasRenderingContext2D,
      element: Element,
      alignment: string,
      text: string,
    ) => {
      const logoWidth = Math.min(element.width - 20, 120);
      const logoHeight = Math.min(element.height - 20, 60);

      let x = 10;
      if (alignment === "center") {
        x = (element.width - logoWidth) / 2;
      } else if (alignment === "right") {
        x = element.width - logoWidth - 10;
      }

      const y = (element.height - logoHeight) / 2;

      // Rectangle du logo
      ctx.fillStyle = normalizeColor("#f0f0f0");
      ctx.strokeStyle = normalizeColor("#ccc");
      ctx.lineWidth = 1;
      ctx.fillRect(x, y, logoWidth, logoHeight);
      ctx.strokeRect(x, y, logoWidth, logoHeight);

      // Texte du placeholder
      ctx.fillStyle = normalizeColor("#666");
      ctx.font = "12px Arial";
      ctx.textAlign = "center";
      ctx.fillText(text, x + logoWidth / 2, y + logoHeight / 2 + 4);
    },
    [],
  );

  const drawCompanyLogo = useCallback(
    (ctx: CanvasRenderingContext2D, element: Element) => { // NOSONAR — complexité inhérente au rendu du logo
      const props = element as CompanyLogoElement;
      const logoUrl = props.src || props.logoUrl || "";

      // ✅ FIX: If no logo URL, show a better placeholder
      if (!logoUrl) {
        drawLogoPlaceholder(
          ctx,
          element,
          "center",
          "Configurez le logo entreprise",
        );
        return;
      }

      const alignment = props.alignment || "left";

      // ✅ CORRECTION 7: Détecter si l'URL a changé
      const lastRenderedUrl = renderedLogoUrlsRef.current.get(element.id);
      if (logoUrl !== lastRenderedUrl) {
        renderedLogoUrlsRef.current.set(element.id, logoUrl);
      }

      // Fond transparent
      ctx.fillStyle = "transparent";
      ctx.fillRect(0, 0, element.width, element.height);

      if (logoUrl) {
        // Vérifier si l'image est en cache
        let cachedImage = imageCache.current.get(logoUrl);

        if (!cachedImage) {
          const img = document.createElement("img");
          img.crossOrigin = "anonymous";
          img.src = logoUrl;

          // Gérer les erreurs de chargement
          img.onerror = () => {
            debugError("❌ [LOGO] Image failed to load:", logoUrl);
          };

          // ✅ CRITICAL: Quand l'image se charge, redessiner le canvas
          img.onload = () => {
            const size = estimateImageMemorySize(img);
            imageCache.current.set(logoUrl, {
              image: img,
              size: size,
              lastUsed: Date.now(),
            });
            // Déclencher un nettoyage après ajout
            cleanupImageCache();
            // Incrémenter le counter pour forcer un redraw
            setImageLoadCount((prev) => prev + 1);
          };

          // Retourner temporairement pour éviter les erreurs
          return;
        }

        const img = cachedImage.image;
        // Mettre à jour la date d'utilisation
        cachedImage.lastUsed = Date.now();

        // ✅ APPROCHE PLUS DIRECTE: Vérifier img.complete au rendu au lieu de compter sur onload
        // Rendre l'image si elle a une URL valide, même si elle n'est pas encore complètement chargée

        if (logoUrl?.trim()) {
          try {
            // Appliquer la rotation si définie
            const rotation = element.rotation || 0;
            // Normaliser l'opacité : si > 1 c'est un pourcentage (0-100)
            const opacity = normalizeOpacity(element.opacity);
            const borderRadius = element.borderRadius || 0;
            const objectFit = element.objectFit || "contain";

            // Calculer les dimensions et position selon objectFit
            const containerWidth = element.width;
            const containerHeight = element.height;

            // Si l'image n'est pas encore chargée, utiliser des dimensions par défaut ou essayer de deviner
            let imageAspectRatio: number;
            if (img.naturalWidth > 0 && img.naturalHeight > 0) {
              imageAspectRatio = img.naturalWidth / img.naturalHeight;
            } else {
              // Estimation par défaut pour les logos d'entreprise (généralement rectangulaires)
              imageAspectRatio = 2; // 2:1 ratio par défaut
            }

            const containerAspectRatio = containerWidth / containerHeight;

            let logoWidth = 0;
            let logoHeight = 0;

            // Normaliser objectFit: toute valeur inconnue → "contain"
            const fit = objectFit === "cover" || objectFit === "fill" || objectFit === "none" || objectFit === "scale-down"
              ? objectFit : "contain";

            switch (fit) {
              case "contain":
                // Respecte les proportions, image tient entièrement dans le conteneur
                if (containerAspectRatio > imageAspectRatio) {
                  logoHeight = containerHeight;
                  logoWidth = logoHeight * imageAspectRatio;
                } else {
                  logoWidth = containerWidth;
                  logoHeight = logoWidth / imageAspectRatio;
                }
                break;

              case "cover":
                // Respecte les proportions, image couvre entièrement le conteneur
                if (containerAspectRatio > imageAspectRatio) {
                  logoWidth = containerWidth;
                  logoHeight = logoWidth / imageAspectRatio;
                  // offsetY sera calculé selon verticalAlign plus tard
                } else {
                  logoHeight = containerHeight;
                  logoWidth = logoHeight * imageAspectRatio;
                  // offsetX sera calculé selon horizontalAlign plus tard
                }
                break;

              case "fill":
                // Étire l'image pour remplir exactement le conteneur
                logoWidth = containerWidth;
                logoHeight = containerHeight;
                break;

              case "none":
                // Taille originale, centrée
                if (img.naturalWidth > 0 && img.naturalHeight > 0) {
                  logoWidth = img.naturalWidth;
                  logoHeight = img.naturalHeight;
                } else {
                  // Taille par défaut si pas encore chargée
                  logoWidth = Math.min(containerWidth, 120);
                  logoHeight = Math.min(containerHeight, 60);
                }
                break;

              case "scale-down": {
                // Taille originale ou contain, selon ce qui est plus petit
                const originalWidth = img.naturalWidth || 120; // Défaut si pas chargé
                const originalHeight = img.naturalHeight || 60; // Défaut si pas chargé

                if (
                  originalWidth <= containerWidth &&
                  originalHeight <= containerHeight
                ) {
                  // Taille originale tient, l'utiliser
                  logoWidth = originalWidth;
                  logoHeight = originalHeight;
                } else if (containerAspectRatio > imageAspectRatio) {
                  // Utiliser contain
                  logoHeight = containerHeight;
                  logoWidth = logoHeight * imageAspectRatio;
                } else {
                    logoWidth = containerWidth;
                    logoHeight = logoWidth / imageAspectRatio;
                }
                break;
              }
              default:
                break;
            }

            // Centrer l'image dans le conteneur
            const imageX = (containerWidth - logoWidth) / 2;
            const imageY = (containerHeight - logoHeight) / 2;

            // Appliquer l'opacité
            if (opacity < 1) {
              ctx.globalAlpha = opacity;
            }

            // Clip avec borderRadius si nécessaire (AVANT rotation)
            if (borderRadius > 0) {
              ctx.save();
              ctx.beginPath();
              roundedRect(
                ctx,
                imageX,
                imageY,
                logoWidth,
                logoHeight,
                borderRadius,
              );
              ctx.clip();
            }

            // Appliquer la rotation si nécessaire
            if (rotation !== 0) {
              ctx.save();
              const centerX = imageX + logoWidth / 2;
              const centerY = imageY + logoHeight / 2;
              ctx.translate(centerX, centerY);
              ctx.rotate((rotation * Math.PI) / 180);
              ctx.translate(-centerX, -centerY);
            }

            // Dessiner l'image
            ctx.drawImage(img, imageX, imageY, logoWidth, logoHeight);

            // Restaurer la rotation si elle était appliquée
            if (rotation !== 0) {
              ctx.restore();
            }

            // Restaurer le clip si borderRadius était appliqué
            if (borderRadius > 0) {
              ctx.restore();
            }

            // Réinitialiser l'opacité
            if (opacity < 1) {
              ctx.globalAlpha = 1;
            }

            // Le contexte a déjà été restauré ci-dessus
            return;
          } catch (error) {
            debugError(`❌ [LOGO] Error rendering image ${logoUrl}:`, error);
            // En cas d'erreur, dessiner un placeholder
            drawLogoPlaceholder(
              ctx,
              element,
              alignment,
              "Erreur de chargement",
            );
          }
        } else {
          // Pas d'URL valide, dessiner un placeholder
          drawLogoPlaceholder(ctx, element, alignment, "URL manquante");
        }
      } else {
        // Pas d'URL, dessiner un placeholder
        drawLogoPlaceholder(ctx, element, alignment, "Company_logo");
      }
    },
    [drawLogoPlaceholder, cleanupImageCache, estimateImageMemorySize],
  ); // ✅ BUGFIX-008: REMOVED setImageLoadCounter

  // ✅ BUGFIX-007: Memoize drawDynamicText to prevent recreation on every render
  const drawDynamicText = useCallback(
    (ctx: CanvasRenderingContext2D, element: Element) => {
      const props = element as DynamicTextElement;
      const text = props.text || props.content || "Texte personnalisable";
      const fontSize = props.fontSize || 14;
      const fontFamily = props.fontFamily || "Arial";
      const fontWeight = props.fontWeight || "normal";
      const fontStyle = props.fontStyle || "normal";
      const autoWrap = props.autoWrap !== false; // Par défaut activé
      const textColor = props.textColor || props.color || "#000000";

      // Appliquer le fond seulement si showBackground est activé
      if (props.showBackground !== false) {
        ctx.fillStyle = normalizeColor(props.backgroundColor || "#e5e7eb");
        ctx.fillRect(0, 0, element.width, element.height);
      }

      ctx.fillStyle = normalizeColor(textColor);
      ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
      ctx.textAlign = "left";

      // Remplacer les variables génériques par des valeurs par défaut pour l'aperçu
      const processedText = text
        .replaceAll("{{customer_name}}", "Dupont Marie")
        .replaceAll("{{customer_first_name}}", "Marie")
        .replaceAll("{{customer_last_name}}", "Dupont")
        .replaceAll("{{customer_email}}", "marie.dupont@email.com")
        .replaceAll("{{customer_phone}}", "+33 1 23 45 67 89")
        .replaceAll("{{order_number}}", "CMD-2026-001")
        .replaceAll("{{order_date}}", new Date().toLocaleDateString("fr-FR"))
        .replaceAll("{{order_total}}", "150,00 €")
        .replaceAll("{{company_name}}", "Ma Société")
        .replaceAll("{{company_email}}", "contact@masociete.com")
        .replaceAll("{{company_phone}}", "+33 1 23 45 67 89")
        .replaceAll("{{company_address}}", "123 Rue de la Paix, 75001 Paris")
        .replaceAll("{{current_date}}", new Date().toLocaleDateString("fr-FR"))
        .replaceAll("{{current_time}}", new Date().toLocaleTimeString("fr-FR"));

      if (autoWrap) {
        // Fonction pour diviser le texte en lignes selon la largeur disponible
        const wrapText = (text: string, maxWidth: number): string[] => {
          const words = text.split(" ");
          const lines: string[] = [];
          let currentLine = "";

          for (const word of words) {
            const testLine = currentLine + (currentLine ? " " : "") + word;
            const metrics = ctx.measureText(testLine);

            if (metrics.width > maxWidth && currentLine) {
              lines.push(currentLine);
              currentLine = word;
            } else {
              currentLine = testLine;
            }
          }

          if (currentLine) {
            lines.push(currentLine);
          }

          return lines;
        };

        // Gérer les sauts de ligne existants (\n)
        const paragraphs = processedText.split("\n");
        let y = 25;

        paragraphs.forEach((paragraph: string) => {
          if (paragraph.trim()) {
            const lines = wrapText(paragraph, element.width - 20); // Marge de 10px de chaque côté
            lines.forEach((line: string) => {
              ctx.fillText(line, 10, y);
              y += fontSize + 4; // Espacement entre lignes
            });
          } else {
            y += fontSize + 4; // Ligne vide
          }
        });
      } else {
        // Comportement original : gérer uniquement les \n existants
        const lines = processedText.split("\n");
        let y = 25;
        lines.forEach((line: string) => {
          ctx.fillText(line, 10, y);
          y += fontSize + 4;
        });
      }
    },
    [],
  ); // No deps - pure function

  // ✅ BUGFIX-007: Memoize drawMentions to prevent recreation on every render
  const drawMentions = useCallback(
    (ctx: CanvasRenderingContext2D, element: Element) => { // NOSONAR — complexité inhérente au rendu des mentions
      const props = element as MentionsElement;
      const fontSizeRaw = (props.fontSize || 10) as number | string;

      // ✅ BUGFIX-021: Robust font size parsing for various formats
      let fontSize: number;
      if (typeof fontSizeRaw === "number") {
        fontSize = fontSizeRaw;
      } else if (typeof fontSizeRaw === "string") {
        // Try removing 'px', 'em', 'rem', 'pt' suffixes
        const numStr = fontSizeRaw.replaceAll(/px|em|rem|pt|%/g, "").trim();
        fontSize = Number.parseFloat(numStr) || 10;
        // If it's 'em' or 'rem', convert to approximate px (1em ≈ 16px)
        if (fontSizeRaw.includes("em") || fontSizeRaw.includes("rem")) {
          fontSize = fontSize * 16;
        }
      } else {
        fontSize = 10;
      }

      // Ensure fontSize is reasonable
      fontSize = Math.max(6, Math.min(72, fontSize));

      const fontFamily = props.fontFamily || "Arial";
      const fontWeight = props.fontWeight || "normal";
      const fontStyle = props.fontStyle || "normal";
      const textAlign = props.textAlign || "left";
      const text =
        props.text ||
        "SARL au capital de 10 000€ - RCS Lyon 123 456 789\nTVA FR 12 345 678 901 - SIRET 123 456 789 00012\ncontact@maboutique.com - +33 4 12 34 56 78";
      const showSeparator = props.showSeparator !== false;
      const separatorStyle = props.separatorStyle || "solid";
      const separatorColor = props.separatorColor || "#e5e7eb";
      const separatorWidth = props.separatorWidth || 1;
      const theme = (props.theme || "legal") as keyof typeof themes;

      // Définition des thèmes pour les mentions
      const themes = {
        legal: {
          backgroundColor: "#ffffff",
          borderColor: "#6b7280",
          textColor: "#374151",
          headerTextColor: "#111827",
        },
        subtle: {
          backgroundColor: "#f9fafb",
          borderColor: "#e5e7eb",
          textColor: "#6b7280",
          headerTextColor: "#374151",
        },
        minimal: {
          backgroundColor: "#ffffff",
          borderColor: "#f3f4f6",
          textColor: "#9ca3af",
          headerTextColor: "#6b7280",
        },
      };

      const currentTheme = themes[theme] || themes.legal;

      // Utiliser les couleurs personnalisées si définies, sinon utiliser le thème
      const bgColor = normalizeColor(
        props.backgroundColor || currentTheme.backgroundColor,
      );
      const txtColor = normalizeColor(
        props.textColor || currentTheme.textColor,
      );

      // Appliquer le fond seulement si showBackground est activé
      if (props.showBackground === true) {
        ctx.fillStyle = bgColor;
        ctx.fillRect(0, 0, element.width, element.height);
      }

      ctx.fillStyle = txtColor;

      let y = 15;

      // Dessiner le séparateur si activé
      if (showSeparator) {
        ctx.strokeStyle = normalizeColor(separatorColor);
        ctx.lineWidth = separatorWidth;

        if (separatorStyle === "double") {
          ctx.beginPath();
          ctx.moveTo(10, y - 5);
          ctx.lineTo(element.width - 10, y - 5);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(10, y - 2);
          ctx.lineTo(element.width - 10, y - 2);
          ctx.stroke();
        } else {
          ctx.setLineDash(getLineDashPattern(separatorStyle));
          ctx.beginPath();
          ctx.moveTo(10, y - 5);
          ctx.lineTo(element.width - 10, y - 5);
          ctx.stroke();
          ctx.setLineDash([]); // Reset line dash
        }

        y += 10;
      }

      ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
      ctx.textAlign = textAlign as CanvasTextAlign;

      // Fonction de wrapping du texte
      const wrapText = (text: string, maxWidth: number): string[] => { // NOSONAR — complexité inhérente au wrapping de texte
        if (!text) return [""];

        // Traiter chaque paragraphe séparément (séparé par \n)
        const paragraphs = text.split("\n");
        const wrappedParagraphs: string[] = [];

        for (const paragraph of paragraphs) {
          if (paragraph.trim() === "") {
            // Ligne vide (séparateur), on la garde telle quelle
            wrappedParagraphs.push("");
            continue;
          }

          // Wrapper le paragraphe comme avant
          const words = paragraph.split(" ");
          const lines: string[] = [];
          let currentLine = "";

          for (const word of words) {
            const testLine = currentLine ? currentLine + " " + word : word;
            const metrics = ctx.measureText(testLine);

            if (metrics.width > maxWidth && currentLine) {
              // Le mot ne rentre pas, on passe à la ligne
              lines.push(currentLine);
              currentLine = word;
            } else {
              currentLine = testLine;
            }
          }

          if (currentLine) {
            lines.push(currentLine);
          }

          wrappedParagraphs.push(...lines);
        }

        return wrappedParagraphs;
      };

      // Wrapper le texte selon la largeur disponible
      const maxWidth = element.width - 20; // Marge de 20px
      const wrappedLines = wrapText(text, maxWidth);

      // Calculer le nombre maximum de lignes qui peuvent tenir
      // Utiliser un line-height fixe: 1.1 * fontSize
      const lineHeight = fontSize * 1.1;
      const maxLines = Math.floor(
        (element.height - y) / lineHeight,
      );

      // Rendre seulement les lignes qui tiennent
      wrappedLines.slice(0, maxLines).forEach((line: string, index: number) => {
        const x = getAlignedX(textAlign, element.width);
        const lineY = y + index * lineHeight;
        ctx.fillText(line, x, lineY);
      });
    },
    [],
  ); // No deps - pure function

  // ✅ BUGFIX-001/004: Memoize drawElement but pass state as parameter to avoid dependency cycle
  const drawElement = useCallback(
    (
      ctx: CanvasRenderingContext2D,
      element: Element,
      currentState: BuilderState,
    ) => {
      // Vérifier si l'élément est visible
      if (element.visible === false) {
        debugLog(
          `[Canvas] Skipping invisible element: ${element.type} (${element.id})`,
        );
        return;
      }

      debugLog(
        `[Canvas] Drawing element: ${element.type} (${
          element.id
        }) - Position: (${element.x}, ${element.y}), Size: ${element.width}x${
          element.height
        }, Rotation: ${element.rotation || 0}°`,
      );

      ctx.save();

      // Appliquer transformation de l'élément
      if (element.rotation) {
        // Rotation autour du centre de l'élément
        const centerX = element.width / 2;
        const centerY = element.height / 2;
        ctx.translate(element.x + centerX, element.y + centerY);
        ctx.rotate((element.rotation * Math.PI) / 180);
        ctx.translate(-centerX, -centerY);
      } else {
        // Pas de rotation, translation normale
        ctx.translate(element.x, element.y);
      }

      // ✅ NOUVELLE LIGNE: Appliquer le clipping rectangle pour limiter le contenu
      applyClippingPath(ctx, element);

      // Dessiner selon le type d'élément
      switch (element.type) {
        case "rectangle":
          debugLog(`[Canvas] Rendering rectangle element: ${element.id}`);
          drawRectangle(ctx, element);
          break;
        case "circle":
          debugLog(`[Canvas] Rendering circle element: ${element.id}`);
          drawCircle(ctx, element);
          break;
        case "text":
          debugLog(`[Canvas] Rendering text element: ${element.id}`);
          drawText(ctx, element);
          break;
        case "line":
          debugLog(`[Canvas] Rendering line element: ${element.id}`);
          drawLine(ctx, element);
          break;
        case "product_table":
          debugLog(`[Canvas] Rendering product table element: ${element.id}`);
          drawProductTable(
            ctx,
            element,
            currentState,
            imageCache,
            setImageLoadCount,
          );
          break;
        case "customer_info":
          debugLog(`[Canvas] Rendering customer info element: ${element.id}`);
          drawCustomerInfo(ctx, element, currentState);
          break;
        case "company_info":
          debugLog(`[Canvas] Rendering company info element: ${element.id}`);
          drawCompanyInfo(ctx, element, currentState);
          break;
        case "company_logo":
          debugLog(`[Canvas] Rendering company logo element: ${element.id}`);
          drawCompanyLogo(ctx, element);
          break;
        case "woocommerce_order_date":
          debugLog(
            `[Canvas] Rendering woocommerce order date element: ${element.id}`,
          );
          drawWoocommerceOrderDate(ctx, element, currentState);
          break;
        case "woocommerce_invoice_number":
          debugLog(
            `[Canvas] Rendering woocommerce invoice number element: ${element.id}`,
          );
          drawWoocommerceInvoiceNumber(ctx, element, currentState);
          break;
        case "document_type":
          debugLog(`[Canvas] Rendering document type element: ${element.id}`);
          drawDocumentType(ctx, element, currentState);
          break;
        case "dynamic_text":
          debugLog(`[Canvas] Rendering dynamic text element: ${element.id}`);
          drawDynamicText(ctx, element);
          break;
        case "mentions":
          debugLog(`[Canvas] Rendering mentions element: ${element.id}`);
          drawMentions(ctx, element);
          break;
        case "image":
          debugLog(`[Canvas] Rendering image element: ${element.id}`);
          drawImage(ctx, element, imageCache);
          break;
        default: {
          // Élément de type inconnu - dessiner un rectangle simple
          const unknownElement = element as BaseElement;
          debugWarn(
            `[Canvas] Unknown element type: ${unknownElement.type} for element ${unknownElement.id}`,
          );
          // Élément générique - dessiner un rectangle simple
          ctx.strokeStyle = normalizeColor("#000000");
          ctx.lineWidth = 1;
          ctx.strokeRect(0, 0, unknownElement.width, unknownElement.height);
        }
      }
      ctx.restore();
    },
    [drawCompanyLogo, drawDynamicText, drawMentions],
  ); // ✅ BUGFIX-007: Include memoized draw functions

  // Fonction pour dessiner la sélection
  const drawSelection = useCallback(
    (
      ctx: CanvasRenderingContext2D,
      selectedIds: string[],
      elements: Element[],
    ) => {
      const selectedElements = elements.filter((el) =>
        selectedIds.includes(el.id),
      );
      if (selectedElements.length === 0) {
        debugLog("[Canvas] Selection cleared - no elements selected");
        return;
      }

      debugLog(
        `[Canvas] Drawing selection for ${selectedElements.length} element(s):`,
        selectedIds,
      );

      // Calculer les bounds de sélection
      let minX = Infinity,
        minY = Infinity,
        maxX = -Infinity,
        maxY = -Infinity;

      selectedElements.forEach((el) => {
        minX = Math.min(minX, el.x);
        minY = Math.min(minY, el.y);
        maxX = Math.max(maxX, el.x + el.width);
        maxY = Math.max(maxY, el.y + el.height);
      });

      // Rectangle de sélection
      ctx.strokeStyle = normalizeColor("#007acc");
      ctx.lineWidth = 1;
      ctx.setLineDash([5, 5]);
      ctx.strokeRect(minX - 2, minY - 2, maxX - minX + 4, maxY - minY + 4);

      // Poignées de redimensionnement (conditionnées par les settings)
      if (canvasSettings?.selectionShowHandles) {
        const handleSize = 6;
        ctx.fillStyle = normalizeColor("#007acc");
        ctx.setLineDash([]);

        // Coins
        ctx.fillRect(
          minX - handleSize / 2,
          minY - handleSize / 2,
          handleSize,
          handleSize,
        );
        ctx.fillRect(
          maxX - handleSize / 2,
          minY - handleSize / 2,
          handleSize,
          handleSize,
        );
        ctx.fillRect(
          minX - handleSize / 2,
          maxY - handleSize / 2,
          handleSize,
          handleSize,
        );
        ctx.fillRect(
          maxX - handleSize / 2,
          maxY - handleSize / 2,
          handleSize,
          handleSize,
        );

        // Centres des côtés
        const midX = (minX + maxX) / 2;
        const midY = (minY + maxY) / 2;
        ctx.fillRect(
          midX - handleSize / 2,
          minY - handleSize / 2,
          handleSize,
          handleSize,
        );
        ctx.fillRect(
          midX - handleSize / 2,
          maxY - handleSize / 2,
          handleSize,
          handleSize,
        );
        ctx.fillRect(
          minX - handleSize / 2,
          midY - handleSize / 2,
          handleSize,
          handleSize,
        );
        ctx.fillRect(
          maxX - handleSize / 2,
          midY - handleSize / 2,
          handleSize,
          handleSize,
        );
      }

      // Poignées de rotation (conditionnées par les settings)
      if (canvasSettings?.selectionRotationEnabled !== false) {
        const rotationHandleSize = 8;
        const rotationHandleDistance = 20;

        // Vérifier si au moins un élément a une rotation proche de 0°
        // Utiliser la même logique de normalisation que dans useCanvasInteraction.ts
        const hasZeroRotation = selectedElements.some((el) => {
          const rotation = (el as any).rotation || 0;
          // Normaliser l'angle entre -180° et 180° (même logique que le snap)
          let normalizedRotation = rotation % 360;
          if (normalizedRotation > 180) normalizedRotation -= 360;
          if (normalizedRotation < -180) normalizedRotation += 360;
          // Utiliser la tolérance pour 0° (10°) pour cohérence avec le snap ultra simple
          return Math.abs(normalizedRotation - 0) <= 10;
        });

        // Couleur différente pour indiquer le snap à 0°
        const handleColor = hasZeroRotation ? "#00cc44" : "#007acc";
        ctx.fillStyle = handleColor;
        ctx.strokeStyle = handleColor;
        ctx.lineWidth = 2;
        ctx.setLineDash([]);

        // Centre de la sélection
        const centerX = (minX + maxX) / 2;
        const centerY = (minY + maxY) / 2;

        // Position de la poignée de rotation (au-dessus du centre)
        const rotationHandleX = centerX;
        const rotationHandleY = minY - rotationHandleDistance;

        // Cercle pour la poignée de rotation
        ctx.beginPath();
        ctx.arc(
          rotationHandleX,
          rotationHandleY,
          rotationHandleSize / 2,
          0,
          2 * Math.PI,
        );
        ctx.fill();

        // Ligne reliant la poignée au centre
        ctx.beginPath();
        ctx.moveTo(centerX, centerY);
        ctx.lineTo(rotationHandleX, rotationHandleY);
        ctx.stroke();
      }

      // Afficher les dimensions pour chaque élément sélectionné
      selectedElements.forEach((el) => {
        if (selectedIds.includes(el.id)) {
          // Coordonnées
          const x = el.x;
          const y = el.y;
          const width = el.width;
          const height = el.height;

          // Afficher les dimensions en pixels sur le coin supérieur droit
          ctx.font = "11px Arial";
          ctx.fillStyle = normalizeColor("#007acc");
          ctx.textAlign = "right";
          ctx.textBaseline = "top";

          const dimensionText = `${(width * 1).toFixed(1)}×${(
            height * 1
          ).toFixed(1)}px`;
          const padding = 4;
          const textWidth = ctx.measureText(dimensionText).width;

          // Fond blanc pour meilleure lisibilité
          ctx.fillStyle = "white";
          ctx.fillRect(
            x + width - textWidth - padding * 2,
            y - 20,
            textWidth + padding * 2,
            18,
          );

          // Texte
          ctx.fillStyle = normalizeColor("#007acc");
          ctx.font = "bold 11px Arial";
          ctx.fillText(dimensionText, x + width - padding, y - 16);
        }
      });
    },
    [canvasSettings],
  );

  // Fonctions pour gérer le menu contextuel
  const showContextMenu = useCallback(
    (x: number, y: number, elementId?: string) => {
      setContextMenu({
        isVisible: true,
        position: { x, y },
        elementId,
      });
    },
    [],
  );

  const hideContextMenu = useCallback(() => {
    setContextMenu((prev) => ({ ...prev, isVisible: false }));
  }, []);

  const handleContextMenuAction = useCallback(
    (action: string, elementId?: string) => { // NOSONAR — complexité inhérente au menu contextuel
      debugLog(
        `[Canvas] Context menu action: ${action} on element ${
          elementId || "none"
        }`,
      );
      if (!elementId) return;

      switch (action) {
        case "bring-to-front": {
          debugLog(`[Canvas] Bringing element ${elementId} to front`);
          const elementIndex = state.elements.findIndex(
            (el) => el.id === elementId,
          );
          if (elementIndex !== -1 && elementIndex < state.elements.length - 1) {
            const element = state.elements[elementIndex];
            const newElements = [
              ...state.elements.slice(0, elementIndex),
              ...state.elements.slice(elementIndex + 1),
              element,
            ];
            dispatch({ type: "SET_ELEMENTS", payload: newElements });
            dispatch({ type: "SET_TEMPLATE_MODIFIED", payload: true });
          }
          hideContextMenu();
          break;
        }
        case "send-to-back": {
          debugLog(`[Canvas] Sending element ${elementId} to back`);
          const elementIndex = state.elements.findIndex(
            (el) => el.id === elementId,
          );
          if (elementIndex !== -1 && elementIndex > 0) {
            const element = state.elements[elementIndex];
            const newElements = [
              element,
              ...state.elements.slice(0, elementIndex),
              ...state.elements.slice(elementIndex + 1),
            ];
            dispatch({ type: "SET_ELEMENTS", payload: newElements });
            dispatch({ type: "SET_TEMPLATE_MODIFIED", payload: true });
          }
          hideContextMenu();
          break;
        }
        case "bring-forward": {
          debugLog(`[Canvas] Bringing element ${elementId} forward`);
          const elementIndex = state.elements.findIndex(
            (el) => el.id === elementId,
          );
          if (elementIndex !== -1 && elementIndex < state.elements.length - 1) {
            const newElements = [...state.elements];
            // Échanger avec l'élément suivant (index + 1)
            [newElements[elementIndex], newElements[elementIndex + 1]] = [
              newElements[elementIndex + 1],
              newElements[elementIndex],
            ];
            dispatch({ type: "SET_ELEMENTS", payload: newElements });
            dispatch({ type: "SET_TEMPLATE_MODIFIED", payload: true });
          }
          hideContextMenu();
          break;
        }
        case "send-backward": {
          debugLog(`[Canvas] Sending element ${elementId} backward`);
          const elementIndex = state.elements.findIndex(
            (el) => el.id === elementId,
          );
          if (elementIndex > 0) {
            const newElements = [...state.elements];
            // Échanger avec l'élément précédent (index - 1)
            [newElements[elementIndex], newElements[elementIndex - 1]] = [
              newElements[elementIndex - 1],
              newElements[elementIndex],
            ];
            dispatch({ type: "SET_ELEMENTS", payload: newElements });
            dispatch({ type: "SET_TEMPLATE_MODIFIED", payload: true });
          }
          hideContextMenu();
          break;
        }
        case "duplicate": {
          debugLog(`[Canvas] Duplicating element ${elementId}`);
          // Dupliquer l'élément avec un nouvel ID et un léger décalage
          const element = state.elements.find((el) => el.id === elementId);
          if (element) {
            const duplicatedElement = {
              ...element,
              id: `element_${Date.now()}_${Math.random()
                .toString(36)
                .slice(2, 11)}`,
              x: element.x + 10,
              y: element.y + 10,
              createdAt: new Date(),
              updatedAt: new Date(),
            };
            dispatch({ type: "ADD_ELEMENT", payload: duplicatedElement });
          }
          break;
        }
        case "copy": {
          debugLog(`[Canvas] Copying element ${elementId}`);
          // Copier l'élément dans le presse-papiers interne
          const element = state.elements.find((el) => el.id === elementId);
          if (element) {
            // Copie non encore implémentée
          }
          break;
        }
        case "cut": {
          debugLog(`[Canvas] Cutting element ${elementId}`);
          // Couper l'élément (copier puis supprimer)
          const element = state.elements.find((el) => el.id === elementId);
          if (element) {
            // Coupe non encore implémentée
            // dispatch({ type: 'REMOVE_ELEMENT', payload: elementId });
          }
          break;
        }
        case "reset-size": {
          debugLog(`[Canvas] Resetting size for element ${elementId}`);
          // Réinitialiser la taille de l'élément à ses dimensions par défaut
          const element = state.elements.find((el) => el.id === elementId);
          if (element) {
            const defaultSizes: {
              [key: string]: { width: number; height: number };
            } = {
              rectangle: { width: 100, height: 100 },
              circle: { width: 100, height: 100 },
              text: { width: 100, height: 30 },
              line: { width: 100, height: 2 },
              product_table: { width: 400, height: 200 },
              customer_info: { width: 300, height: 80 },
              company_info: { width: 300, height: 120 },
              company_logo: { width: 150, height: 80 },
              document_type: { width: 150, height: 30 },
              dynamic_text: { width: 200, height: 60 },
              mentions: { width: 400, height: 80 },
            };

            const defaultSize = defaultSizes[element.type] || {
              width: 100,
              height: 100,
            };
            dispatch({
              type: "UPDATE_ELEMENT",
              payload: {
                id: elementId,
                updates: {
                  width: defaultSize.width,
                  height: defaultSize.height,
                },
              },
            });
          }
          break;
        }
        case "fit-to-content": {
          debugLog(`[Canvas] Fitting element ${elementId} to content`);
          // Ajuster la taille de l'élément à son contenu (pour le texte principalement)
          const element = state.elements.find((el) => el.id === elementId);
          if (
            element &&
            (element.type === "text" || element.type === "dynamic_text")
          ) {
            // Pour les éléments texte, ajuster la hauteur selon le contenu
            const canvas = document.createElement("canvas");
            const ctx = canvas.getContext("2d");
            if (ctx) {
              const textElement = element as TextElement;
              const fontSize = textElement.fontSize || 14;
              const fontFamily = textElement.fontFamily || "Arial";
              const fontWeight = textElement.fontWeight || "normal";
              const fontStyle = textElement.fontStyle || "normal";

              ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;

              const text = textElement.text || "Texte";
              const lines = text.split("\n");
              const lineHeight = fontSize + 4;
              const contentHeight = lines.length * lineHeight + 20; // Marges

              dispatch({
                type: "UPDATE_ELEMENT",
                payload: {
                  id: elementId,
                  updates: { height: Math.max(contentHeight, 30) },
                },
              });
            }
          }
          break;
        }
        case "delete":
          debugLog(`[Canvas] Deleting element ${elementId}`);
          dispatch({ type: "REMOVE_ELEMENT", payload: elementId });
          break;
        case "lock": {
          debugLog(`[Canvas] Toggling lock for element ${elementId}`);
          // Basculer l'état verrouillé de l'élément
          const element = state.elements.find((el) => el.id === elementId);
          if (element) {
            dispatch({
              type: "UPDATE_ELEMENT",
              payload: {
                id: elementId,
                updates: { locked: !element.locked },
              },
            });
            dispatch({ type: "SET_TEMPLATE_MODIFIED", payload: true });
          }
          hideContextMenu();
          break;
        }
      }
    },
    [state.elements, dispatch, hideContextMenu],
  );

  const getContextMenuItems = useCallback(
    (elementId?: string): ContextMenuItem[] => {
      if (!elementId) {
        // Menu contextuel pour le canvas vide
        return [
          {
            id: "section-edit",
            section: "ÉDITION",
          },
          {
            id: "paste",
            label: "Coller",
            icon: "📋",
            shortcut: "Ctrl+V",
            action: () => {
              // Coller depuis le presse-papiers (non encore implémenté)
            },
            disabled: true, // Désactiver jusqu'à implémentation
          },
          {
            id: "select-all",
            label: "Tout sélectionner",
            icon: "☑️",
            shortcut: "Ctrl+A",
            action: () => {
              // Sélectionner tous les éléments
              const allElementIds = state.elements.map((el) => el.id);
              dispatch({ type: "SET_SELECTION", payload: allElementIds });
            },
          },
        ];
      }

      // Menu contextuel pour un élément
      const element = state.elements.find((el) => el.id === elementId);
      const isLocked = element?.locked || false;

      const items: ContextMenuItem[] = [
        // Section Ordre des calques
        {
          id: "section-layers",
          section: "CALQUES",
        },
        {
          id: "layer-order",
          label: "Ordre des calques",
          icon: "📚",
          children: [
            {
              id: "bring-to-front",
              label: "Premier plan",
              icon: "⬆️",
              shortcut: "Ctrl+↑",
              action: () =>
                handleContextMenuAction("bring-to-front", elementId),
              disabled: isLocked,
            },
            {
              id: "bring-forward",
              label: "Avancer d'un plan",
              icon: "↗️",
              shortcut: "Ctrl+Shift+↑",
              action: () => handleContextMenuAction("bring-forward", elementId),
              disabled: isLocked,
            },
            {
              id: "send-backward",
              label: "Reculer d'un plan",
              icon: "↙️",
              shortcut: "Ctrl+Shift+↓",
              action: () => handleContextMenuAction("send-backward", elementId),
              disabled: isLocked,
            },
            {
              id: "send-to-back",
              label: "Arrière plan",
              icon: "⬇️",
              shortcut: "Ctrl+↓",
              action: () => handleContextMenuAction("send-to-back", elementId),
              disabled: isLocked,
            },
          ],
        },
        { id: "separator1", separator: true },

        // Section Édition
        {
          id: "section-edit",
          section: "ÉDITION",
        },
        {
          id: "duplicate",
          label: "Dupliquer",
          icon: "📋",
          shortcut: "Ctrl+D",
          action: () => handleContextMenuAction("duplicate", elementId),
          disabled: isLocked,
          children: [
            {
              id: "duplicate-here",
              label: "Dupliquer ici",
              icon: "📋",
              action: () => handleContextMenuAction("duplicate", elementId),
              disabled: isLocked,
            },
            {
              id: "duplicate-multiple",
              label: "Dupliquer plusieurs...",
              icon: "📋📋",
              action: () =>
                handleContextMenuAction("duplicate-multiple", elementId),
              disabled: isLocked,
            },
          ],
        },
        {
          id: "clipboard",
          label: "Presse-papiers",
          icon: "📄",
          children: [
            {
              id: "copy",
              label: "Copier",
              icon: "📄",
              shortcut: "Ctrl+C",
              action: () => handleContextMenuAction("copy", elementId),
              disabled: false,
            },
            {
              id: "cut",
              label: "Couper",
              icon: "✂️",
              shortcut: "Ctrl+X",
              action: () => handleContextMenuAction("cut", elementId),
              disabled: isLocked,
            },
          ],
        },
        { id: "separator2", separator: true },

        // Section Taille
        {
          id: "section-size",
          section: "TAILLE",
        },
        {
          id: "reset-size",
          label: "Taille par défaut",
          icon: "📏",
          shortcut: "Ctrl+0",
          action: () => handleContextMenuAction("reset-size", elementId),
          disabled: isLocked,
        },
        {
          id: "fit-to-content",
          label: "Ajuster au contenu",
          icon: "📐",
          shortcut: "Ctrl+Shift+F",
          action: () => handleContextMenuAction("fit-to-content", elementId),
          disabled:
            isLocked ||
            !(element?.type === "text" || element?.type === "dynamic_text"),
        },
        { id: "separator3", separator: true },

        // Section État
        {
          id: "section-state",
          section: "ÉTAT",
        },
        {
          id: "lock",
          label: isLocked ? "Déverrouiller" : "Verrouiller",
          icon: isLocked ? "🔓" : "🔒",
          shortcut: isLocked ? "Ctrl+Shift+L" : "Ctrl+L",
          action: () => handleContextMenuAction("lock", elementId),
        },
        { id: "separator4", separator: true },

        // Section Danger
        {
          id: "section-danger",
          section: "SUPPRESSION",
        },
        {
          id: "delete",
          label: "Supprimer",
          icon: "🗑️",
          shortcut: "Suppr",
          action: () => handleContextMenuAction("delete", elementId),
          disabled: false,
        },
      ];

      return items;
    },
    [state.elements, handleContextMenuAction, dispatch],
  );

  // Fonction pour dessiner la grille
  const drawGrid = useCallback(
    (
      ctx: CanvasRenderingContext2D,
      w: number,
      h: number,
      size: number,
      color: string,
    ) => {
      ctx.strokeStyle = color;
      ctx.lineWidth = 1;

      for (let x = 0; x <= w; x += size) {
        ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, h);
        ctx.stroke();
      }

      for (let y = 0; y <= h; y += size) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(w, y);
        ctx.stroke();
      }
    },
    [],
  ); // No deps - pure function

  // Fonction pour dessiner les guides
  const drawGuides = useCallback(
    (
      ctx: CanvasRenderingContext2D,
      canvasWidth: number,
      canvasHeight: number,
    ) => {
      ctx.save();
      ctx.strokeStyle = normalizeColor("#007acc");
      ctx.lineWidth = 1;
      ctx.setLineDash([5, 5]);

      // Guide horizontal au milieu
      ctx.beginPath();
      ctx.moveTo(0, canvasHeight / 2);
      ctx.lineTo(canvasWidth, canvasHeight / 2);
      ctx.stroke();

      // Guide vertical au milieu
      ctx.beginPath();
      ctx.moveTo(canvasWidth / 2, 0);
      ctx.lineTo(canvasWidth / 2, canvasHeight);
      ctx.stroke();

      ctx.restore();
    },
    [],
  );

  // Gestionnaire de clic droit pour le canvas
  const handleCanvasContextMenu = useCallback(
    (event: MouseEvent<HTMLCanvasElement>) => {
      event.preventDefault();
      debugLog(
        `👆 Canvas: Context menu triggered at (${event.clientX}, ${event.clientY})`,
      );
      debugLog(
        `[Canvas] Context menu triggered at (${event.clientX}, ${event.clientY})`,
      );
      handleContextMenu(event, (x, y, elementId) => {
        debugLog(
          `📋 Canvas: Context menu callback - Element: ${
            elementId || "canvas"
          }, Position: (${x}, ${y})`,
        );
        debugLog(
          `[Canvas] Context menu callback - Element: ${
            elementId || "canvas"
          }, Position: (${x}, ${y})`,
        );
        showContextMenu(x, y, elementId);
      });
    },
    [handleContextMenu, showContextMenu],
  );

  // Fonction de rendu du canvas
  const renderCanvas = useCallback(() => { // NOSONAR — complexité inhérente au rendu du canvas
    const startTime = Date.now();
    renderCountRef.current += 1;

    debugLog(
      `🎨 Canvas: Render #${renderCountRef.current} started - Elements: ${state.elements.length}, Zoom: ${state.canvas.zoom}%, Selection: ${state.selection.selectedElements.length} items`,
    );

    debugLog(
      `[Canvas] Render #${renderCountRef.current} started - Elements: ${
        state.elements.length
      }, Zoom: ${state.canvas.zoom}%, Pan: (${state.canvas.pan.x.toFixed(
        1,
      )}, ${state.canvas.pan.y.toFixed(1)}), Selection: ${
        state.selection.selectedElements.length
      } items`,
    );

    const canvas = canvasRef.current;
    if (!canvas) {
      debugLog("❌ Canvas: Render cancelled - canvas ref is null");
      debugLog("[Canvas] Render cancelled - canvas ref is null");
      return;
    }

    const ctx = canvas.getContext("2d");
    if (!ctx) {
      debugLog("❌ Canvas: Render cancelled - canvas context unavailable");
      debugLog("[Canvas] Render cancelled - canvas context unavailable");
      return;
    }

    // Clear canvas with background color from settings (matching PDF background)
    const canvasBgColor = normalizeColor("#ffffff");
    debugLog(
      `🖌️ Canvas: Clearing canvas with background color: ${canvasBgColor}`,
    );
    debugLog(
      `[Canvas] Clearing canvas with background color: ${canvasBgColor}`,
    );
    ctx.fillStyle = canvasBgColor;
    ctx.fillRect(0, 0, width, height);

    // Note: Canvas border is now handled by CSS styling based on settings`n`n    // Appliquer transformation (pan uniquement - zoom géré par CSS)
    ctx.save();

    ctx.translate(state.canvas.pan.x, state.canvas.pan.y);

    // Note: Zoom is now handled by CSS display size, no need for ctx.scale()

    // NOTE: Les marges seront réactivées après que le rendu des éléments soit fixé
    // const showMargins = canvasSettings.showMargins;
    // if (showMargins && canvasSettings) {
    //   const marginTopPx = (canvasSettings.marginTop || 0) * 3.78;
    //   const marginLeftPx = (canvasSettings.marginLeft || 0) * 3.78;
    //   ctx.translate(marginLeftPx, marginTopPx);
    // }

    // Dessiner la grille si activée (utiliser les paramètres Canvas Settings et l'état du toggle)
    if (canUseGridNavigation && canvasSettings?.gridShow && state.canvas.showGrid) {
      drawGrid(
        ctx,
        width,
        height,
        canvasSettings?.gridSize || 20,
        canvasSettings?.gridColor || "#e0e0e0",
      );
    }

    // Dessiner les guides si activés (utiliser les paramètres Canvas Settings et l'état du template)
    if (canvasSettings?.guidesEnabled && state.template.showGuides) {
      drawGuides(ctx, width, height);
    }

    // Dessiner les éléments
    debugLog(
      `📝 Canvas: Rendering ${visibleElementsList.length} visible elements (lazy loading: ${lazyLoadingEnabled})`,
    );
    debugLog(
      `[Canvas] Rendering ${visibleElementsList.length} visible elements (lazy loading: ${lazyLoadingEnabled})`,
    );
    visibleElementsList.forEach((element) => {
      debugLog(
        `🎯 Canvas: Drawing element: ${element.type} (${element.id}) at (${element.x}, ${element.y}) ${element.width}x${element.height}`,
      );
      debugLog(
        `[Canvas] Drawing element: ${element.type} (${element.id}) at (${element.x}, ${element.y}) ${element.width}x${element.height}`,
      );
      drawElement(ctx, element, state); // ✅ BUGFIX-001/004: Pass state as parameter
    });

    // Dessiner la sélection temporaire (rectangle/lasso en cours)
    if (selectionState?.isSelecting) {
      if (
        selectionState.selectionMode === "rectangle" &&
        selectionState.selectionRect.width > 0 &&
        selectionState.selectionRect.height > 0
      ) {
        // Dessiner le rectangle de sélection
        ctx.save();
        ctx.strokeStyle = normalizeColor("#0066cc");
        ctx.lineWidth = 1;
        ctx.setLineDash([5, 5]);
        ctx.strokeRect(
          selectionState.selectionRect.x,
          selectionState.selectionRect.y,
          selectionState.selectionRect.width,
          selectionState.selectionRect.height,
        );

        // Remplir avec une couleur semi-transparente
        ctx.fillStyle = "rgba(0, 102, 204, 0.1)";
        ctx.fillRect(
          selectionState.selectionRect.x,
          selectionState.selectionRect.y,
          selectionState.selectionRect.width,
          selectionState.selectionRect.height,
        );
        ctx.restore();
      } else if (
        selectionState.selectionMode === "lasso" &&
        selectionState.selectionPoints.length > 1
      ) {
        // Dessiner le lasso
        ctx.save();
        ctx.strokeStyle = normalizeColor("#0066cc");
        ctx.lineWidth = 1;
        ctx.setLineDash([5, 5]);
        ctx.beginPath();
        ctx.moveTo(
          selectionState.selectionPoints[0].x,
          selectionState.selectionPoints[0].y,
        );
        for (let i = 1; i < selectionState.selectionPoints.length; i++) {
          ctx.lineTo(
            selectionState.selectionPoints[i].x,
            selectionState.selectionPoints[i].y,
          );
        }
        ctx.closePath();
        ctx.stroke();

        // Remplir avec une couleur semi-transparente
        ctx.fillStyle = "rgba(0, 102, 204, 0.1)";
        ctx.fill();
        ctx.restore();
      }
    }

    // Dessiner la sélection
    if (state.selection.selectedElements.length > 0) {
      drawSelection(ctx, state.selection.selectedElements, state.elements);
    }

    ctx.restore();

    // Log rendu terminé avec métriques de performance
    const renderTime = Date.now() - startTime;
    debugLog(
      `✅ Canvas: Render #${renderCountRef.current} completed in ${renderTime}ms - ${state.elements.length} elements rendered`,
    );
    debugLog(
      `[Canvas] Render #${renderCountRef.current} completed in ${renderTime}ms - ${state.elements.length} elements rendered`,
    );

    // Log avertissement si le rendu prend trop de temps
    if (renderTime > 100) {
      debugWarn(
        `⚠️ Canvas: Slow render detected: ${renderTime}ms for ${state.elements.length} elements`,
      );
      debugWarn(
        `[Canvas] Slow render detected: ${renderTime}ms for ${state.elements.length} elements`,
      );
    }
  }, [
    width,
    height,
    canvasSettings,
    state,
    drawElement,
    drawGrid,
    drawGuides,
    selectionState,
    drawSelection,
    visibleElementsList,
    lazyLoadingEnabled,
  ]); // ✅ Include memoized drawGrid and drawGuides

  // Redessiner quand l'état change - CORRECTION: Supprimer renderCanvas des dépendances pour éviter les boucles
  useEffect(() => {
    debugLog(
      `🔄 Canvas: State change detected - triggering render. Elements: ${state.elements.length}, Selection: ${state.selection.selectedElements.length}, Zoom: ${state.canvas.zoom}%`,
    );
    debugLog(
      `[Canvas] State change detected - triggering render. Elements: ${state.elements.length}, Selection: ${state.selection.selectedElements.length}, Zoom: ${state.canvas.zoom}%`,
    );
    renderCanvas();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    state,
    canvasSettings,
    imageLoadCount,
    selectionState?.updateTrigger,
    visibleElementsList,
  ]); // Dépendances directes au lieu de renderCanvas

  // Rendu initial - REMOVED: Redondant avec l'effet principal ci-dessus

  // ✅ Force initial render when elements first load (for cached images)
  useEffect(() => {
    if (state.elements.length > 0 && !initialImageCheckDoneRef.current) {
      debugLog(
        `[Canvas] Initial elements loaded (${state.elements.length} elements) - scheduling image loading checks`,
      );
      initialImageCheckDoneRef.current = true;

      // Force multiple renders to ensure images are displayed
      const timer1 = setTimeout(() => {
        debugLog(`[Canvas] Image loading check #1`);
        setImageLoadCount((prev) => prev + 1);
      }, 100);

      const timer2 = setTimeout(() => {
        debugLog(`[Canvas] Image loading check #2`);
        setImageLoadCount((prev) => prev + 1);
      }, 500);

      const timer3 = setTimeout(() => {
        debugLog(`[Canvas] Image loading check #3`);
        setImageLoadCount((prev) => prev + 1);
      }, 1000);

      // Add longer timeout for slow-loading images
      const timer4 = setTimeout(() => {
        debugLog(`[Canvas] Image loading check #4 (final)`);
        setImageLoadCount((prev) => prev + 1);
      }, 2000);

      return () => {
        clearTimeout(timer1);
        clearTimeout(timer2);
        clearTimeout(timer3);
        clearTimeout(timer4);
      };
    }
  }, [state.elements.length]);

  // ✅ CORRECTION 1: Ajouter beforeunload event pour avertir des changements non-sauvegardés
  useEffect(() => {
    const handleBeforeUnload = (e: Event) => {
      if (state.template.isModified) {
        e.preventDefault();
      }
    };

    window.addEventListener("beforeunload", handleBeforeUnload, {
      passive: true,
    });
    return () => window.removeEventListener("beforeunload", handleBeforeUnload);
  }, [state.template.isModified]);

  // 🎯 Initialize monitoring dashboard
  useEffect(() => {
    CanvasMonitoringDashboard.initialize();
    // Silent initialization
  }, []);

  // Calculate border style based on canvas settings and license
  const { isPremium, canUseGridNavigation } = getEditorFeatureFlags();

  let borderStyle = "none";
  if (isDragOver) {
    borderStyle = "2px solid #007acc";
  } else if (isPremium && canvasSettings?.borderWidth && canvasSettings.borderWidth > 0) {
    borderStyle = `${canvasSettings.borderWidth}px solid ${canvasSettings.borderColor || DEFAULT_SETTINGS.borderColor}`;
  }

  let boxShadow = "none";
  if (isDragOver) {
    boxShadow = "0 0 0 2px rgba(0, 122, 204, 0.2)";
  } else if (canvasSettings?.shadowEnabled) {
    boxShadow = "2px 8px 16px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.2)";
  }

  const containerBackground = isPremium
    ? canvasSettings?.containerBackgroundColor || DEFAULT_SETTINGS.containerBackgroundColor
    : DEFAULT_SETTINGS.containerBackgroundColor;

  // Calculate canvas display size based on zoom
  const zoomScale = state.canvas.zoom / 100;
  const displayWidth = width * zoomScale;
  const displayHeight = height * zoomScale;

  debugLog(
    `[Canvas] Rendering canvas element - Display size: ${displayWidth}x${displayHeight}, Border: ${borderStyle}, Drag over: ${isDragOver}`,
  );

  // ✅ Exposer une fonction pour capturer l'image du canvas
  useEffect(() => {
    const captureCanvasPreview = () => {
      if (canvasRef.current) {
        try {
          // Retourner l'image PNG du canvas en base64
          return canvasRef.current.toDataURL("image/png");
        } catch (error) {
          debugError("Erreur lors de la capture du canvas:", error);
          return null;
        }
      }
      return null;
    };

    // Exposer la fonction globalement
    (globalThis as any).pdfBuilderCaptureCanvasPreview = captureCanvasPreview;

    return () => {
      delete (globalThis as any).pdfBuilderCaptureCanvasPreview;
    };
  }, []);

  return (
    <>
      <div
        ref={canvasWrapperRef}
        role="application"
        aria-label="Éditeur de canvas PDF"
        onDragEnter={handleDragEnter}
        onDrop={handleDrop}
        onDragOver={handleDragOver}
        onDragLeave={handleDragLeave}
        style={{
          display: "inline-flex",
          alignItems: "center",
          justifyContent: "center",
          position: "relative",
          border: borderStyle,
          borderRadius: "4px",
          backgroundColor: containerBackground,
          transition: "border-color 0.2s ease, box-shadow 0.2s ease",
          boxShadow: boxShadow,
          pointerEvents: "auto",
        }}
      >
        <canvas
          ref={canvasRef}
          width={width}
          height={height}
          className={className}
          onClick={handleCanvasClick}
          onMouseDown={handleMouseDown}
          onMouseMove={handleMouseMove}
          onMouseUp={handleMouseUp}
          onContextMenu={handleCanvasContextMenu}
          style={{
            width: `${displayWidth}px`,
            height: `${displayHeight}px`,
            cursor: "crosshair",
            backgroundColor: "#ffffff",
            border: "none",
            boxShadow: canvasSettings?.shadowEnabled
              ? "2px 8px 16px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.2)"
              : "none",
            display: "block",
          }}
        />
        {canvasSettings?.showMargins &&
          (() => {
            const mTop = (canvasSettings.marginTop ?? 0) * zoomScale;
            const mBottom = (canvasSettings.marginBottom ?? 0) * zoomScale;
            const mLeft = (canvasSettings.marginLeft ?? 0) * zoomScale;
            const mRight = (canvasSettings.marginRight ?? 0) * zoomScale;
            const lineColor = "rgba(0, 120, 215, 0.6)";
            const commonStyle: React.CSSProperties = {
              position: "absolute",
              pointerEvents: "none",
              backgroundColor: lineColor,
              zIndex: 10,
            };
            return (
              <div
                style={{
                  position: "absolute",
                  top: 0,
                  left: 0,
                  width: `${displayWidth}px`,
                  height: `${displayHeight}px`,
                  pointerEvents: "none",
                  zIndex: 10,
                  overflow: "hidden",
                }}
              >
                {/* Ligne haut */}
                <div
                  style={{
                    ...commonStyle,
                    top: `${mTop}px`,
                    left: 0,
                    right: 0,
                    height: "1px",
                  }}
                />
                {/* Ligne bas */}
                <div
                  style={{
                    ...commonStyle,
                    top: `${displayHeight - mBottom}px`,
                    left: 0,
                    right: 0,
                    height: "1px",
                  }}
                />
                {/* Ligne gauche */}
                <div
                  style={{
                    ...commonStyle,
                    left: `${mLeft}px`,
                    top: 0,
                    bottom: 0,
                    width: "1px",
                  }}
                />
                {/* Ligne droite */}
                <div
                  style={{
                    ...commonStyle,
                    left: `${displayWidth - mRight}px`,
                    top: 0,
                    bottom: 0,
                    width: "1px",
                  }}
                />
              </div>
            );
          })()}
      </div>
      {contextMenu.isVisible && (
        <ContextMenu
          items={getContextMenuItems(contextMenu.elementId)}
          position={contextMenu.position}
          onClose={hideContextMenu}
          isVisible={contextMenu.isVisible}
        />
      )}
    </>
  );
};
