import * as common from '/pages/src/common.mjs';
import * as o101 from './o101/common.mjs';

const listContainer = document.getElementById('list-container');
const headerRow = document.getElementById('header-row');
const dashEl = document.getElementById('dash');
const wrapperEl = document.getElementById('wrapper');
const checkboxesContainer = document.getElementById('cols-checkboxes');
const settingsPanel = document.getElementById('settings-panel');

// --- DURÉES DES POWER-UPS ZWIFT (en ms) ---
const PU_DURATIONS = {
    "LIGHTNESS": 30000, "DRAFTBOOST": 40000, "AERO": 15000,
    "UNDRAFTABLE": 20000, "CLOAK": 15000, "STEAMROLLER": 30000, "ANVIL": 30000,
    "1": 30000, "2": 40000, "3": 15000, "4": 20000, "5": 15000, "6": 30000, "7": 30000
};

// Map pour suivre les timers actifs { athleteId: { startTime, duration, lastType } }
const puTracker = new Map();

// --- DICTIONNAIRE DES MAILLOTS DISTINCTIFS ---
const DISTINCTIVE_JERSEYS = {
    1001: 'images/maillot-vert.png',   
    1002: 'images/maillot-apois.png',  
    1003: 'images/maillot-orange.png'  
};

// --- VARIABLES DU NOUVEAU MOTEUR FTS ---
let activeSprintLeaders = new Set();
let activeKOMLeaders = new Set();
let knownSegments = new Map(); // Pour mémoriser les segments même après les avoir passés
let lastFtsScan = 0;

// --- DEFINITION DES COLONNES ---
const ALL_COLS = [
    { id: 'flag',  label: 'NAT',  width: '40px', def: true,  align: 'center' },
    { id: 'cat',   label: 'CAT',  width: '40px', def: true,  align: 'center' },
    { id: 'pu',    label: 'PU',   width: '35px', def: true,  align: 'center' },
    { id: 'grp',   label: 'GRP',  width: '35px', def: true,  align: 'center' },
    { id: 'name',  label: 'NOM',  width: '200px', def: true,  align: 'left' }, 
    { id: 'team',  label: 'CLUB', width: '95px', def: true,  align: 'left' },
    { id: 'draft', label: 'ASP',  width: '50px', def: false, align: 'right' },
    { id: 'watts', label: 'PWR',  width: '60px', def: true,  align: 'right' },
    { id: 'wkg',   label: 'WKG',  width: '55px', def: true,  align: 'right' },
    { id: 'bpm',   label: 'HR',   width: '45px', def: true,  align: 'right' },
    { id: 'gap',   label: 'GAP',  width: '65px', def: true,  align: 'right' }
];

let settings = {
    opacity: 90,
    zoom: 100,
    maxRiders: 30, 
    autoRiders: false, 
    showHeaders: true, 
    showJerseys: true,
    refreshRate: 1,
    cols: {},      
    colWidths: {},
    colFontSizes: {},  
    panelPos: { x: 50, y: 50 } 
};

let calculatedMaxRiders = 30;
let lastUpdate = 0; 

// --- SCANNER DE BASE DE DONNÉES INTERNE ZENMASTER ---
let zmTeamAssignments = null;
let zmAllTeams = null;

async function fetchZenmasterTeamsDirectly() {
    try {
        if (!indexedDB.databases) return;
        
        const dbs = await indexedDB.databases();
        let dbNamesToScan = dbs.map(db => db.name).filter(name => 
            name.toLowerCase().includes('team') || 
            name.toLowerCase().includes('point') ||
            name.toLowerCase().includes('zen')
        );

        if (dbNamesToScan.length === 0) {
            dbNamesToScan = ['teams', 'points-leaderboard', 'zenTeams'];
        }

        let foundAssignments = null;
        let foundTeams = null;

        for (let dbName of dbNamesToScan) {
            const db = await new Promise((resolve) => {
                const req = indexedDB.open(dbName);
                req.onsuccess = () => resolve(req.result);
                req.onerror = () => resolve(null);
            });

            if (!db) continue;

            for (let storeName of db.objectStoreNames) {
                const data = await new Promise((resolve) => {
                    try {
                        const tx = db.transaction(storeName, 'readonly');
                        const store = tx.objectStore(storeName);
                        const req = store.getAll();
                        req.onsuccess = () => resolve(req.result);
                        req.onerror = () => resolve(null);
                    } catch(e) { resolve(null); }
                });

                if (data && data.length > 0) {
                    const isAssignments = data.some(d => d.zwid !== undefined || d.athleteId !== undefined || (d.id !== undefined && typeof d.id === 'number' && d.id > 1000));
                    if (isAssignments) foundAssignments = data;
                    else if (data.some(d => d.color || d.bgColor || d.hex_color || storeName.toLowerCase().includes('team'))) foundTeams = data;
                }
            }
            db.close();
        }

        if (foundAssignments) zmTeamAssignments = foundAssignments;
        if (foundTeams) zmAllTeams = foundTeams;
        
    } catch(e) {}
}

fetchZenmasterTeamsDirectly();
setInterval(fetchZenmasterTeamsDirectly, 5000);

function getZenmasterOverride(athleteId, rawTeam) {
    let matchedTeamName = null;
    let matchedTeamColor = null;

    if (zmTeamAssignments) {
        let assignment = null;
        if (Array.isArray(zmTeamAssignments)) {
            assignment = zmTeamAssignments.find(t => String(t.zwid) === String(athleteId) || String(t.id) === String(athleteId) || String(t.athleteId) === String(athleteId));
        } else if (typeof zmTeamAssignments === 'object') {
            assignment = zmTeamAssignments[athleteId];
        }
        if (assignment) {
            matchedTeamName = assignment.team || assignment.teamId || assignment.teamName || assignment.name || (typeof assignment === 'string' ? assignment : null);
        }
    }

    let searchTeamName = matchedTeamName || rawTeam;
    if (searchTeamName && zmAllTeams) {
        let teamInfo = null;
        if (Array.isArray(zmAllTeams)) {
            teamInfo = zmAllTeams.find(t => 
                (t.name && String(t.name).toLowerCase() === String(searchTeamName).toLowerCase()) ||
                (t.team && String(t.team).toLowerCase() === String(searchTeamName).toLowerCase()) ||
                (t.id && String(t.id).toLowerCase() === String(searchTeamName).toLowerCase())
            );
        } else if (typeof zmAllTeams === 'object') {
            teamInfo = zmAllTeams[searchTeamName] || Object.values(zmAllTeams).find(t => t.name && String(t.name).toLowerCase() === String(searchTeamName).toLowerCase());
        }

        if (teamInfo) {
            matchedTeamColor = teamInfo.color || teamInfo.bgColor || teamInfo.hex_color || null;
            matchedTeamName = teamInfo.name || teamInfo.team || searchTeamName;
        }
    }

    return {
        name: (matchedTeamName && typeof matchedTeamName === 'string' && matchedTeamName.trim() !== '') ? matchedTeamName : null,
        color: (matchedTeamColor && matchedTeamColor !== '#FFF' && matchedTeamColor !== '#FFFFFF') ? matchedTeamColor : null
    };
}

// --- CACHE API SAUCE (POUR LES CATEGORIES) ---
const subgroupCache = new Map();
const pendingRequests = new Set();

function getSubgroupLabel(id) {
    if (!id) return null;
    if (subgroupCache.has(id)) return subgroupCache.get(id);
    
    if (!pendingRequests.has(id)) {
        pendingRequests.add(id);
        if (common && common.rpc && typeof common.rpc.getEventSubgroup === 'function') {
            common.rpc.getEventSubgroup(id).then(sg => {
                if (sg && sg.subgroupLabel) subgroupCache.set(id, sg.subgroupLabel);
                else if (sg && sg.label) subgroupCache.set(id, sg.label);
                else {
                    subgroupCache.set(id, null);
                    setTimeout(() => subgroupCache.delete(id), 10000);
                }
                pendingRequests.delete(id);
            }).catch(() => pendingRequests.delete(id));
        } else {
            pendingRequests.delete(id);
        }
    }
    return null; 
}

// --- GESTION REGLAGES ---
function loadSettings() {
    const sOp = localStorage.getItem('myMod_Coureur_Op');
    const sZoom = localStorage.getItem('myMod_Coureur_Zoom');
    
    settings.opacity = sOp ? parseInt(sOp) : 90;
    settings.zoom = sZoom ? parseInt(sZoom) : 100;

    const sMax = localStorage.getItem('myMod_Coureur_MaxRiders');
    settings.maxRiders = sMax ? parseInt(sMax) : 30;

    const sAuto = localStorage.getItem('myMod_Coureur_AutoRiders');
    settings.autoRiders = sAuto !== null ? (sAuto === 'true') : false;

    const sHeads = localStorage.getItem('myMod_Coureur_ShowHead');
    settings.showHeaders = sHeads !== null ? (sHeads === 'true') : true;

    const sJerseys = localStorage.getItem('myMod_Coureur_Jerseys');
    if (sJerseys !== null) settings.showJerseys = (sJerseys === 'true');

    const sRef = localStorage.getItem('myMod_Coureur_Refresh');
    if (sRef) settings.refreshRate = parseInt(sRef);

    const sCols = localStorage.getItem('myMod_Coureur_Cols_V6'); 
    if (sCols) {
        settings.cols = JSON.parse(sCols);
        ALL_COLS.forEach(c => {
            if (settings.cols[c.id] === undefined) settings.cols[c.id] = c.def;
        });
    }
    else {
        ALL_COLS.forEach(c => settings.cols[c.id] = c.def);
    }

    const sWidths = localStorage.getItem('myMod_Coureur_Widths');
    if (sWidths) settings.colWidths = JSON.parse(sWidths);

    const sFonts = localStorage.getItem('myMod_Coureur_ColFonts');
    if (sFonts) settings.colFontSizes = JSON.parse(sFonts);
    
    const sPos = localStorage.getItem('myMod_Coureur_PanelPos');
    if(sPos) settings.panelPos = JSON.parse(sPos);

    if (document.getElementById('in-opacity')) document.getElementById('in-opacity').value = settings.opacity;
    if (document.getElementById('in-zoom')) document.getElementById('in-zoom').value = settings.zoom;
    
    if (settingsPanel) {
        settingsPanel.style.left = settings.panelPos.x + 'px';
        settingsPanel.style.top = settings.panelPos.y + 'px';
    }
    
    if (settings.autoRiders) calculateAutoRiders();

    renderSettingsUI();
    applyVisuals();
    updateGridLayout();
    initDraggablePanel();

    window.addEventListener('resize', () => {
        if (settings.autoRiders) calculateAutoRiders();
    });
}

function calculateAutoRiders() {
    const h = document.documentElement.clientHeight || window.innerHeight;
    const headerHeight = settings.showHeaders ? 40 : 5;
    const marginSafety = 20; 
    const availableHeight = h - headerHeight - marginSafety;
    const rowHeight = 38; 
    const count = Math.floor(availableHeight / rowHeight);
    calculatedMaxRiders = Math.min(Math.max(3, count), 50);
}

function saveSettings() {
    if (document.getElementById('in-opacity')) settings.opacity = parseInt(document.getElementById('in-opacity').value);
    if (document.getElementById('in-zoom')) settings.zoom = parseInt(document.getElementById('in-zoom').value);
    
    const inMax = document.getElementById('in-max-riders');
    if(inMax) settings.maxRiders = parseInt(inMax.value);

    const inRef = document.getElementById('in-refresh');
    if(inRef) settings.refreshRate = parseInt(inRef.value);

    const chkAuto = document.getElementById('chk-auto-riders');
    if(chkAuto) settings.autoRiders = chkAuto.checked;

    const chkHead = document.getElementById('chk-show-headers');
    if(chkHead) settings.showHeaders = chkHead.checked;

    const chkJerseys = document.getElementById('chk-show-jerseys');
    if(chkJerseys) settings.showJerseys = chkJerseys.checked;

    ALL_COLS.forEach(c => {
        const chk = document.getElementById(`chk-${c.id}`);
        if(chk) settings.cols[c.id] = chk.checked;
    });
    
    localStorage.setItem('myMod_Coureur_Op', settings.opacity);
    localStorage.setItem('myMod_Coureur_Zoom', settings.zoom);
    localStorage.setItem('myMod_Coureur_MaxRiders', settings.maxRiders);
    localStorage.setItem('myMod_Coureur_Refresh', settings.refreshRate);
    localStorage.setItem('myMod_Coureur_AutoRiders', settings.autoRiders);
    localStorage.setItem('myMod_Coureur_ShowHead', settings.showHeaders);
    localStorage.setItem('myMod_Coureur_Jerseys', settings.showJerseys);
    localStorage.setItem('myMod_Coureur_Cols_V6', JSON.stringify(settings.cols)); 
    localStorage.setItem('myMod_Coureur_Widths', JSON.stringify(settings.colWidths));
    localStorage.setItem('myMod_Coureur_ColFonts', JSON.stringify(settings.colFontSizes));
    localStorage.setItem('myMod_Coureur_PanelPos', JSON.stringify(settings.panelPos));

    if (settings.autoRiders) calculateAutoRiders();

    applyVisuals();
    updateGridLayout();
    if (settingsPanel) settingsPanel.style.display = 'none';
}

function initDraggablePanel() {
    const handle = document.getElementById('panel-drag-handle');
    if (!handle || !settingsPanel) return;

    let isDragging = false;
    let startX, startY, initialLeft, initialTop;

    handle.addEventListener('mousedown', (e) => {
        isDragging = true;
        startX = e.clientX;
        startY = e.clientY;
        initialLeft = settingsPanel.offsetLeft;
        initialTop = settingsPanel.offsetTop;
        handle.style.cursor = 'grabbing';
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        settings.panelPos.x = initialLeft + dx;
        settings.panelPos.y = initialTop + dy;
        settingsPanel.style.left = settings.panelPos.x + 'px';
        settingsPanel.style.top = settings.panelPos.y + 'px';
    });

    document.addEventListener('mouseup', () => {
        if(isDragging) {
            isDragging = false;
            handle.style.cursor = 'grab';
        }
    });
}

function renderSettingsUI() {
    if (!checkboxesContainer) return;
    checkboxesContainer.innerHTML = '';
    
    const divHead = document.createElement('div');
    divHead.className = 'global-setting';
    divHead.innerHTML = `
        <div style="margin-bottom:5px;">
            <input type="checkbox" id="chk-show-headers" ${settings.showHeaders ? 'checked' : ''}>
            <label for="chk-show-headers" style="font-weight:bold; color:#fc6719;">AFFICHER LES ENTÊTES</label>
        </div>
        <div style="margin-bottom:5px;">
            <input type="checkbox" id="chk-show-jerseys" ${settings.showJerseys ? 'checked' : ''}>
            <label for="chk-show-jerseys" style="font-weight:bold; color:#fc6719;">AFFICHER MAILLOTS (FTS)</label>
        </div>
    `;
    checkboxesContainer.appendChild(divHead);
    
    divHead.querySelector('#chk-show-headers').addEventListener('change', (e) => {
        settings.showHeaders = e.target.checked;
        updateGridLayout();
    });

    divHead.querySelector('#chk-show-jerseys').addEventListener('change', (e) => {
        settings.showJerseys = e.target.checked;
    });

    const divMax = document.createElement('div');
    divMax.className = 'input-group';
    const sliderDisplay = settings.autoRiders ? 'none' : 'block';
    
    divMax.innerHTML = `
        <div style="display:flex; justify-content:space-between; align-items:center;">
            <label>Nombre de coureurs : <span id="max-riders-label" style="color:white; font-weight:bold;">${settings.autoRiders ? 'AUTO' : settings.maxRiders}</span></label>
            <div style="display:flex; align-items:center;">
                <input type="checkbox" id="chk-auto-riders" ${settings.autoRiders ? 'checked' : ''}>
                <label for="chk-auto-riders" style="margin-left:5px; font-size:10px; cursor:pointer;">AUTO</label>
            </div>
        </div>
        <input type="range" id="in-max-riders" min="3" max="60" value="${settings.maxRiders}" style="display:${sliderDisplay}">
    `;
    checkboxesContainer.appendChild(divMax);

    const divRefresh = document.createElement('div');
    divRefresh.className = 'input-group';
    divRefresh.innerHTML = `
        <div style="display:flex; justify-content:space-between; align-items:center;">
            <label>Rafraîchissement : <span id="refresh-label" style="color:white; font-weight:bold;">${settings.refreshRate}s</span></label>
        </div>
        <input type="range" id="in-refresh" min="1" max="10" value="${settings.refreshRate}">
    `;
    checkboxesContainer.appendChild(divRefresh);

    const rangeInput = divMax.querySelector('#in-max-riders');
    const labelSpan = divMax.querySelector('#max-riders-label');
    const autoCheck = divMax.querySelector('#chk-auto-riders');
    const refreshInput = divRefresh.querySelector('#in-refresh');
    const refreshLabel = divRefresh.querySelector('#refresh-label');

    rangeInput.addEventListener('input', (e) => {
        settings.maxRiders = parseInt(e.target.value);
        labelSpan.innerText = settings.maxRiders;
    });

    refreshInput.addEventListener('input', (e) => {
        settings.refreshRate = parseInt(e.target.value);
        refreshLabel.innerText = settings.refreshRate + 's';
    });

    autoCheck.addEventListener('change', (e) => {
        settings.autoRiders = e.target.checked;
        if (settings.autoRiders) {
            rangeInput.style.display = 'none';
            labelSpan.innerText = 'AUTO';
            calculateAutoRiders();
        } else {
            rangeInput.style.display = 'block';
            labelSpan.innerText = settings.maxRiders;
        }
    });

    ALL_COLS.forEach(c => {
        const lbl = c.label || 'NAT'; 
        const checked = settings.cols[c.id] ? 'checked' : '';
        const isFlexible = c.width.includes('fr'); 
        
        let currentW = settings.colWidths[c.id];
        if (!currentW) currentW = parseInt(c.width) || 40; 
        let currentF = settings.colFontSizes[c.id] || 15; 

        let minW = 20, maxW = 150;
        if (c.id === 'name') { minW = 50; maxW = 400; }
        if (c.id === 'team') { minW = 30; maxW = 250; }
        if (c.id === 'grp')  { minW = 20; maxW = 80; } 

        let widthSliderHtml = isFlexible 
            ? `<span class="locked-val">AUTO</span>`
            : `<input type="range" class="mini-slider" id="sli-w-${c.id}" min="${minW}" max="${maxW}" value="${currentW}" title="Larg.: ${currentW}px">`;

        const div = document.createElement('div');
        div.className = 'col-config-block';
        div.innerHTML = `
            <div class="col-head">
                <input type="checkbox" id="chk-${c.id}" ${checked}>
                <label for="chk-${c.id}">${lbl}</label>
            </div>
            <div class="col-params">
                <div class="param-row">
                    <span class="p-lbl">Larg.</span>
                    ${widthSliderHtml}
                </div>
                <div class="param-row">
                    <span class="p-lbl">Txt</span>
                    <input type="range" class="mini-slider" id="sli-f-${c.id}" min="10" max="25" value="${currentF}" title="Taille : ${currentF}px">
                </div>
            </div>
        `;
        checkboxesContainer.appendChild(div);

        if (!isFlexible) {
            div.querySelector(`#sli-w-${c.id}`).addEventListener('input', (e) => {
                settings.colWidths[c.id] = parseInt(e.target.value);
                updateGridLayout(); 
            });
        }
        div.querySelector(`#sli-f-${c.id}`).addEventListener('input', (e) => {
            settings.colFontSizes[c.id] = parseInt(e.target.value);
        });
        div.querySelector(`#chk-${c.id}`).addEventListener('change', (e) => {
             settings.cols[c.id] = e.target.checked;
             updateGridLayout();
        });
    });
}

function updateGridLayout() {
    if (headerRow) headerRow.style.display = settings.showHeaders ? 'grid' : 'none';
    const activeCols = ALL_COLS.filter(c => settings.cols[c.id]);
    let gridTemplateParts = [];
    let totalFixedWidth = 0;

    activeCols.forEach(c => {
        if (c.width.includes('fr')) {
            gridTemplateParts.push('1fr');
            totalFixedWidth += 200; 
        } else {
            const w = settings.colWidths[c.id] || parseInt(c.width);
            gridTemplateParts.push(`${w}px`);
            totalFixedWidth += w;
        }
    });

    const totalW = totalFixedWidth + 10; 
    if (dashEl) {
        dashEl.style.width = `${Math.max(300, totalW)}px`;
        const gridTemplate = gridTemplateParts.join(' ');
        dashEl.style.setProperty('--grid-layout', gridTemplate);
    }

    let headerHtml = '';
    activeCols.forEach(c => {
        headerHtml += `<div class="h-col ${c.align}">${c.label}</div>`;
    });
    if (headerRow) headerRow.innerHTML = headerHtml;
}

function applyVisuals() {
    const opLabel = document.getElementById('op-label');
    if (opLabel) opLabel.innerText = settings.opacity;
    
    if (dashEl) dashEl.style.backgroundColor = `rgba(0, 0, 0, ${settings.opacity/100})`;
    if (listContainer) listContainer.style.backgroundColor = `rgba(0, 0, 0, ${settings.opacity/100})`;
    
    const zoomLabel = document.getElementById('zoom-label');
    if (zoomLabel) zoomLabel.innerText = (settings.zoom/100).toFixed(1);
    
    if (wrapperEl) wrapperEl.style.transform = `scale(${settings.zoom/100})`;
}

// --- UTILITAIRES ---
function formatTime(sec) {
    if (sec == null || isNaN(sec)) return '';
    const abs = Math.abs(sec);
    if (abs < 60) return Math.round(abs) + 's';
    const m = Math.floor(abs / 60);
    const s = Math.floor(abs % 60);
    return `${m}:${s.toString().padStart(2,'0')}`;
}

function getFlagImg(countryCode) {
    if (!countryCode) return '';
    const cc = countryCode.toLowerCase();
    return `<img src="https://flagcdn.com/20x15/${cc}.png" alt="${cc}" onerror="this.style.display='none'">`;
}

function formatName(first, last) {
    if (o101 && o101.fmtFirstName && o101.fmtLastName) {
        try {
            return o101.fmtFirstName({athlete:{firstName:first}}, 1) + '. ' + o101.fmtLastName({athlete:{lastName:last}});
        } catch(e) {}
    }
    if (!last) return first || 'Inconnu';
    let cleanLast = last.replace(/\[.*?\]|\(.*?\)/g, '').trim();
    const fInitial = first ? first.charAt(0).toUpperCase() + '.' : '';
    return `${fInitial} ${cleanLast.toUpperCase()}`;
}

function stringToColorHSL(str) {
    let hash = 0;
    let safeStr = String(str || "");
    for (let i = 0; i < safeStr.length; i++) {
        hash = safeStr.charCodeAt(i) + ((hash << 5) - hash);
    }
    const h = Math.abs(hash) % 360;
    const s = 80;
    const l = 70;
    return `hsl(${h}, ${s}%, ${l}%)`;
}

function sanitizeTeamName(rawTeam) {
    if (!rawTeam) return "Indépendant";
    if (Array.isArray(rawTeam)) rawTeam = rawTeam[0];
    if (typeof rawTeam === 'object' && rawTeam !== null) {
        rawTeam = rawTeam.name || rawTeam.teamName || String(rawTeam);
    }
    const str = String(rawTeam).trim();
    return str === '' ? "Indépendant" : str;
}

function detectCategory(r) {
    let a = r.athlete || {};
    let s = r.state || {};
    let zp = a.zwiftpower || a.zp || {};

    let subId = a.eventSubgroupId || a.subgroupId || r.eventSubgroupId || r.subgroupId || s.eventSubgroupId || s.subgroupId;

    const toLetter = (val) => {
        if (val === undefined || val === null) return null;
        let str = String(val).toUpperCase().trim();
        if (str === '1' || str === 'A' || str.includes('CAT A') || str.includes('GROUP A')) return 'A';
        if (str === '2' || str === 'B' || str.includes('CAT B') || str.includes('GROUP B')) return 'B';
        if (str === '3' || str === 'C' || str.includes('CAT C') || str.includes('GROUP C')) return 'C';
        if (str === '4' || str === 'D' || str.includes('CAT D') || str.includes('GROUP D')) return 'D';
        if (str === '5' || str === 'E' || str.includes('CAT E') || str.includes('GROUP E')) return 'E';
        
        let match = str.match(/\b([A-E])\b/);
        if (match) return match[1];
        return null;
    };

    let cat = null;

    if (!cat) cat = toLetter(zp.category || zp.cat || a.zpCategory || a.zwiftPowerCategory);

    if (!cat && subId) {
        let fetchedLabel = getSubgroupLabel(subId);
        if (fetchedLabel) cat = toLetter(fetchedLabel);
    }

    if (!cat) {
        let fullName = `${a.firstName || ''} ${a.lastName || ''}`.toUpperCase();
        let matchName = fullName.match(/\(\s*([A-E])\s*\)|\[\s*([A-E])\s*\]/);
        if (matchName) cat = matchName[1] || matchName[2];
    }

    if (!cat) {
        let team = a.team || '';
        if (Array.isArray(team)) team = team[0];
        if (team) {
            let matchTeam = String(team).toUpperCase().match(/\(\s*([A-E])\s*\)|\[\s*([A-E])\s*\]/);
            if (matchTeam) cat = matchTeam[1] || matchTeam[2];
        }
    }

    if (!cat) {
        let rawCat = a.category || r.category || r.categoryId || s.categoryId || a.subgroupLabel || r.subgroupLabel;
        if (rawCat === undefined && a.subgroup && typeof a.subgroup === 'object') {
            rawCat = a.subgroup.subgroupLabel || a.subgroup.label;
        } else if (rawCat === undefined && typeof r.subgroup === 'object') {
            rawCat = r.subgroup.subgroupLabel || r.subgroup.label;
        } else if (rawCat === undefined) {
            rawCat = r.subgroup; 
        }
        cat = toLetter(rawCat);
    }

    return cat || '-';
}

function getCategoryBadge(cat) {
    if (!cat || cat === '-' || cat === '') return '-';
    
    let bg = '#555';
    let color = '#fff';
    
    if (cat === 'A') { bg = '#ff3333'; color = '#fff'; }
    else if (cat === 'B') { bg = '#55ff55'; color = '#000'; }
    else if (cat === 'C') { bg = '#33ccff'; color = '#000'; }
    else if (cat === 'D') { bg = '#ffcc33'; color = '#000'; }
    else if (cat === 'E') { bg = '#d633ff'; color = '#fff'; }
    else { return '-'; } 
    
    return `<span style="background-color:${bg}; color:${color}; padding: 2px 6px; border-radius: 4px; font-weight: 900; box-shadow: 0 1px 2px rgba(0,0,0,0.5);">${cat}</span>`;
}

// --- LOGIQUE ZWIFT ---
function updateRiders(data) {
    const now = Date.now();
    if (now - lastUpdate < settings.refreshRate * 1000) return;
    lastUpdate = now;

    let riders = [];
    if (Array.isArray(data)) riders = data;
    else if (data && data.riders) riders = data.riders;
    else if (data && data.nearby) riders = data.nearby;
    else return;

    if (riders.length === 0) {
        if (listContainer) listContainer.innerHTML = '<div class="status-msg">Aucun coureur proche</div>';
        return;
    }

    if (settings.autoRiders) calculateAutoRiders();

    let myTeamName = null;
    const me = riders.find(r => r.watching);
    if (me && me.athlete && me.athlete.team) {
        let rawT = me.athlete.team;
        if (Array.isArray(rawT)) rawT = rawT[0];
        let tName = rawT ? rawT.trim() : "Indépendant";

        let zmOverride = getZenmasterOverride(me.athlete.id || me.id, rawT);
        if (zmOverride.name) {
            tName = zmOverride.name.trim();
        } 
        else if (o101 && o101.preferredTeamColor) {
            try {
                let tc = o101.preferredTeamColor(rawT, me.athlete.id || me.id);
                if (tc && tc.name && tc.name.trim() !== '') tName = tc.name.trim();
            } catch(e) {}
        }
        
        myTeamName = tName;
    }

    const limit = settings.autoRiders ? calculatedMaxRiders : settings.maxRiders;
    let closestRiders = [...riders].sort((a, b) => Math.abs(a.gap || 0) - Math.abs(b.gap || 0)).slice(0, limit);
    let visibleIds = new Set(closestRiders.map(r => r.id || (r.athlete && r.athlete.id)));

    riders.sort((a, b) => (a.gap || 0) - (b.gap || 0));

    let groups = [];
    if (riders.length > 0) {
        let currentGroup = [riders[0]];
        for (let i = 1; i < riders.length; i++) {
            const r = riders[i];
            const prev = riders[i-1];
            const diff = (r.gap || 0) - (prev.gap || 0);

            if (diff > 2) { 
                groups.push(currentGroup);
                currentGroup = [r];
            } else {
                currentGroup.push(r);
            }
        }
        groups.push(currentGroup);
    }

    let html = '';
    const activeCols = ALL_COLS.filter(c => settings.cols[c.id]);
    let groupAlt = false; 

    groups.forEach((group, groupIndex) => {
        const visibleRidersInGroup = group.filter(r => visibleIds.has(r.id || (r.athlete && r.athlete.id)));
        if (visibleRidersInGroup.length === 0) return;

        let avgWkg = 0;
        let groupTeams = new Map();
        let colorsMap = new Map();

        if (group.length > 0) {
            let totalWkg = 0;
            group.forEach(r => {
                const p = (r.state && r.state.power) || 0;
                const w = (r.athlete && r.athlete.weight) || 75;
                totalWkg += (p/w);

                if (r.athlete && r.athlete.team) {
                    let rawTeam = r.athlete.team;
                    if (Array.isArray(rawTeam)) rawTeam = rawTeam[0];
                    let athleteId = r.athlete.id || r.id;
                    
                    let tName = rawTeam ? rawTeam.trim() : "Indépendant";
                    let tColor = null;

                    let zmOverride = getZenmasterOverride(athleteId, rawTeam);
                    if (zmOverride.name) tName = zmOverride.name.trim();
                    if (zmOverride.color) tColor = zmOverride.color;

                    if (!zmOverride.name && !zmOverride.color && o101 && o101.preferredTeamColor) {
                        try {
                            const tc = o101.preferredTeamColor(rawTeam, athleteId);
                            if (tc) {
                                if (tc.name && tc.name.trim() !== '') tName = tc.name.trim();
                                if (tc.color && tc.color !== '#FFF' && tc.color !== '#FFFFFF') tColor = tc.color;
                            }
                        } catch(e) {}
                    }

                    if (tName !== "Indépendant" && tName !== "") {
                        groupTeams.set(tName, (groupTeams.get(tName) || 0) + 1);
                        if (tColor) colorsMap.set(tName, tColor);
                    }
                }
            });
            avgWkg = (totalWkg / group.length).toFixed(1);
        }

        let teamBadgesHtml = '';
        const sortedGroupTeams = [...groupTeams.entries()].sort((a, b) => b[1] - a[1]);
        
        sortedGroupTeams.slice(0, 10).forEach(([name, count]) => {
            const color = colorsMap.get(name) || stringToColorHSL(name);
            teamBadgesHtml += `<span style="border:1px solid ${color}; color:${color}; background:${color}22; padding:0 3px; border-radius:3px; margin-left:4px; font-size:10px; font-weight:bold;" title="${name}">${count}</span>`;
        });

        let labelGap = '';
        let classGap = 'gap-count';

        if (groupIndex === 0) {
            labelGap = '<span style="color:#44ff44; font-weight:900;">TÊTE</span>';
        } else {
            const prevGroup = groups[groupIndex - 1];
            const lastOfPrev = prevGroup[prevGroup.length - 1];
            const firstOfCurr = group[0];
            const diff = (firstOfCurr.gap || 0) - (lastOfPrev.gap || 0);
            
            groupAlt = !groupAlt;
            labelGap = `<span class="gap-time">+ ${formatTime(diff)}</span>`;
        }

        html += `<div class="gap-separator">
                    ${labelGap}
                    <span class="${classGap}">${group.length} 👤</span>
                    <span class="gap-wkg" style="background:#333; color:#aaa; font-family:monospace; padding:1px 6px; border-radius:10px; font-size:11px;">${avgWkg} w/kg</span>
                    ${teamBadgesHtml}
                 </div>`;

        visibleRidersInGroup.forEach(r => {
            let d = {}; 
            d.flag = ''; d.team = '-';
            d.grp = group.length;

            d.cat = getCategoryBadge(detectCategory(r));

            let isMyTeammate = false;
            let teamColor = '#FFFFFF'; 

            if (r.athlete) {
                d.name = formatName(r.athlete.firstName, r.athlete.lastName);
                
                let flagContent = null;
                if (o101 && o101.fmtFlag) {
                    try { flagContent = o101.fmtFlag({ athlete: r.athlete }); } catch(e) {}
                }
                
                if (flagContent) {
                    d.flag = flagContent;
                } else {
                    let cc = r.athlete.countryAlpha2 || r.athlete.countryCode;
                    d.flag = getFlagImg(cc); 
                }

                if (r.athlete.team) {
                    let rawTeam = r.athlete.team;
                    if (Array.isArray(rawTeam)) rawTeam = rawTeam[0];
                    let athleteId = r.athlete.id || r.id;

                    let tName = rawTeam ? rawTeam.trim() : "Indépendant";
                    let tColor = null;

                    let zmOverride = getZenmasterOverride(athleteId, rawTeam);
                    if (zmOverride.name) tName = zmOverride.name.trim();
                    if (zmOverride.color) tColor = zmOverride.color;
                    
                    if (!zmOverride.name && !zmOverride.color && o101 && o101.preferredTeamColor) {
                        try {
                            const tc = o101.preferredTeamColor(rawTeam, athleteId);
                            if (tc) {
                                if (tc.name && tc.name.trim() !== '') tName = tc.name.trim();
                                if (tc.color && tc.color !== '#FFF' && tc.color !== '#FFFFFF') tColor = tc.color;
                            }
                        } catch(e) {}
                    }

                    d.team = tName;
                    teamColor = tColor || stringToColorHSL(tName);
                    
                    if (myTeamName && tName !== "Indépendant" && tName === myTeamName && !r.watching) {
                        isMyTeammate = true;
                    }
                }
            } else { d.name = 'Inconnu'; }
    
            const pwr = (r.state && r.state.power) || 0;
            const wgt = (r.athlete && r.athlete.weight) || 75;
            const wkg = (pwr / wgt);
            
            d.watts = pwr > 0 ? Math.round(pwr) : '';
            d.wkg = wkg > 0 ? wkg.toFixed(1) : '';
            d.bpm = (r.state && r.state.heartrate) > 0 ? r.state.heartrate : '';
    
            const draft = (r.state && r.state.draft) || 0;
            d.draft = draft > 5 ? Math.round(draft) : ''; 
    
            const gap = r.gap || 0;
            if (gap === 0) {
                d.gap = '';
            } else {
                const timeStr = formatTime(gap);
                const sign = gap > 0 ? '+' : ''; 
                const gapColor = gap > 0 ? '#ff5555' : '#55ff55';
                d.gap = `${sign}<span style="color:${gapColor}">${timeStr}</span>`;
            }

            // --- GESTION PU ET COOLDOWN ---
            let stateToUse = r.state || {};
            if (r.watching && common.athlete && common.athlete.state) {
                stateToUse = common.athlete.state;
            }

            let puCode = stateToUse.activePowerUp || "";
            if (!puCode && stateToUse.powerup !== undefined) puCode = stateToUse.powerup;
            if (!puCode && stateToUse.powerUp !== undefined) puCode = stateToUse.powerUp;

            let puKey = puCode;
            if (typeof puCode === 'string') {
                const low = puCode.toLowerCase();
                if (low.includes('feather') || low.includes('lightness')) puKey = 1;
                else if (low.includes('draft') || low.includes('van')) puKey = 2;
                else if (low.includes('aero') || low.includes('helmet')) puKey = 3;
                else if (low.includes('burrito') || low.includes('undraftable')) puKey = 4;
                else if (low.includes('cloak') || low.includes('ghost')) puKey = 5;
                else if (low.includes('steam')) puKey = 6;
                else if (low.includes('anvil')) puKey = 7;
                else if (low.includes('coffee')) puKey = 8;
            }

            let puIcon = '';
            let puStr = String(puKey).toUpperCase();
            let bgStyle = "";

            if (puKey && puKey !== "0" && puKey !== "") {
                const athId = r.athleteId || r.id;
                // Si nouveau PU ou changement de type, on réinitialise le timer
                if (!puTracker.has(athId) || puTracker.get(athId).lastType !== puStr) {
                    puTracker.set(athId, { 
                        startTime: now, 
                        duration: PU_DURATIONS[puStr] || 15000, 
                        lastType: puStr 
                    });
                }

                const tracker = puTracker.get(athId);
                const elapsed = now - tracker.startTime;
                const remainingPct = Math.max(0, 100 - (elapsed / tracker.duration * 100));

                if (remainingPct > 0) {
                    bgStyle = `background: linear-gradient(to right, rgba(252, 103, 25, 0.4) ${remainingPct}%, transparent ${remainingPct}%);`;
                } else {
                    puTracker.delete(athId);
                }
            } else {
                puTracker.delete(r.athleteId || r.id);
            }

            // ON UTILISE LES IMAGES POUR LA COLONNE PU
            const imgStyle = 'height: 18px; vertical-align: middle; margin-top: -2px; border-radius: 2px;';

            if (puKey == 1) puIcon = `<img src="images/pu-feather.png" style="${imgStyle}" alt="Feather">`;
            else if (puKey == 2) puIcon = `<img src="images/pu-draft.png" style="${imgStyle}" alt="Van">`;
            else if (puKey == 3) puIcon = `<img src="images/pu-aero.png" style="${imgStyle}" alt="Aero">`;
            else if (puKey == 4) puIcon = `<img src="images/pu-burrito.png" style="${imgStyle}" alt="Burrito">`;
            else if (puKey == 5) puIcon = `<img src="images/pu-ghost.png" style="${imgStyle}" alt="Ghost">`;
            else if (puKey == 6) puIcon = `<img src="images/pu-steamroller.png" style="${imgStyle}" alt="Steamroller">`;
            else if (puKey == 7) puIcon = `<img src="images/pu-anvil.png" style="${imgStyle}" alt="Anvil">`;
            else if (puKey == 8) puIcon = `<img src="images/pu-coffee.png" style="${imgStyle}" alt="Coffee">`;
            
            // --- LOGIQUE MAILLOTS DISTINCTIFS (MOTEUR FTS + ZWIFT) ---
            if (puIcon === '' && settings.showJerseys) {
                // FORCE LA CONVERSION EN STRING POUR ÉVITER LES BUGS DE COMPARAISON
                const athId = String(r.athleteId || r.id || (r.athlete && r.athlete.id));
                let jId = (r.state && r.state.jerseyId) || (r.athlete && r.athlete.jerseyId) || (r.athlete && r.athlete.jersey) || 0;
                
                // 1. Notre propre moteur FTS (Ultra-précis)
                if (activeKOMLeaders.has(athId)) {
                    puIcon = `<img src="images/maillot-apois.png" style="${imgStyle}" alt="Maillot Pois">`;
                }
                else if (activeSprintLeaders.has(athId)) {
                    puIcon = `<img src="images/maillot-vert.png" style="${imgStyle}" alt="Maillot Vert">`;
                }
                // 2. Le système par défaut de Zwift (Fallback)
                else if (jId !== 0 && jId !== '') {
                    if (DISTINCTIVE_JERSEYS[jId]) {
                        puIcon = `<img src="${DISTINCTIVE_JERSEYS[jId]}" style="${imgStyle}" alt="Maillot">`;
                    }
                    else if (typeof jId === 'string' && jId.toLowerCase().includes('green')) {
                        puIcon = `<img src="images/maillot-vert.png" style="${imgStyle}" alt="Maillot Vert">`;
                    }
                    else if (typeof jId === 'string' && (jId.toLowerCase().includes('polka') || jId.toLowerCase().includes('kom'))) {
                        puIcon = `<img src="images/maillot-apois.png" style="${imgStyle}" alt="Maillot Pois">`;
                    }
                }
            }

            d.pu = puIcon;
            // --------------------------------------------------
    
            let z = 'z1';
            if (wkg >= 6.0) z='z6'; else if (wkg >= 4.5) z='z5'; else if (wkg >= 3.7) z='z4';
            else if (wkg >= 3.2) z='z3'; else if (wkg >= 2.5) z='z2';
    
            let rowClass = 'rider-row';
            if (r.watching) rowClass += ' watching'; 
            else if (isMyTeammate) rowClass += ' teammate';
            else if (groupAlt) rowClass += ' group-alt'; 
            
            // --- CORRECTION VUE FAN : AJOUT DE L'ID POUR LE CLIC ---
            const clickId = r.athleteId || r.id || (r.athlete && r.athlete.id);
            html += `<div class="${rowClass}" data-id="${clickId}" title="Cliquer pour observer ce coureur">`;
            
            activeCols.forEach(col => {
                let content = d[col.id];
                let classes = `cell ${col.align}`;
                let titleText = String(content).replace(/<[^>]*>?/gm, '');
                const fontSize = settings.colFontSizes[col.id] || 15;
                let style = `font-size:${fontSize}px;`;
                
                if (col.id === 'flag') classes += ' r-flag'; 
                if (col.id === 'cat') classes += ' r-cat';   
                if (col.id === 'grp') classes += ' r-grp'; 
                if (col.id === 'name') {
                    classes += ' r-name';
                    style += bgStyle; // INJECTION DU COOLDOWN DANS LE NOM
                }
                if (col.id === 'pu') classes += ' r-pu'; 
                
                if (col.id === 'team') {
                    classes += ' r-team';
                    if (content !== '-') {
                        style += `
                            border: 1px solid ${teamColor}; 
                            color: ${teamColor};
                            background: ${teamColor}15;
                            border-radius: 4px;
                            padding: 0 4px;
                            font-weight: 600;
                        `;
                    }
                }

                if (col.id === 'draft') classes += ' r-draft'; 
                if (col.id === 'watts') classes += ' r-watts';
                if (col.id === 'bpm') classes += ' r-bpm';
                if (col.id === 'wkg') classes += ' r-wkg ' + z;
                if (col.id === 'gap') classes += ' r-gap';
    
                html += `<div class="${classes}" style="${style}" title="${titleText}">${content}</div>`;
            });
            html += `</div>`;
        });
    });

    if (listContainer) listContainer.innerHTML = html;
}

export async function main() {
    if (o101 && o101.initNationFlags) {
        try { await o101.initNationFlags(); } catch (e) { console.warn("Erreur Init O101 Flags:", e); }
    }
    if (o101 && o101.initTeamColors) {
        try { await o101.initTeamColors(); } catch (e) { console.warn("Erreur Init O101 TeamColors:", e); }
    }

    if (listContainer) listContainer.innerHTML = '<div class="status-msg" style="color:orange">Démarrage...</div>';

    loadSettings();
    document.getElementById('btn-settings').addEventListener('click', () => {
        const p = document.getElementById('settings-panel');
        if (p) p.style.display = p.style.display === 'block' ? 'none' : 'block';
    });
    document.getElementById('btn-save').addEventListener('click', saveSettings);
    
    document.getElementById('in-opacity').addEventListener('input', (e) => {
        settings.opacity = e.target.value; applyVisuals();
    });
    document.getElementById('in-zoom').addEventListener('input', (e) => {
        settings.zoom = e.target.value; applyVisuals();
    });

    // --- ECOUTEUR DE CLIC (VUE FAN) RE-AJOUTÉ ---
    if (listContainer) {
        listContainer.addEventListener('click', async (e) => {
            const row = e.target.closest('.rider-row');
            if (row && row.dataset.id) {
                const targetId = Number(row.dataset.id);
                try {
                    // Commande officielle de Sauce (watch)
                    if (common.rpc && typeof common.rpc.watch === 'function') {
                        await common.rpc.watch(targetId);
                    }
                } catch (err) {
                    console.warn("Impossible de changer la caméra vers ce coureur :", err);
                }
            }
        });
    }

    if (common && common.initInteractionListeners) {
        common.initInteractionListeners();
        
        if (typeof common.onSauceConnected === 'function') {
            common.onSauceConnected((isConnected) => {
                if (listContainer) {
                    if (isConnected) {
                        listContainer.innerHTML = '<div class="status-msg" style="color:yellow">Sauce Connecté... Attente Zwift</div>';
                    } else {
                        listContainer.innerHTML = '<div class="status-msg" style="color:red">Sauce Déconnecté</div>';
                    }
                }
            });
        }

        // --- NOUVEAU : CYCLE DU MOTEUR FTS EN ARRIÈRE-PLAN ---
        common.subscribe('athlete/watching', async (packet) => {
            if (!packet) return;

            const now = Date.now();
            if (now - lastFtsScan < 5000) return; // Scan toutes les 5 secondes
            lastFtsScan = now;

            const dataSource = packet.segmentData || (common.athlete ? common.athlete.segmentData : null);
            
            // 1. MÉMORISATION DES SEGMENTS RENCONTRÉS
            if (dataSource) {
                let entries = [];
                if (typeof dataSource.values === 'function') entries = Array.from(dataSource.values());
                else if (Array.isArray(dataSource)) entries = dataSource;
                else entries = Object.values(dataSource);

                for (const seg of entries) {
                    if (seg && typeof seg === 'object' && seg.id && seg.name) {
                        const segNameUpper = seg.name.toUpperCase();
                        let type = (segNameUpper.includes('KOM') || segNameUpper.includes('EPIC') || segNameUpper.includes('CLIMB')) ? 'kom' : 'sprint';
                        knownSegments.set(seg.id, type); // On stocke pour toujours
                    }
                }
            }

            if (knownSegments.size === 0) return;

            let newSprintLeaders = new Set();
            let newKomLeaders = new Set();

            // 2. RÉCUPÉRATION DES CHRONOS POUR TOUS LES SEGMENTS CONNUS
            for (const [segId, type] of knownSegments.entries()) {
                try {
                    let results = null;
                    if (common.rpc && typeof common.rpc.getSegmentResults === 'function') {
                        results = await common.rpc.getSegmentResults(segId);
                    } else if (common.rpc && typeof common.rpc.getEventSegmentResults === 'function') {
                        results = await common.rpc.getEventSegmentResults(segId);
                    }

                    if (results && results.length > 0) {
                        // On trie pour avoir le meilleur chrono (FTS)
                        let ftsRes = results.slice().sort((a, b) => (a.elapsed || 99999999) - (b.elapsed || 99999999));
                        let leader = ftsRes[0];

                        if (leader) {
                            // FORCE EN STRING POUR LA COMPARAISON EXACTE
                            const leaderId = String(leader.athleteId || leader.id);
                            
                            if (type === 'kom') {
                                newKomLeaders.add(leaderId);
                            } else {
                                newSprintLeaders.add(leaderId);
                            }
                        }
                    }
                } catch (e) {
                    // Silencieux pour ne pas polluer
                }
            }

            // Met à jour les classements globaux pour l'affichage
            activeSprintLeaders = newSprintLeaders;
            activeKOMLeaders = newKomLeaders;
        });

        common.subscribe('nearby', updateRiders);
    }
}