// Lightweight Altitude Chart using Chart.js for WASM performance
// This replaces Syncfusion charts in browser environments for better performance.
// Chart.js is warmed during idle startup and loaded on demand if needed.

// Guard against multiple loads
if (typeof window._lightweightChartLoaded === 'undefined') {
    window._lightweightChartLoaded = true;
    window._chartInstances = new Map();
    window._chartResizeObservers = new Map();
    window._chartTouchGuards = new Map();
}

window.ensureChartJsLoaded = async function() {
    if (window.astroAppAssets?.ensureChartJsBundle) {
        await window.astroAppAssets.ensureChartJsBundle();
    }

    if (typeof Chart === 'undefined') {
        console.error('[AltChart] Chart.js not loaded.');
        return false;
    }

    return true;
};

// Parse chart DateTime values consistently with backend behavior.
// DateTimeKind.Unspecified values represent observatory-local wall-clock times.
function parseChartDate(value, timezoneOffsetMinutes) {
    if (value instanceof Date) return value;
    if (typeof value === 'number') return new Date(value);
    if (typeof value !== 'string') return new Date(value);

    // Treat chart timestamps as floating wall-clock values (observatory logical time).
    // Ignore timezone suffixes/offsets to avoid mixed DateTimeKind (Unspecified/Local/UTC)
    // causing cross-day/hour shifts in the browser.
    const isoMatch = value.match(
        /^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,7}))?)?(?:[zZ]|[+\-]\d{2}:\d{2})?$/
    );
    if (isoMatch) {
        const year = Number.parseInt(isoMatch[1], 10);
        const month = Number.parseInt(isoMatch[2], 10) - 1;
        const day = Number.parseInt(isoMatch[3], 10);
        const hour = Number.parseInt(isoMatch[4], 10);
        const minute = Number.parseInt(isoMatch[5], 10);
        const second = isoMatch[6] ? Number.parseInt(isoMatch[6], 10) : 0;
        const fraction = isoMatch[7] ?? '0';
        const millisecond = Number.parseInt((fraction + '000').slice(0, 3), 10);
        return new Date(year, month, day, hour, minute, second, millisecond);
    }

    return new Date(value);
}

function isTouchOptimizedTooltipDevice() {
    if (typeof window === 'undefined') {
        return false;
    }

    const maxTouchPoints = typeof navigator !== 'undefined' && Number.isFinite(navigator.maxTouchPoints)
        ? navigator.maxTouchPoints
        : 0;

    const hasCoarsePointer = window.matchMedia && window.matchMedia('(hover: none), (pointer: coarse)').matches;
    const hasTouch = hasCoarsePointer || 'ontouchstart' in window || maxTouchPoints > 0;
    if (!hasTouch) {
        return false;
    }

    const isCompactViewport = window.matchMedia
        ? window.matchMedia('(max-width: 1024px)').matches
        : (window.innerWidth || 0) <= 1024;

    return isCompactViewport;
}

function escapeTooltipHtml(value) {
    return String(value ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

function tooltipIconHtml(icon, color = 'currentColor') {
    const common = `style="width:0.86rem;height:0.86rem;flex:0 0 auto;color:${color};" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"`;
    switch (icon) {
        case 'check':
            return `<svg ${common}><path d="M20 6 9 17l-5-5"/></svg>`;
        case 'blocked':
            return `<svg ${common}><circle cx="12" cy="12" r="9"/><path d="m5 5 14 14"/></svg>`;
        case 'time':
            return `<svg ${common}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>`;
        case 'moon':
            return `<svg ${common}><path d="M21 14.2A8.5 8.5 0 0 1 9.8 3a7 7 0 1 0 11.2 11.2Z"/></svg>`;
        case 'filter':
            return `<svg ${common}><path d="M4 5h16"/><path d="M7 12h10"/><path d="M10 19h4"/></svg>`;
        case 'target':
            return `<svg ${common}><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>`;
        default:
            return `<span style="width:0.5rem;height:0.5rem;border-radius:999px;background:${color};display:inline-block;flex:0 0 auto;"></span>`;
    }
}

function tooltipLineHtml(icon, text, color = '#cbd5e1') {
    return `<div style="display:flex;align-items:center;gap:0.38rem;margin-top:0.3rem;">${tooltipIconHtml(icon, color)}<span>${escapeTooltipHtml(text)}</span></div>`;
}

function getOrCreateTouchTooltipElement(chart) {
    if (typeof document === 'undefined') {
        return null;
    }

    let tooltipEl = document.querySelector(`.am-lightweight-chart-touch-tooltip[data-chart-id="${chart.canvas.id}"]`);
    if (!tooltipEl) {
        tooltipEl = document.createElement('div');
        tooltipEl.className = 'am-lightweight-chart-touch-tooltip';
        tooltipEl.dataset.chartId = chart.canvas.id;
        tooltipEl.style.position = 'fixed';
        tooltipEl.style.left = '50%';
        tooltipEl.style.bottom = 'max(env(safe-area-inset-bottom, 0px), 0.75rem)';
        tooltipEl.style.width = 'min(22rem, calc(100vw - 1.5rem))';
        tooltipEl.style.maxWidth = 'calc(100vw - 1.5rem)';
        tooltipEl.style.padding = '0.625rem 0.8rem';
        tooltipEl.style.borderRadius = '0.9rem';
        tooltipEl.style.background = 'rgba(3, 7, 18, 0.94)';
        tooltipEl.style.border = '1px solid rgba(148, 163, 184, 0.22)';
        tooltipEl.style.boxShadow = '0 18px 40px rgba(0, 0, 0, 0.34)';
        tooltipEl.style.backdropFilter = 'blur(10px)';
        tooltipEl.style.webkitBackdropFilter = 'blur(10px)';
        tooltipEl.style.color = '#e5e7eb';
        tooltipEl.style.fontSize = '0.75rem';
        tooltipEl.style.lineHeight = '1.35';
        tooltipEl.style.pointerEvents = 'none';
        tooltipEl.style.opacity = '0';
        tooltipEl.style.transform = 'translate(-50%, 10px)';
        tooltipEl.style.transition = 'opacity 140ms ease, transform 140ms ease';
        tooltipEl.style.zIndex = '10060';
        document.body.appendChild(tooltipEl);
    }

    return tooltipEl;
}

function clearTouchTooltipHideTimer(chart) {
    if (chart && chart.$amTouchTooltipHideTimer) {
        clearTimeout(chart.$amTouchTooltipHideTimer);
        chart.$amTouchTooltipHideTimer = null;
    }
}

function hideTouchTooltip(chart) {
    const tooltipEl = getOrCreateTouchTooltipElement(chart);
    if (!tooltipEl) {
        return;
    }

    tooltipEl.style.opacity = '0';
    tooltipEl.style.transform = 'translate(-50%, 10px)';
}

function scheduleTouchTooltipHide(chart, delayMs = 900) {
    clearTouchTooltipHideTimer(chart);
    if (!chart) {
        return;
    }

    chart.$amTouchTooltipHideTimer = window.setTimeout(() => {
        hideTouchTooltip(chart);
    }, delayMs);
}

function buildTouchTooltipHtml(tooltipItems, data, parseDate) {
    if (!tooltipItems || tooltipItems.length === 0) {
        return '';
    }

    const time = tooltipItems[0].parsed?.x;
    if (!time) {
        return '';
    }

    const matchingDsoPoint = data.dsoData?.find(d => Math.abs(parseDate(d.time).getTime() - time) < 60000);
    const matchingMoonPoint = data.moonData?.find(d => Math.abs(parseDate(d.time).getTime() - time) < 60000);
    const isObservable = matchingDsoPoint?.isObservable === true;
    const statusText = isObservable ? 'Observable' : 'Blocked';
    const statusColor = isObservable ? '#86efac' : '#fca5a5';
    const timeLabel = new Date(time).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });

    const lines = [];

    if (matchingDsoPoint) {
        lines.push({ icon: 'target', color: '#93c5fd', text: `${matchingDsoPoint.altitudeRaw?.toFixed(1) ?? matchingDsoPoint.altitude?.toFixed(1)}° Alt | ${matchingDsoPoint.azimuth?.toFixed(1) ?? 0}° Az` });
    }

    if (data.showMoon && matchingMoonPoint && matchingDsoPoint?.moonDistance > 0) {
        const moonIllum = matchingMoonPoint.illumination != null
            ? ` | ${(matchingMoonPoint.illumination * 100).toFixed(0)}%`
            : '';
        lines.push({ icon: 'moon', color: '#c4b5fd', text: `${matchingMoonPoint.altitudeRaw?.toFixed(1) ?? matchingMoonPoint.altitude?.toFixed(1)}° | ${matchingDsoPoint.moonDistance.toFixed(1)}° dist${moonIllum}` });
    }

    if (matchingDsoPoint?.observableFilters?.length > 0) {
        lines.push({ icon: 'check', color: '#86efac', text: matchingDsoPoint.observableFilters.join(', ') });
    }

    if (matchingDsoPoint?.blockedFilters?.length > 0) {
        lines.push({ icon: 'blocked', color: '#fca5a5', text: matchingDsoPoint.blockedFilters.join(', ') });
    }

    const body = lines
        .map(line => tooltipLineHtml(line.icon, line.text, line.color))
        .join('');

    return `
        <div style="display:flex;align-items:center;gap:0.45rem;font-weight:700;color:#ffffff;font-size:0.8rem;">
            ${tooltipIconHtml('time', '#bfdbfe')}
            <span>${escapeTooltipHtml(timeLabel)}</span>
            <span style="display:inline-flex;align-items:center;gap:0.28rem;margin-left:0.2rem;color:${statusColor};">
                ${tooltipIconHtml(isObservable ? 'check' : 'blocked', statusColor)}
                <span>${escapeTooltipHtml(statusText)}</span>
            </span>
        </div>
        ${body}
    `;
}

function renderTouchTooltip(context, data, parseDate, useTouchLayout) {
    const { chart, tooltip } = context;
    const tooltipEl = getOrCreateTouchTooltipElement(chart);
    if (!tooltipEl) {
        return;
    }

    if (!tooltip || tooltip.opacity === 0 || !tooltip.dataPoints?.length) {
        scheduleTouchTooltipHide(chart, 120);
        return;
    }

    clearTouchTooltipHideTimer(chart);
    tooltipEl.innerHTML = buildTouchTooltipHtml(tooltip.dataPoints, data, parseDate);
    if (useTouchLayout) {
        tooltipEl.style.left = '50%';
        tooltipEl.style.right = 'auto';
        tooltipEl.style.top = 'auto';
        tooltipEl.style.bottom = 'max(env(safe-area-inset-bottom, 0px), 0.75rem)';
        tooltipEl.style.width = 'min(22rem, calc(100vw - 1.5rem))';
        tooltipEl.style.transform = 'translate(-50%, 0)';
    } else {
        const canvasRect = chart.canvas.getBoundingClientRect();
        const tooltipWidth = Math.min(352, Math.max(260, tooltipEl.offsetWidth || 300));
        const left = Math.min(
            window.innerWidth - tooltipWidth - 12,
            Math.max(12, canvasRect.left + tooltip.caretX + 14)
        );
        const top = Math.max(12, canvasRect.top + tooltip.caretY - 24);
        tooltipEl.style.left = `${left}px`;
        tooltipEl.style.right = 'auto';
        tooltipEl.style.top = `${top}px`;
        tooltipEl.style.bottom = 'auto';
        tooltipEl.style.width = 'min(22rem, calc(100vw - 1.5rem))';
        tooltipEl.style.transform = 'translate(0, 0)';
    }
    tooltipEl.style.opacity = '1';
    if (useTouchLayout) {
        scheduleTouchTooltipHide(chart);
    }
}

// Create or update an altitude chart
window.createLightweightAltitudeChart = async function(canvasId, chartData) {
    if (!await ensureChartJsLoaded()) {
        return false;
    }
    
    const canvas = document.getElementById(canvasId);
    if (!canvas) {
        return false;
    }
    
    // Destroy existing chart if any - check both our Map AND Chart.js internal registry
    if (window._chartInstances.has(canvasId)) {
        try {
            window._chartInstances.get(canvasId).destroy();
        } catch (e) { }
        window._chartInstances.delete(canvasId);
    }

    if (window._chartResizeObservers && window._chartResizeObservers.has(canvasId)) {
        try {
            window._chartResizeObservers.get(canvasId).disconnect();
        } catch (e) { }
        window._chartResizeObservers.delete(canvasId);
    }

    if (window._chartTouchGuards && window._chartTouchGuards.has(canvasId)) {
        try {
            const guard = window._chartTouchGuards.get(canvasId);
            guard.canvas.removeEventListener('touchstart', guard.onTouchStart, guard.options);
            guard.canvas.removeEventListener('touchmove', guard.onTouchMove, guard.options);
            guard.canvas.removeEventListener('touchend', guard.onTouchEnd, guard.options);
            guard.canvas.removeEventListener('touchcancel', guard.onTouchEnd, guard.options);
        } catch (e) { }
        window._chartTouchGuards.delete(canvasId);
    }
    
    // Also check Chart.js internal registry (handles cases where chart exists but not in our Map)
    const existingChart = Chart.getChart(canvas);
    if (existingChart) {
        try {
            existingChart.destroy();
        } catch (e) { }
    }
    
    const ctx = canvas.getContext('2d');
    if (!ctx) {
        console.error(`[AltChart] Failed to get 2d context from canvas`);
        return false;
    }

    const chartHost = canvas.parentElement || canvas;
    if (chartHost) {
        chartHost.style.overflowX = 'hidden';
        chartHost.style.overscrollBehaviorX = 'contain';
    }

    canvas.style.touchAction = 'pan-y';
    canvas.style.overscrollBehaviorX = 'contain';
    
    // Parse the chart data
    let data;
    try {
        data = typeof chartData === 'string' ? JSON.parse(chartData) : chartData;
    } catch (parseErr) {
        return false;
    }

    const observatoryOffsetMinutes = Number.isFinite(data?.observatoryTimezoneOffsetMinutes)
        ? data.observatoryTimezoneOffsetMinutes
        : null;
    const parseDate = (value) => parseChartDate(value, observatoryOffsetMinutes);
    const useTouchTooltipLayout = isTouchOptimizedTooltipDevice();
    
    // Prepare datasets
    const datasets = [];
    
    // DSO altitude line with segment coloring
    if (data.dsoData && data.dsoData.length > 0) {
        datasets.push({
            label: data.dsoLabel || 'DSO',
            data: data.dsoData.map(d => ({ x: parseDate(d.time), y: d.altitude })),
            borderColor: function(context) {
                const index = context.dataIndex;
                if (data.dsoData[index] && data.dsoData[index].color) {
                    return data.dsoData[index].color;
                }
                return '#22c55e'; // Default green
            },
            segment: {
                borderColor: function(context) {
                    const index = context.p0DataIndex;
                    if (data.dsoData[index] && data.dsoData[index].color) {
                        return data.dsoData[index].color;
                    }
                    return '#22c55e';
                }
            },
            borderWidth: 3,
            fill: false,
            tension: 0.1,
            pointRadius: 0,
            pointHoverRadius: 4
        });
    }
    
    // Moon altitude line
    if (data.showMoon && data.moonData && data.moonData.length > 0) {
        datasets.push({
            label: data.moonLabel || 'Moon',
            data: data.moonData.map(d => ({ x: parseDate(d.time), y: d.altitude })),
            borderColor: '#94a3b8',
            borderWidth: 2,
            borderDash: [5, 5],
            fill: false,
            tension: 0.1,
            pointRadius: 0,
            pointHoverRadius: 3
        });
    }
    
    // Custom horizon area
    if (data.horizonData && data.horizonData.length > 0) {
        datasets.push({
            label: data.horizonLabel || 'Horizon',
            data: data.horizonData.map(d => ({ x: parseDate(d.time), y: d.altitude })),
            borderColor: 'rgba(255, 21, 0, 0.6)',
            backgroundColor: 'rgba(255, 21, 0, 0.4)',
            borderWidth: 1,
            fill: true,
            tension: 0.1,
            pointRadius: 0
        });
    }
    
    // Build annotations for striplines
    const annotations = {};

    // Twilight striplines (background) - must cover full chart area
    if (data.twilightStriplines && data.twilightStriplines.length > 0) {
        data.twilightStriplines.forEach((strip, index) => {
            const startDate = parseDate(strip.start);
            const endDate = parseDate(strip.end);
            annotations[`twilight_${index}`] = {
                type: 'box',
                xMin: startDate,
                xMax: endDate,
                yMin: 0,
                yMax: 90,
                backgroundColor: strip.color,
                borderWidth: 0,
                drawTime: 'beforeDatasetsDraw',
                z: -10
            };
        });
    }
    
    // Filter blocking striplines removed - not needed (was not in SF chart)
    
    // Session time highlight
    if (data.sessionStart && data.sessionEnd) {
        annotations['session'] = {
            type: 'box',
            xMin: parseDate(data.sessionStart),
            xMax: parseDate(data.sessionEnd),
            yMin: 0,
            yMax: 90,
            backgroundColor: 'rgba(76, 175, 80, 0.3)',
            borderColor: '#4CAF50',
            borderWidth: 2,
            drawTime: 'beforeDatasetsDraw',
            label: {
                display: true,
                content: data.sessionLabel || 'Session',
                position: 'start',
                color: '#ffffff',
                font: { size: 12, weight: 600 }
            }
        };
    }
    
    // Current time vertical line
    if (data.currentTime) {
        annotations['nowLine'] = {
            type: 'line',
            xMin: parseDate(data.currentTime),
            xMax: parseDate(data.currentTime),
            yMin: 0,
            yMax: 90,
            borderColor: 'rgba(34, 197, 94, 0.8)',
            borderWidth: 2,
            borderDash: [6, 2]
        };
        // Separate label annotation at top, positioned to the right of the line
        annotations['nowLabel'] = {
            type: 'label',
            xValue: parseDate(data.currentTime),
            yValue: 82,
            content: data.nowLabel || 'Now',
            backgroundColor: 'transparent',
            color: '#22c55e',
            font: { size: 11, weight: '500' },
            position: { x: 'start' },
            xAdjust: -9,
            rotation: 270
        };
    }
    
    // Midnight vertical line
    if (data.midnightTime) {
        annotations['midnightLine'] = {
            type: 'line',
            xMin: parseDate(data.midnightTime),
            xMax: parseDate(data.midnightTime),
            yMin: 0,
            yMax: 90,
            borderColor: 'rgba(255, 255, 255, 0.4)',
            borderWidth: 1,
            borderDash: [2, 4]
        };
        // Separate label annotation at top, positioned to the right of the line
        annotations['midnightLabel'] = {
            type: 'label',
            xValue: parseDate(data.midnightTime),
            yValue: 82,
            content: data.midnightLabel || '',
            backgroundColor: 'transparent',
            color: '#ffffff',
            font: { size: 10, weight: '400' },
            position: { x: 'start' },
            xAdjust: -9,
            rotation: 270
        };
    }
    
    // Set Time vertical line (blue) - user-selected time in SkyAtlas
    if (data.setTime) {
        annotations['setTimeLine'] = {
            type: 'line',
            xMin: parseDate(data.setTime),
            xMax: parseDate(data.setTime),
            yMin: 0,
            yMax: 90,
            borderColor: 'rgba(59, 130, 246, 0.9)',
            borderWidth: 2,
            borderDash: [6, 2]
        };
        // Separate label annotation at top
        annotations['setTimeLabel'] = {
            type: 'label',
            xValue: parseDate(data.setTime),
            yValue: 82,
            content: data.setTimeLabel || 'Set Time',
            backgroundColor: 'transparent',
            color: '#3b82f6',
            font: { size: 11, weight: '500' },
            position: { x: 'start' },
            xAdjust: -9,
            rotation: 270
        };
    }
    
    // Create the chart
    let chart;
    try {
        chart = new Chart(ctx, {
        type: 'line',
        data: { datasets },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: false,
            layout: {
                padding: 0
            },
            interaction: {
                mode: useTouchTooltipLayout ? 'nearest' : 'index',
                intersect: false
            },
            scales: {
                x: {
                    type: 'time',
                    min: data.startTime ? parseDate(data.startTime) : undefined,
                    max: data.endTime ? parseDate(data.endTime) : undefined,
                    time: {
                        unit: 'hour',
                        stepSize: 2,
                        displayFormats: {
                            hour: 'HH:mm'
                        }
                    },
                    grid: {
                        display: false,
                        drawBorder: false
                    },
                    ticks: {
                        color: '#9ca3af',
                        maxRotation: 0,
                        autoSkip: true,
                        maxTicksLimit: 10
                    },
                    border: {
                        display: false
                    }
                },
                y: {
                    min: 0,
                    max: 90,
                    ticks: {
                        stepSize: 15,
                        callback: function(value) {
                            return value + '°';
                        },
                        color: '#9ca3af'
                    },
                    grid: {
                        display: false,
                        drawBorder: false
                    },
                    border: {
                        display: false
                    }
                }
            },
            plugins: {
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        color: '#e5e7eb',
                        usePointStyle: true,
                        pointStyle: 'line',
                        padding: 15,
                        generateLabels: function(chart) {
                            const datasets = chart.data.datasets;
                            return datasets.map((dataset, i) => {
                                const isDSO = dataset.label?.includes('DSO');
                                const isHorizon = dataset.label?.includes('Horizon');
                                const isMoon = dataset.label?.includes('Moon');
                                let color = dataset.borderColor;
                                if (isDSO) color = '#6b7280'; // Grey for DSO
                                if (isMoon) color = '#94a3b8';
                                if (isHorizon) color = 'rgba(255, 21, 0, 0.6)';
                                return {
                                    text: dataset.label,
                                    fontColor: '#e5e7eb',
                                    fillStyle: color,
                                    strokeStyle: color,
                                    lineWidth: dataset.borderWidth,
                                    hidden: !chart.isDatasetVisible(i),
                                    index: i,
                                    pointStyle: isHorizon ? 'triangle' : 'line',
                                    lineDash: dataset.borderDash || []
                                };
                            });
                        }
                    }
                },
                annotation: {
                    annotations: annotations
                },
                tooltip: {
                    enabled: false,
                    external: function(context) {
                        renderTouchTooltip(context, data, parseDate, useTouchTooltipLayout);
                    },
                    position: useTouchTooltipLayout ? 'nearest' : 'average',
                    backgroundColor: 'rgba(0, 0, 0, 0.9)',
                    titleColor: '#ffffff',
                    titleFont: { size: 12, weight: '600' },
                    bodyColor: '#e5e7eb',
                    bodyFont: { size: 11 },
                    borderColor: 'rgba(255,255,255,0.2)',
                    borderWidth: 1,
                    padding: 8,
                    cornerRadius: 6,
                    displayColors: false,
                    callbacks: {
                        title: function(context) {
                            const date = new Date(context[0].parsed.x);
                            const dsoPoint = data.dsoData?.find(d => Math.abs(parseDate(d.time).getTime() - context[0].parsed.x) < 60000);
                            const isObservable = dsoPoint?.isObservable === true;
                            const statusText = isObservable ? 'Observable' : 'Blocked';
                            return `Time ${date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}  ${statusText}`;
                        },
                        label: function(context) {
                            return null;
                        },
                        afterBody: function(context) {
                            const time = context[0].parsed.x;
                            const dsoPoint = data.dsoData?.find(d => Math.abs(parseDate(d.time).getTime() - time) < 60000);
                            const moonPoint = data.moonData?.find(d => Math.abs(parseDate(d.time).getTime() - time) < 60000);
                            
                            const lines = [];
                            
                            if (dsoPoint) {
                                lines.push(`${dsoPoint.altitudeRaw?.toFixed(1) ?? dsoPoint.altitude?.toFixed(1)}° Alt | ${dsoPoint.azimuth?.toFixed(1) ?? 0}° Az`);
                            }
                            
                            if (data.showMoon && moonPoint && dsoPoint?.moonDistance > 0) {
                                const moonIllum = moonPoint.illumination != null ? ` | ${(moonPoint.illumination * 100).toFixed(0)}%` : '';
                                lines.push(`Moon: ${moonPoint.altitudeRaw?.toFixed(1) ?? moonPoint.altitude?.toFixed(1)}° | ${dsoPoint.moonDistance.toFixed(1)}° dist${moonIllum}`);
                            }
                            
                            // Filter observability - compact
                            if (dsoPoint?.observableFilters?.length > 0 || dsoPoint?.blockedFilters?.length > 0) {
                                if (dsoPoint.observableFilters?.length > 0) {
                                    lines.push(`Observable filters: ${dsoPoint.observableFilters.join(', ')}`);
                                }
                                if (dsoPoint.blockedFilters?.length > 0) {
                                    lines.push(`Blocked filters: ${dsoPoint.blockedFilters.join(', ')}`);
                                }
                            }
                            
                            return lines;
                        }
                    }
                }
            }
        }
    });
    } catch (chartErr) {
        return false;
    }
    
    window._chartInstances.set(canvasId, chart);

    if (isTouchOptimizedTooltipDevice()) {
        let touchStartX = 0;
        let touchStartY = 0;
        let horizontalGestureLocked = false;
        const options = { passive: false };
        const onTouchStart = (event) => {
            const touch = event.touches && event.touches[0];
            if (!touch) {
                return;
            }

            clearTouchTooltipHideTimer(chart);
            touchStartX = touch.clientX;
            touchStartY = touch.clientY;
            horizontalGestureLocked = false;
        };

        const onTouchMove = (event) => {
            const touch = event.touches && event.touches[0];
            if (!touch) {
                return;
            }

            const dx = touch.clientX - touchStartX;
            const dy = touch.clientY - touchStartY;

            if (!horizontalGestureLocked && Math.abs(dx) > 6 && Math.abs(dx) > Math.abs(dy)) {
                horizontalGestureLocked = true;
            }

            if (horizontalGestureLocked && event.cancelable) {
                event.preventDefault();
            }
        };

        const onTouchEnd = () => {
            horizontalGestureLocked = false;
            scheduleTouchTooltipHide(chart, 650);
        };

        canvas.addEventListener('touchstart', onTouchStart, options);
        canvas.addEventListener('touchmove', onTouchMove, options);
        canvas.addEventListener('touchend', onTouchEnd, options);
        canvas.addEventListener('touchcancel', onTouchEnd, options);

        window._chartTouchGuards.set(canvasId, {
            canvas,
            onTouchStart,
            onTouchMove,
            onTouchEnd,
            options
        });
    }

    if (typeof ResizeObserver !== 'undefined') {
        const resizeHost = canvas.parentElement || canvas;
        const observer = new ResizeObserver(() => {
            try {
                chart.resize();
            } catch (e) { }
        });
        observer.observe(resizeHost);
        window._chartResizeObservers.set(canvasId, observer);
    }

    return true;
};

// Destroy a chart
window.destroyLightweightChart = function(canvasId) {
    if (window._chartTouchGuards && window._chartTouchGuards.has(canvasId)) {
        try {
            const guard = window._chartTouchGuards.get(canvasId);
            guard.canvas.removeEventListener('touchstart', guard.onTouchStart, guard.options);
            guard.canvas.removeEventListener('touchmove', guard.onTouchMove, guard.options);
            guard.canvas.removeEventListener('touchend', guard.onTouchEnd, guard.options);
            guard.canvas.removeEventListener('touchcancel', guard.onTouchEnd, guard.options);
        } catch (e) { }
        window._chartTouchGuards.delete(canvasId);
    }

    if (window._chartResizeObservers && window._chartResizeObservers.has(canvasId)) {
        try {
            window._chartResizeObservers.get(canvasId).disconnect();
        } catch (e) { }
        window._chartResizeObservers.delete(canvasId);
    }

    if (window._chartInstances.has(canvasId)) {
        const chart = window._chartInstances.get(canvasId);
        clearTouchTooltipHideTimer(chart);
        const tooltipEl = document.querySelector(`.am-lightweight-chart-touch-tooltip[data-chart-id="${canvasId}"]`);
        if (tooltipEl) {
            tooltipEl.remove();
        }
        chart.destroy();
        window._chartInstances.delete(canvasId);
        return true;
    }
    return false;
};

// Update chart data without full recreation
window.updateLightweightChartData = async function(canvasId, chartData) {
    if (!window._chartInstances.has(canvasId)) {
        return await window.createLightweightAltitudeChart(canvasId, chartData);
    }
    
    const chart = window._chartInstances.get(canvasId);
    const data = typeof chartData === 'string' ? JSON.parse(chartData) : chartData;
    
    // Update DSO data
    if (chart.data.datasets[0] && data.dsoData) {
        const observatoryOffsetMinutes = Number.isFinite(data?.observatoryTimezoneOffsetMinutes)
            ? data.observatoryTimezoneOffsetMinutes
            : null;
        chart.data.datasets[0].data = data.dsoData.map(d => ({ x: parseChartDate(d.time, observatoryOffsetMinutes), y: d.altitude }));
    }
    
    // Update moon data
    if (chart.data.datasets[1] && data.moonData) {
        const observatoryOffsetMinutes = Number.isFinite(data?.observatoryTimezoneOffsetMinutes)
            ? data.observatoryTimezoneOffsetMinutes
            : null;
        chart.data.datasets[1].data = data.moonData.map(d => ({ x: parseChartDate(d.time, observatoryOffsetMinutes), y: d.altitude }));
    }
    
    chart.update('none');
    return true;
};

