/**
 * =========================================================================
 * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0
 * Composants : Messagerie Interne Pro (Architecture Classique et Memo)
 * Gère jusqu'à 20 pièces jointes par message.
 * =========================================================================
 */

const { useState, useEffect, useRef, memo, useCallback } = React;
const { MessageSquare, ArrowUp, ArrowDown, Loader, AlertTriangle, Image, FileText, X, ArrowLeft, Download } = window;

// -------------------------------------------------------------------------
// UTILITAIRE PDF.JS DYNAMIQUE (Charge une seule fois la librairie)
// -------------------------------------------------------------------------
let pdfJsLoaded = false;
let pdfJsLoadingPromise = null;

const loadPdfJs = () => {
    if (pdfJsLoaded) {
        return Promise.resolve();
    }
    
    if (pdfJsLoadingPromise) {
        return pdfJsLoadingPromise;
    }

    pdfJsLoadingPromise = new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
        
        script.onload = () => {
            window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
            pdfJsLoaded = true;
            resolve();
        };
        
        script.onerror = reject;
        document.head.appendChild(script);
    });
    
    return pdfJsLoadingPromise;
};

// Composant miniature PDF (Avec File d'attente intelligente en arrière-plan)
const PdfThumbnail = memo(({ url }) => {
    const containerRef = useRef(null);
    const canvasRef = useRef(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(false);
    const [isVisible, setIsVisible] = useState(false);

    // 1. Détection de visibilité et File d'attente d'arrière-plan
    useEffect(() => {
        let isMounted = true;
        let observerDisposed = false;

        const observer = new IntersectionObserver(
            ([entry]) => {
                if (entry.isIntersecting) {
                    if (isMounted) setIsVisible(true);
                    observer.disconnect();
                    observerDisposed = true;
                }
            },
            { rootMargin: '300px' }
        );

        if (containerRef.current) {
            observer.observe(containerRef.current);
        }

        // Gestionnaire de file d'attente pour charger les PDF hors-champ
        if (!window.pdfIdleQueue) window.pdfIdleQueue = [];
        
        const queueItem = () => {
            if (isMounted && !observerDisposed) {
                setIsVisible(true);
                observer.disconnect();
                observerDisposed = true;
            }
        };
        
        window.pdfIdleQueue.push(queueItem);

        // Démarre le processeur de fond s'il n'est pas actif
        if (!window.pdfIdleProcessorStarted) {
            window.pdfIdleProcessorStarted = true;
            
            const processIdleQueue = () => {
                if (window.pdfIdleQueue && window.pdfIdleQueue.length > 0) {
                    const task = window.pdfIdleQueue.shift();
                    task();
                }
                // Exécute 1 rendu caché toutes les 800ms pour ne pas surcharger le CPU
                setTimeout(processIdleQueue, 800); 
            };
            
            // Attendre 3 secondes (laisser la priorité absolue aux messages visibles)
            setTimeout(processIdleQueue, 3000); 
        }

        return () => {
            isMounted = false;
            observer.disconnect();
            observerDisposed = true;
            if (window.pdfIdleQueue) {
                const idx = window.pdfIdleQueue.indexOf(queueItem);
                if (idx > -1) window.pdfIdleQueue.splice(idx, 1);
            }
        };
    }, []);

    // 2. Rendu effectif du Canvas
    useEffect(() => {
        if (!isVisible) return;

        let isMounted = true;
        let renderTask = null;

        const renderPage = async () => {
            try {
                await loadPdfJs();
                
                if (!isMounted) return;

                const loadingTask = window.pdfjsLib.getDocument(url);
                const pdf = await loadingTask.promise;
                const page = await pdf.getPage(1);
                
                const viewport = page.getViewport({ scale: 1 });
                const canvas = canvasRef.current;
                
                if (!canvas || !isMounted) return;
                
                const context = canvas.getContext('2d', { alpha: false });
                
                const scale = 200 / viewport.width; 
                const scaledViewport = page.getViewport({ scale });
                
                canvas.width = Math.floor(scaledViewport.width);
                canvas.height = Math.floor(scaledViewport.height);
                
                renderTask = page.render({ canvasContext: context, viewport: scaledViewport });
                await renderTask.promise;
                
                if (isMounted) {
                    setLoading(false);
                }
            } catch (e) {
                if (isMounted) {
                    setError(true);
                    setLoading(false);
                }
            }
        };
        
        renderPage();
        
        return () => {
            isMounted = false;
            if (renderTask) {
                renderTask.cancel();
            }
        };
    }, [url, isVisible]);

    return (
        <div ref={containerRef} className="w-full h-32 bg-white flex items-center justify-center overflow-hidden relative">
            {loading && !error && (
                <Loader className="animate-spin text-slate-300 absolute" size={24} />
            )}
            
            {error && (
                <FileText className="text-slate-300 absolute" size={32} />
            )}
            
            <canvas 
                ref={canvasRef} 
                className={`w-full h-full object-contain ${loading || error ? 'opacity-0' : 'opacity-100'} transition-opacity duration-300`} 
            />
        </div>
    );
});

// Composant rendu PDF complet (Multipage)
const PdfFullViewer = memo(({ url }) => {
    const [pdf, setPdf] = useState(null);
    const [error, setError] = useState(false);

    useEffect(() => {
        let isMounted = true;
        
        loadPdfJs().then(() => {
            if (!isMounted) return;
            
            const loadingTask = window.pdfjsLib.getDocument(url);
            
            loadingTask.promise.then(doc => {
                if (isMounted) {
                    setPdf(doc);
                }
            }).catch(() => {
                if (isMounted) {
                    setError(true);
                }
            });
        }).catch(() => {
            if (isMounted) {
                setError(true);
            }
        });
        
        return () => { 
            isMounted = false; 
        };
    }, [url]);

    if (error) {
        return (
            <div className="text-slate-400 flex flex-col items-center justify-center h-full gap-4">
                <AlertTriangle size={48} />
                <span className="font-bold">Impossible de charger le PDF.</span>
            </div>
        );
    }
    
    if (!pdf) {
        return (
            <div className="text-slate-400 flex flex-col items-center justify-center h-full gap-4">
                <Loader className="animate-spin" size={48} />
                <span className="font-bold tracking-widest uppercase text-xs">Génération de l'aperçu...</span>
            </div>
        );
    }

    return (
        <div className="w-full h-full overflow-y-auto custom-scrollbar flex flex-col items-center gap-6 pb-10 pt-2 px-2 sm:px-4">
            {Array.from(new Array(pdf.numPages), (el, index) => (
                <PdfPage key={`page_${index + 1}`} pdf={pdf} pageNumber={index + 1} />
            ))}
        </div>
    );
});

const PdfPage = memo(({ pdf, pageNumber }) => {
    const containerRef = useRef(null);
    const canvasRef = useRef(null);
    const [loading, setLoading] = useState(true);
    const [isVisible, setIsVisible] = useState(false);
    
    // File d'attente d'arrière-plan pour les pages du lecteur PDF
    useEffect(() => {
        let isMounted = true;
        let observerDisposed = false;

        const observer = new IntersectionObserver(
            ([entry]) => {
                if (entry.isIntersecting) {
                    if (isMounted) setIsVisible(true);
                    observer.disconnect();
                    observerDisposed = true;
                }
            },
            { rootMargin: '500px' }
        );

        if (containerRef.current) {
            observer.observe(containerRef.current);
        }

        if (!window.pdfPageIdleQueue) window.pdfPageIdleQueue = [];
        
        const queueItem = () => {
            if (isMounted && !observerDisposed) {
                setIsVisible(true);
                observer.disconnect();
                observerDisposed = true;
            }
        };
        
        window.pdfPageIdleQueue.push(queueItem);

        if (!window.pdfPageProcessorStarted) {
            window.pdfPageProcessorStarted = true;
            const processIdleQueue = () => {
                if (window.pdfPageIdleQueue && window.pdfPageIdleQueue.length > 0) {
                    const task = window.pdfPageIdleQueue.shift();
                    task();
                }
                setTimeout(processIdleQueue, 600); // Rend une page toutes les 600ms en cache
            };
            setTimeout(processIdleQueue, 2000);
        }

        return () => {
            isMounted = false;
            observer.disconnect();
            observerDisposed = true;
            if (window.pdfPageIdleQueue) {
                const idx = window.pdfPageIdleQueue.indexOf(queueItem);
                if (idx > -1) window.pdfPageIdleQueue.splice(idx, 1);
            }
        };
    }, []);

    useEffect(() => {
        if (!isVisible) return;

        let isMounted = true;
        let renderTask = null;

        pdf.getPage(pageNumber).then(page => {
            if (!isMounted) return;
            
            const baseViewport = page.getViewport({ scale: 1.0 });
            
            const maxWidth = 768; 
            const pixelRatio = Math.min(window.devicePixelRatio || 1, 2); 
            const targetResolution = maxWidth * pixelRatio;
            
            let scale = targetResolution / baseViewport.width;
            
            if (scale > 2) scale = 2;
            if (scale < 0.5) scale = 0.5;
            
            const viewport = page.getViewport({ scale });
            const canvas = canvasRef.current;
            
            if (!canvas) return;
            
            const context = canvas.getContext('2d', { alpha: false }); 
            
            canvas.width = Math.floor(viewport.width);
            canvas.height = Math.floor(viewport.height);

            renderTask = page.render({ canvasContext: context, viewport });
            
            renderTask.promise.then(() => {
                if(isMounted) {
                    setLoading(false);
                }
            }).catch(() => {
                // Erreur silencieuse
            });
        });

        return () => {
            isMounted = false;
            if (renderTask) {
                renderTask.cancel();
            }
        };
    }, [pdf, pageNumber, isVisible]);

    return (
        <div ref={containerRef} className="relative shadow-2xl rounded-xl overflow-hidden bg-white w-full max-w-3xl min-h-[300px] shrink-0">
            {loading && (
                <div className="absolute inset-0 flex items-center justify-center bg-slate-100">
                    <Loader size={24} className="animate-spin text-slate-400" />
                </div>
            )}
            <canvas ref={canvasRef} className="block w-full h-auto" />
        </div>
    );
});

// Modale d'avertissement de modération pour les Riverains
const ModerationModal = ({ word, onModify, onRequestControl }) => (
    <div className="absolute inset-0 z-[999999] bg-slate-900/80 backdrop-blur-sm flex items-center justify-center p-4 animate-in fade-in">
        <div className="bg-white rounded-2xl p-6 max-w-md w-full shadow-2xl">
            <div className="flex items-center gap-3 text-amber-600 mb-4">
                <AlertTriangle size={28} />
                <h3 className="font-black text-xl text-slate-800">Avertissement</h3>
            </div>
            <div className="text-sm text-slate-600 mb-6 leading-relaxed">
                À cause de l'utilisation de l'expression <strong className="text-red-500">"{word}"</strong>, vous devez modifier votre message. 
                <br/><br/>
                Vous avez également la possibilité de faire contrôler votre message par le support technique avant envoi au client.
            </div>
            <div className="flex flex-col gap-3">
                <button 
                    type="button" 
                    onClick={onModify} 
                    className="w-full bg-slate-100 text-slate-700 py-3 rounded-xl font-bold hover:bg-slate-200 transition"
                >
                    Modifier mon message
                </button>
                <button 
                    type="button" 
                    onClick={onRequestControl} 
                    className="w-full bg-amber-500 text-white py-3 rounded-xl font-bold hover:bg-amber-600 transition shadow-md flex items-center justify-center gap-2"
                >
                    <window.Shield size={18}/> Envoyer pour contrôle
                </button>
            </div>
        </div>
    </div>
);

// Utilitaire de vérification de liste noire
const checkForBlacklist = (text, settings) => {
    if (!settings || !settings.blacklist) {
        return null;
    }
    
    const words = settings.blacklist.split(',')
        .map(w => w.trim().toLowerCase())
        .filter(w => w);
        
    const lowerText = text.toLowerCase();
    
    for (let w of words) {
        if (lowerText.includes(w)) {
            return w;
        }
    }
    
    return null;
};

// -------------------------------------------------------------------------
// COMPOSANT 1 : BULLE DE MESSAGE (MÉMORISÉE POUR PERFORMANCES EXTRÊMES)
// -------------------------------------------------------------------------

const MessageBubble = memo(({ msg, isMe, displayAuthor }) => {
    const [viewing, setViewing] = useState(null); 
    
    const attachments = [];
    const regex = /\[ATTACHMENT:([a-zA-Z0-9_-]+):(image|pdf)\]/g;
    let match;
    
    while ((match = regex.exec(msg.detail || '')) !== null) {
        attachments.push({ id: match[1], type: match[2] });
    }
    
    const textContent = (msg.detail || '').replace(regex, '').trim();
    // CORRECTION XSS STORED : On utilise authorType au lieu de author
    const isHtmlAllowed = msg.authorType === 'Admin' || msg.authorType === 'Système' || msg.authorType === 'Systeme';

    return (
        <div className={`flex flex-col w-full ${isMe ? 'items-end' : 'items-start'} mb-4`} style={{ overflowAnchor: 'none' }}>
            {msg.isAlert === 2 && (
                <div className="bg-amber-100 text-amber-800 text-[10px] font-bold px-2 py-1 rounded-t-lg mb-0.5 inline-flex items-center gap-1">
                    <AlertTriangle size={12}/> En attente de contrôle
                </div>
            )}
            
            <div className={`max-w-[85%] sm:max-w-[70%] p-3 rounded-2xl flex flex-col shadow-sm ${
                isMe 
                ? 'bg-emerald-600 text-white rounded-br-sm' 
                : 'bg-white border border-slate-200 text-slate-700 rounded-bl-sm'
            } ${msg.isAlert === 2 && !isMe ? 'opacity-60 border-amber-300 bg-amber-50' : ''}`}>
                
                {textContent && (
                    isHtmlAllowed ? (
                        <div 
                            dangerouslySetInnerHTML={{ 
                                __html: window.DOMPurify 
                                    ? window.DOMPurify.sanitize(textContent) 
                                    : textContent.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;')
                            }} 
                            className="text-sm leading-relaxed whitespace-pre-wrap admin-html" 
                        />
                    ) : (
                        <p className="text-sm leading-relaxed whitespace-pre-wrap break-words">{textContent}</p>
                    )
                )}
                
                {attachments.length > 0 && (
                    <div className={`mt-2 flex flex-wrap gap-2 ${textContent ? 'pt-2 border-t border-white/10' : ''}`}>
                        {attachments.map((att, i) => (
                            <React.Fragment key={i}>
                                {att.type === 'image' && (
                                    <div onClick={() => setViewing(att)} className="cursor-pointer shrink-0">
                                        <img 
                                            src={`?api=file/download&type=image&id=${att.id}`} 
                                            className={`rounded-lg max-h-48 object-contain border w-auto transition-transform hover:scale-[1.02] ${isMe ? 'border-white/20 bg-black/10' : 'border-slate-200/50 bg-slate-50'}`} 
                                            alt="Pièce jointe" 
                                            loading="lazy"
                                        />
                                    </div>
                                )}
                                {att.type === 'pdf' && (
                                    <div onClick={() => setViewing(att)} className={`cursor-pointer overflow-hidden rounded-xl border flex flex-col group text-left transition-transform hover:scale-[1.02] shrink-0 ${isMe ? 'border-white/20 bg-emerald-700/50' : 'border-slate-200 bg-white'} w-32`}>
                                        <PdfThumbnail url={`?api=file/download&type=pdf&id=${att.id}`} />
                                        <div className={`p-2 flex items-center gap-1.5 shrink-0 border-t ${isMe ? 'border-white/20 text-white' : 'border-slate-100 text-slate-700'}`}>
                                            <FileText size={12} className="shrink-0" />
                                            <span className="text-[9px] font-bold truncate">Aperçu PDF</span>
                                        </div>
                                    </div>
                                )}
                            </React.Fragment>
                        ))}
                    </div>
                )}
            </div>
            
            <span className="text-[10px] font-bold text-slate-400 mt-1 px-1 flex items-center gap-1">
                {displayAuthor} • {new Date(String(msg.created_at || '').replace(' ', 'T')).toLocaleString('fr-FR')}
                {msg.id.toString().startsWith('temp_') && <Loader size={10} className="animate-spin inline text-slate-300 ml-1" />}
            </span>

            {viewing && (
                <div className="fixed inset-0 z-[999999] bg-slate-900/95 backdrop-blur-sm flex flex-col animate-in fade-in duration-200" onClick={(e) => e.stopPropagation()}>
                    <div className="flex justify-between items-center p-4 bg-slate-900/50 text-white shrink-0 border-b border-white/10">
                        <button onClick={() => setViewing(null)} className="flex items-center gap-2 px-4 py-2 hover:bg-white/10 rounded-xl transition text-sm font-bold">
                            <ArrowLeft size={18}/> Retour
                        </button>
                        <a href={`?api=file/download&type=${viewing.type}&id=${viewing.id}`} download className="flex items-center gap-2 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 rounded-xl transition text-sm font-bold shadow-lg">
                            <Download size={18}/> <span className="hidden sm:inline">Télécharger</span>
                        </a>
                    </div>
                    <div className="flex-1 flex flex-col items-center justify-center p-2 sm:p-4 min-h-0 relative w-full">
                        {viewing.type === 'image' && (
                            <img src={`?api=file/download&type=image&id=${viewing.id}`} className="max-w-full max-h-full object-contain rounded-xl shadow-2xl shrink-0" alt="Aperçu" />
                        )}
                        {viewing.type === 'pdf' && (
                            <PdfFullViewer url={`?api=file/download&type=pdf&id=${viewing.id}&inline=1`} />
                        )}
                    </div>
                </div>
            )}
        </div>
    );
}, (prevProps, nextProps) => {
    return prevProps.msg.id === nextProps.msg.id && prevProps.msg.resolved === nextProps.msg.resolved && prevProps.msg.isAlert === nextProps.msg.isAlert;
});

// -------------------------------------------------------------------------
// COMPOSANT 2 : CONTENEUR DE TCHAT (ARCHITECTURE CLASSIQUE)
// -------------------------------------------------------------------------

const ChatBox = ({ messages, currentUserRole, panneauId, refreshData, onSend, clientName, settings = {} }) => {
    const containerRef = useRef(null);
    const textareaRef = useRef(null);
    const [draft, setDraft] = useState('');
    const [sending, setSending] = useState(false);
    const [selectedFiles, setSelectedFiles] = useState([]);
    const [fileError, setFileError] = useState('');
    const [isAtBottom, setIsAtBottom] = useState(true);
    const [isDragging, setIsDragging] = useState(false);
    const [dragCounter, setDragCounter] = useState(0);
    const [moderationWarning, setModerationWarning] = useState(null);

    useEffect(() => {
        if (textareaRef.current) {
            textareaRef.current.style.height = 'auto';
            textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
        }
    }, [draft]);

    useEffect(() => { 
        const authorRole = currentUserRole === 'admin' ? 'Admin' : 'Client';
        const unread = messages.filter(m => !m.resolved && m.author !== authorRole);
        
        if (unread.length > 0 && currentUserRole !== 'public' && currentUserRole !== 'riverain') { 
            fetch(window.ECO_CONFIG.apiBaseUrl + 'interactions/read', { 
                method: 'POST', 
                body: JSON.stringify({ panneauId, author: authorRole }) 
            }).then(() => { 
                if(refreshData) {
                    refreshData(true); 
                }
            }); 
            
            if (window.Notification && Notification.permission === 'granted' && localStorage.getItem('eco_push_enabled') === 'true') {
                const latestMsgId = unread[0].id || unread[0].created_at; 
                const lastNotified = sessionStorage.getItem('last_notified_msg_' + panneauId);
                
                if (lastNotified !== latestMsgId) {
                    new Notification("eco-panneau.fr", { body: "Nouveau message reçu.", icon: "/favicon.svg" });
                    sessionStorage.setItem('last_notified_msg_' + panneauId, latestMsgId);
                }
            }
        }
    }, [messages, panneauId, currentUserRole, refreshData]);

    const handleScroll = useCallback(() => {
        if (!containerRef.current) return;
        const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
        const isBottom = scrollHeight - scrollTop - clientHeight < 50;
        setIsAtBottom(isBottom);
    }, []);

    const scrollToBottom = useCallback((smooth = true) => {
        if (containerRef.current) {
            containerRef.current.scrollTo({ 
                top: containerRef.current.scrollHeight, 
                behavior: smooth ? 'smooth' : 'auto' 
            });
        }
    }, []);

    useEffect(() => {
        const timer = setTimeout(() => scrollToBottom(false), 100);
        return () => clearTimeout(timer);
    }, [scrollToBottom]);

    useEffect(() => {
        if (isAtBottom) {
            scrollToBottom(true);
        }
    }, [messages.length, isAtBottom, scrollToBottom]);

    const addFiles = (fileList) => {
        setFileError('');
        const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
        const files = Array.from(fileList).filter(f => validTypes.includes(f.type));
        
        if (files.length === 0) {
            return;
        }

        if (selectedFiles.length + files.length > 20) {
            setFileError("Maximum 20 fichiers autorisés par message.");
            return;
        }
        
        const validFiles = [];
        for (let f of files) {
            if (f.size > 5 * 1024 * 1024) {
                setFileError("Un ou plusieurs fichiers dépassent 5 Mo.");
                return;
            }
            validFiles.push(f);
        }
        
        setSelectedFiles(prev => [...prev, ...validFiles]);
    };

    const handleFileChange = (e) => {
        addFiles(e.target.files);
        e.target.value = null;
    };

    const handlePaste = (e) => {
        if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) {
            addFiles(e.clipboardData.files);
        }
    };

    const handleDragEnter = (e) => {
        e.preventDefault();
        e.stopPropagation();
        setDragCounter(prev => prev + 1);
        setIsDragging(true);
    };

    const handleDragLeave = (e) => {
        e.preventDefault();
        e.stopPropagation();
        setDragCounter(prev => {
            const nc = prev - 1;
            if (nc === 0) {
                setIsDragging(false);
            }
            return nc;
        });
    };

    const handleDragOver = (e) => {
        e.preventDefault();
        e.stopPropagation();
    };

    const handleDrop = (e) => {
        e.preventDefault();
        e.stopPropagation();
        setDragCounter(0);
        setIsDragging(false);
        
        if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
            addFiles(e.dataTransfer.files);
        }
    };

    const removeFile = (index) => {
        const newFiles = [...selectedFiles];
        newFiles.splice(index, 1);
        setSelectedFiles(newFiles);
    };

    const executeSend = async (isPendingControl = false) => {
        setSending(true);

        let finalDraft = draft.trim();
        
        for (let file of selectedFiles) {
            const fileType = file.type.includes('pdf') ? 'pdf' : 'image';
            try {
                const isPublic = currentUserRole === 'public' || currentUserRole === 'riverain';
                const fileId = await window.uploadFile(file, fileType, null, false, isPublic);
                finalDraft += `\n[ATTACHMENT:${fileId}:${fileType}]`;
            } catch(err) {
                setFileError("Erreur lors de l'upload : " + err.message);
                setSending(false);
                return;
            }
        }

        if (onSend) {
            await onSend(finalDraft, isPendingControl);
        }
        
        setDraft(''); 
        setSelectedFiles([]);
        setSending(false);
        setModerationWarning(null);

        setTimeout(() => scrollToBottom(true), 50);
    };

    const handleInitialSend = (e) => {
        e.preventDefault(); 
        
        if (!draft.trim() && selectedFiles.length === 0) {
            return;
        }
        
        if (currentUserRole === 'public' || currentUserRole === 'riverain') {
            const word = checkForBlacklist(draft, settings);
            if (word) {
                setModerationWarning(word);
                return;
            }
        }
        
        executeSend(false);
    };

    return (
        <div 
            className="flex flex-col h-full bg-slate-50 relative overflow-hidden"
            onDragEnter={handleDragEnter}
            onDragLeave={handleDragLeave}
            onDragOver={handleDragOver}
            onDrop={handleDrop}
        >
            {isDragging && (
                <div className="absolute inset-0 z-[100] bg-emerald-500/20 backdrop-blur-sm border-4 border-emerald-500 border-dashed rounded-2xl flex flex-col items-center justify-center pointer-events-none">
                    <div className="bg-white p-6 rounded-2xl shadow-xl flex flex-col items-center text-center">
                        <ArrowUp size={48} className="text-emerald-500 mb-4 animate-bounce" />
                        <span className="text-xl font-black text-slate-800">Déposez vos fichiers ici</span>
                        <span className="text-sm font-bold text-slate-500 mt-2">Images et PDF acceptés (max 20)</span>
                    </div>
                </div>
            )}

            {moderationWarning && (
                <ModerationModal 
                    word={moderationWarning} 
                    onModify={() => setModerationWarning(null)} 
                    onRequestControl={() => executeSend(true)} 
                />
            )}

            <div 
                ref={containerRef}
                onScroll={handleScroll}
                className="flex-1 min-h-0 overflow-y-auto p-4 flex flex-col custom-scrollbar relative"
            >
                {messages.map((msg) => {
                    const isMe = (currentUserRole === 'admin' && msg.author === 'Admin') || 
                                 (currentUserRole === 'client' && msg.author === 'Client') || 
                                 (currentUserRole === 'public' && msg.author !== 'Admin' && msg.author !== 'Client' && msg.author !== 'Système' && msg.author !== 'Systeme');

                    let displayAuthor = msg.author;
                    
                    if (panneauId === 'CONTACT_PUBLIC') {
                        if (msg.author === 'Admin') {
                            displayAuthor = 'Service client';
                        } else if (msg.author === 'Système' || msg.author === 'Systeme') {
                            displayAuthor = 'Système';
                        } else {
                            displayAuthor = `Visiteur ${msg.author}`;
                        }
                    } else {
                        if (msg.author === 'Admin') {
                            displayAuthor = 'Support';
                        } else if (msg.author === 'Client') {
                            displayAuthor = clientName || 'Client';
                        } else if (msg.author === 'Système' || msg.author === 'Systeme') {
                            displayAuthor = 'Système';
                        } else {
                            displayAuthor = 'Riverain';
                        }
                    }

                    return (
                        <MessageBubble 
                            key={msg.id} 
                            msg={msg} 
                            isMe={isMe} 
                            displayAuthor={displayAuthor} 
                        />
                    );
                })}

                {messages.length === 0 && (
                    <div className="text-center text-slate-400 text-sm font-medium italic my-auto">
                        Aucun message.
                    </div>
                )}
            </div>

            {!isAtBottom && messages.length > 0 && (
                <div className="absolute bottom-24 left-0 right-0 flex justify-center pointer-events-none z-10">
                    <button 
                        onClick={() => scrollToBottom(true)}
                        className="bg-slate-800/90 backdrop-blur text-white px-4 py-2 rounded-full shadow-lg text-xs font-bold pointer-events-auto hover:bg-slate-900 transition flex items-center gap-2 animate-in fade-in slide-in-from-bottom-2"
                    >
                        <ArrowDown size={14}/> Nouveaux messages
                    </button>
                </div>
            )}
            
            <div className="flex flex-col w-full bg-white border-t border-slate-200 shrink-0 z-20 transition-colors">
                {fileError && (
                    <p className="text-xs font-bold text-red-500 px-4 pt-2">{fileError}</p>
                )}
                
                {selectedFiles.length > 0 && (
                    <div className="px-4 py-2 mt-2 mx-2 bg-slate-50 border border-slate-200 rounded-xl flex flex-col gap-2 max-h-32 overflow-y-auto custom-scrollbar">
                        {selectedFiles.map((f, i) => (
                            <div key={i} className="flex items-center justify-between shrink-0">
                                <span className="text-xs font-bold text-slate-600 flex items-center gap-2">
                                    {f.type.includes('pdf') ? <FileText size={14}/> : <Image size={14}/>}
                                    <span className="truncate max-w-[200px]">{f.name}</span>
                                </span>
                                <button type="button" onClick={() => removeFile(i)} className="text-red-500 hover:text-red-700 bg-red-50 p-1 rounded-md transition">
                                    <X size={14}/>
                                </button>
                            </div>
                        ))}
                    </div>
                )}
                
                <form onSubmit={handleInitialSend} className="p-3 flex gap-2 shrink-0 items-end">
                    <label className="cursor-pointer p-3 text-slate-500 hover:text-emerald-600 hover:bg-emerald-50 rounded-xl transition bg-slate-50 border border-slate-100 shadow-sm" title="Joindre des photos ou des PDF (max 20)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
                        <input type="file" multiple className="hidden" accept="image/jpeg, image/png, image/webp, application/pdf" disabled={sending || selectedFiles.length >= 20} onChange={handleFileChange} />
                    </label>
                    <textarea 
                        ref={textareaRef}
                        value={draft} 
                        onChange={e => setDraft(e.target.value)} 
                        onPaste={handlePaste}
                        placeholder="Écrire une réponse..." 
                        disabled={sending} 
                        rows="1" 
                        className="flex-1 border-2 border-slate-100 rounded-xl px-4 py-3 outline-none focus:border-emerald-50 text-sm transition resize-none custom-scrollbar max-h-32 overflow-hidden" 
                    />
                    <button type="submit" disabled={sending || (!draft.trim() && selectedFiles.length === 0)} title="Envoyer le message" className="bg-emerald-600 text-white p-3 rounded-xl hover:bg-emerald-700 transition disabled:opacity-50 shadow-sm shrink-0">
                        {sending ? <Loader className="animate-spin w-5 h-5"/> : <ArrowUp size={20} />}
                    </button>
                </form>
            </div>
        </div>
    );
};

// -------------------------------------------------------------------------
// COMPOSANT 3 : FORMULAIRE DE PREMIER CONTACT (RIVERAIN)
// -------------------------------------------------------------------------

const PublicContactForm = ({ panneauId, showToast, onSuccess, isPreview, hideAlert = false, hideAttachments = false, settings = {} }) => {
    const [submitting, setSubmitting] = useState(false);
    const [isAlert, setIsAlert] = useState(false);
    const [mountTime] = useState(Math.floor(Date.now() / 1000));
    const [selectedFiles, setSelectedFiles] = useState([]);
    const [message, setMessage] = useState('');
    const [isDragging, setIsDragging] = useState(false);
    const [dragCounter, setDragCounter] = useState(0);
    const [moderationWarning, setModerationWarning] = useState(null);
    const textareaRef = useRef(null);

    useEffect(() => {
        if (textareaRef.current) {
            textareaRef.current.style.height = ''; 
            const scrollH = textareaRef.current.scrollHeight;
            const clientH = textareaRef.current.clientHeight;
            
            if (scrollH > clientH) {
                textareaRef.current.style.height = `${scrollH}px`;
            }
        }
    }, [message]);

    const addFiles = (fileList) => {
        const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
        const files = Array.from(fileList).filter(f => validTypes.includes(f.type));
        
        if (files.length === 0) {
            return;
        }

        if (selectedFiles.length + files.length > 20) {
            if (showToast) {
                showToast("Maximum 20 fichiers autorisés.", "error");
            }
            return;
        }
        
        const validFiles = [];
        for (let f of files) {
            if (f.size > 5 * 1024 * 1024) {
                if (showToast) {
                    showToast("Un ou plusieurs fichiers dépassent 5 Mo.", "error");
                }
                return;
            }
            validFiles.push(f);
        }
        
        setSelectedFiles(prev => [...prev, ...validFiles]);
    };

    const handleFileChange = (e) => {
        addFiles(e.target.files);
        e.target.value = null;
    };

    const handlePaste = (e) => {
        if (!hideAttachments && e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) {
            addFiles(e.clipboardData.files);
        }
    };

    const handleDragEnter = (e) => {
        if (hideAttachments) return;
        e.preventDefault();
        e.stopPropagation();
        setDragCounter(prev => prev + 1);
        setIsDragging(true);
    };

    const handleDragLeave = (e) => {
        if (hideAttachments) return;
        e.preventDefault();
        e.stopPropagation();
        setDragCounter(prev => {
            const nc = prev - 1;
            if (nc === 0) {
                setIsDragging(false);
            }
            return nc;
        });
    };

    const handleDragOver = (e) => {
        if (hideAttachments) return;
        e.preventDefault();
        e.stopPropagation();
    };

    const handleDrop = (e) => {
        if (hideAttachments) return;
        e.preventDefault();
        e.stopPropagation();
        setDragCounter(0);
        setIsDragging(false);
        
        if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
            addFiles(e.dataTransfer.files);
        }
    };

    const removeFile = (index) => {
        const newFiles = [...selectedFiles];
        newFiles.splice(index, 1);
        setSelectedFiles(newFiles);
    };

    const executeSubmit = async (authorEmail, isPendingControl = false) => {
        setSubmitting(true);
        
        let finalDetail = message.trim();
        
        if (!hideAttachments) {
            for (let file of selectedFiles) {
                const fileType = file.type.includes('pdf') ? 'pdf' : 'image';
                try {
                    const fileId = await window.uploadFile(file, fileType, null, false, true);
                    finalDetail += `\n[ATTACHMENT:${fileId}:${fileType}]`;
                } catch(err) {
                    if (showToast) {
                        showToast("Erreur lors de l'upload : " + err.message, 'error');
                    }
                    setSubmitting(false);
                    return;
                }
            }
        }

        const data = { 
            panneauId, 
            detail: finalDetail, 
            author: authorEmail, 
            isAlert: isPendingControl ? 2 : (isAlert ? 1 : 0), 
            hp_text: document.querySelector('input[name="hp_text"]')?.value || '', 
            hp_time: mountTime 
        };
        
        try {
            await fetch(window.ECO_CONFIG.apiBaseUrl + 'interactions', { 
                method: 'POST', 
                body: JSON.stringify(data) 
            });
            
            if (showToast) {
                showToast("Message envoyé. Le responsable vous répondra par e-mail.", "success");
            }
            
            setMessage(''); 
            setIsAlert(false); 
            setSelectedFiles([]); 
            setModerationWarning(null); 
            
            if (onSuccess) {
                onSuccess();
            }
        } catch(err) { 
            if (showToast) {
                showToast("Erreur lors de l'envoi", "error"); 
            }
        }
        setSubmitting(false);
    };

    const handleInitialSubmit = (e) => {
        e.preventDefault(); 
        
        if (isPreview) {
            if (showToast) {
                showToast("Ceci est un aperçu interactif. La messagerie n'est pas fonctionnelle ici.", "info");
            }
            return;
        }

        const word = checkForBlacklist(message, settings);
        
        if (word) {
            setModerationWarning(word);
            return;
        }
        
        executeSubmit(e.target.email.value, false);
    };

    return (
        <form 
            onSubmit={handleInitialSubmit} 
            onDragEnter={handleDragEnter}
            onDragLeave={handleDragLeave}
            onDragOver={handleDragOver}
            onDrop={handleDrop}
            className="space-y-3 flex flex-col min-h-full p-2 relative"
        >
            {isDragging && !hideAttachments && (
                <div className="absolute inset-0 z-[100] bg-emerald-500/20 backdrop-blur-sm border-4 border-emerald-500 border-dashed rounded-2xl flex flex-col items-center justify-center pointer-events-none">
                    <div className="bg-white p-6 rounded-2xl shadow-xl flex flex-col items-center text-center">
                        <ArrowUp size={48} className="text-emerald-500 mb-4 animate-bounce" />
                        <span className="text-xl font-black text-slate-800">Relâchez vos fichiers ici</span>
                    </div>
                </div>
            )}

            {moderationWarning && (
                <ModerationModal 
                    word={moderationWarning} 
                    onModify={() => setModerationWarning(null)} 
                    onRequestControl={() => {
                        const email = document.querySelector('input[name="email"]')?.value || '';
                        executeSubmit(email, true);
                    }} 
                />
            )}

            <input type="text" name="hp_text" className="absolute opacity-0 -z-10 w-0 h-0" tabIndex="-1" autoComplete="off" />
            <input type="hidden" name="hp_time" value={mountTime} />

            <input 
                name="email" 
                type="email" 
                required 
                placeholder="Votre e-mail (pour recevoir la réponse)" 
                className="w-full border-2 rounded-xl p-3 text-sm outline-none transition shrink-0 border-slate-200 focus:border-slate-800" 
            />
            
            {hideAttachments && selectedFiles.length > 0 && (
                <div className="bg-white border border-slate-200 rounded-xl p-2 flex flex-col gap-2 max-h-32 overflow-y-auto custom-scrollbar shrink-0 mt-2">
                    {selectedFiles.map((f, i) => (
                        <div key={i} className="bg-slate-50 p-2 rounded-lg border border-slate-200 flex items-center justify-between shadow-sm shrink-0">
                            <span className="text-xs font-bold text-slate-600 flex items-center gap-2">
                                {f.type.includes('pdf') ? <FileText size={16}/> : <Image size={16}/>}
                                <span className="truncate max-w-[200px]">{f.name}</span>
                            </span>
                            <button type="button" onClick={() => removeFile(i)} disabled={submitting} className="text-red-500 hover:text-red-700 bg-red-50 p-1.5 rounded-lg transition disabled:opacity-50">
                                <X size={14}/>
                            </button>
                        </div>
                    ))}
                </div>
            )}

            <textarea 
                ref={textareaRef}
                name="detail" 
                value={message} 
                onChange={(e) => setMessage(e.target.value)} 
                onPaste={handlePaste}
                onInput={(e) => { 
                    e.target.style.height = 'auto'; 
                    e.target.style.height = (e.target.scrollHeight) + 'px'; 
                }}
                required={hideAttachments || selectedFiles.length === 0} 
                placeholder="Votre message... (coller ou glisser des images est accepté)" 
                className="w-full border-2 rounded-xl p-3 text-sm outline-none transition resize-none flex-1 border-slate-200 focus:border-slate-800 overflow-hidden min-h-[120px]"
            ></textarea>
            
            {!hideAttachments && selectedFiles.length > 0 && (
                <div className="bg-white border border-slate-200 rounded-xl p-2 flex flex-col gap-2 max-h-32 overflow-y-auto custom-scrollbar shrink-0 mt-2">
                    {selectedFiles.map((f, i) => (
                        <div key={i} className="bg-slate-50 p-2 rounded-lg border border-slate-200 flex items-center justify-between shadow-sm shrink-0">
                            <span className="text-xs font-bold text-slate-600 flex items-center gap-2">
                                {f.type.includes('pdf') ? <FileText size={16}/> : <Image size={16}/>}
                                <span className="truncate max-w-[200px]">{f.name}</span>
                            </span>
                            <button type="button" onClick={() => removeFile(i)} disabled={submitting} className="text-red-500 hover:text-red-700 bg-red-50 p-1.5 rounded-lg transition disabled:opacity-50">
                                <X size={14}/>
                            </button>
                        </div>
                    ))}
                </div>
            )}

            {!hideAlert && (
                <label className="flex items-center gap-2 cursor-pointer p-2 border border-slate-200 rounded-lg hover:bg-slate-50 transition select-none shrink-0 mt-1">
                    <input 
                        type="checkbox" 
                        checked={isAlert} 
                        onChange={(e) => setIsAlert(e.target.checked)} 
                        className="w-4 h-4 accent-red-500 shrink-0" 
                    />
                    <span className="text-[11px] font-bold text-slate-700 leading-tight">Signaler une urgence (nuisance grave, danger)</span>
                </label>
            )}
            
            <div className="flex gap-2 items-center shrink-0 mt-1">
                {!hideAttachments && (
                    <label className={`cursor-pointer w-[46px] h-[46px] rounded-xl transition shadow-sm flex items-center justify-center shrink-0 border ${submitting || selectedFiles.length >= 20 ? 'bg-slate-50 border-slate-100 text-slate-400 opacity-50' : 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50 hover:text-emerald-600 hover:border-emerald-200'}`} title="Joindre des photos ou PDF (max 20)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
                        <input type="file" multiple className="hidden" accept="image/jpeg, image/png, image/webp, application/pdf" disabled={submitting || selectedFiles.length >= 20} onChange={handleFileChange} />
                    </label>
                )}
                <button type="submit" disabled={submitting || (hideAttachments ? !message.trim() : (selectedFiles.length === 0 && !message.trim()))} className="flex-1 h-[46px] bg-slate-900 text-white rounded-xl font-black uppercase tracking-widest text-xs hover:bg-slate-800 transition flex justify-center items-center gap-2 shadow-md disabled:opacity-50">
                    {submitting ? <Loader className="animate-spin w-4 h-4"/> : <MessageSquare size={14}/>} Envoyer
                </button>
            </div>
        </form>
    );
};

// Injection du module RiverainContactForm avec le message de prévention
const _PublicContactFormWrapper = window.PublicContactForm;
window.PublicContactForm = (props) => {
    const [isAlertLocal, setIsAlertLocal] = useState(false);

    // Interception pour capturer l'état "isAlert" depuis le DOM
    useEffect(() => {
        const checkAlert = () => {
            const checkbox = document.querySelector('input[type="checkbox"]');
            if (checkbox && checkbox.checked !== isAlertLocal) {
                setIsAlertLocal(checkbox.checked);
            }
        };
        document.addEventListener('change', checkAlert);
        return () => document.removeEventListener('change', checkAlert);
    }, [isAlertLocal]);

    return (
        <div className="flex flex-col h-full relative">
            <_PublicContactFormWrapper {...props} />
            {isAlertLocal && !props.hideAlert && (
                <div className="bg-red-50 text-red-800 p-3 rounded-xl border border-red-200 text-xs mt-2 shadow-sm animate-in fade-in slide-in-from-top-2 mx-2 mb-2">
                    <strong className="flex items-center gap-1 mb-1">
                        <window.AlertTriangle size={14}/> Attention sécurité
                    </strong>
                    Ce formulaire ne contacte que le chef de chantier de manière asynchrone. En cas d'accident grave, d'incendie ou de danger immédiat pour les personnes, veuillez contacter les services de secours (112, 15, 17, 18).
                </div>
            )}
        </div>
    );
};

// EXPORT GLOBAL AJOUTÉ : PdfFullViewer
Object.assign(window, { ChatBox, PublicContactForm, PdfFullViewer });

/* EOF ===== [_socle_messagerie.jsx] =============== */