// Cache for the full dxstats API response, so we can reload on the fly if the user changes the value of their continent
// in the select box
let dxStatsData = null;
// Forecast chart
let kpChart = null;

// Load solar conditions
function loadSolarConditions() {
    $.getJSON('/api/v1/solar', function(jsonData) {

        // HF

        const hfConditionClass = { 'Good': 'bg-success-subtle', 'Fair': 'bg-warning-subtle', 'Poor': 'bg-danger-subtle' };

        if (jsonData.hf_conditions) {
            Object.entries(jsonData.hf_conditions).forEach(function([key, condition]) {
                const cell = $('#hf-conditions-' + key);
                cell.text(condition);
                cell.addClass(hfConditionClass[condition]);
            });
        }

        // VHF

        if (jsonData.vhf_conditions) {
            Object.entries(jsonData.vhf_conditions).forEach(function([key, condition]) {
                const cell = $('#vhf-conditions-' + key);
                cell.text(condition);
                let vhfClass;
                if (condition === 'Band Closed') {
                    vhfClass = 'bg-danger-subtle';
                } else if (condition.includes('High')) {
                    vhfClass = 'bg-warning-subtle';
                } else {
                    vhfClass = 'bg-success-subtle';
                }
                cell.addClass(vhfClass);
            });
        }
        if (jsonData.aurora_latitude !== null && jsonData.aurora_latitude !== undefined) {
            $('#vhf-conditions-aurora-lat').text(jsonData.aurora_latitude + '°');
        }

        // Solar Weather

        const swFields = {
            'sfi':                  'sw-sfi',
            'sunspots':             'sw-sunspots',
            'band_conditions_desc': 'sw-solar-flux-desc',
            'k_index':              'sw-k-index',
            'a_index':              'sw-a-index',
            'geomag_field':         'sw-geomag-field',
            'geomag_storm_scale':   'sw-geomag-storm-scale',
            'geomag_storm_desc':    'sw-geomag-storm-desc',
            'geomag_noise':         'sw-geomag-noise',
            'x_ray':                'sw-x-ray',
            'blackout_desc':        'sw-xray-desc',
            'proton_flux':          'sw-proton-flux',
            'solar_storm_scale':    'sw-solar-storm-scale',
            'proton_flux_desc':     'sw-proton-desc',
            'electron_flux':        'sw-electron-flux',
            'electron_flux_desc':   'sw-electron-desc',
        };
        Object.entries(swFields).forEach(function([field, id]) {
            const val = jsonData[field];
            if (val !== null && val !== undefined) {
                $('#' + id).text(val);
            }
        });

        // Solar Weather - colouring

        function applySwClass(valsId, descId, cls) {
            $('#' + valsId).addClass(cls);
            $('#' + descId).addClass(cls);
        }

        const sfi = jsonData.sfi;
        if (sfi !== null && sfi !== undefined) {
            applySwClass('sw-solar-flux-vals', 'sw-solar-flux-desc',
                sfi > 120 ? 'bg-success-subtle' : sfi > 90 ? 'bg-warning-subtle' : 'bg-danger-subtle');
        }

        const kIndex = jsonData.k_index;
        if (kIndex !== null && kIndex !== undefined) {
            applySwClass('sw-geomag-vals', 'sw-geomag-desc',
                kIndex < 5 ? 'bg-success-subtle' : kIndex < 7 ? 'bg-warning-subtle' : 'bg-danger-subtle');
        }

        const xRay = jsonData.x_ray;
        if (xRay) {
            const letter = xRay[0].toUpperCase();
            const xRayClass = (letter === 'X') ? 'bg-danger-subtle'
                            : (letter === 'M') ? 'bg-warning-subtle'
                            : 'bg-success-subtle';
            applySwClass('sw-xray-vals', 'sw-xray-desc', xRayClass);
        }

        const protonFlux = jsonData.proton_flux;
        if (protonFlux !== null && protonFlux !== undefined) {
            applySwClass('sw-proton-vals', 'sw-proton-desc',
                protonFlux <= 100 ? 'bg-success-subtle' : protonFlux <= 10000 ? 'bg-warning-subtle' : 'bg-danger-subtle');
        }

        const electronFlux = jsonData.electron_flux;
        if (electronFlux !== null && electronFlux !== undefined) {
            applySwClass('sw-electron-vals', 'sw-electron-desc',
                electronFlux <= 100 ? 'bg-success-subtle' : electronFlux <= 1000 ? 'bg-warning-subtle' : 'bg-danger-subtle');
        }

        // Forecast

        renderKIndexForecast(jsonData.k_index_forecast);
        renderSolarStormForecast(jsonData.solar_storm_forecast);
        renderBlackoutForecast(jsonData.blackout_forecast_r1r2, jsonData.blackout_forecast_r3_or_greater);
    });
}

// Render the K-index forecast as a Chart.js bar chart, one bar per 3-hour UTC period
function renderKIndexForecast(data) {
    if (!data) return;

    const entries = Object.entries(data)
        .map(([tsStr, kp]) => ({ ts: parseFloat(tsStr), kp }))
        .sort((a, b) => a.ts - b.ts);
    if (entries.length === 0) return;

    // x-axis labels. Show date only on the first bar of each day, time on all bars
    const labels = entries.map((e, i) => {
        const dt = new Date(e.ts * 1000);
        const timeStr = String(dt.getUTCHours()).padStart(2, '0') + ':00';
        const dateStr = dt.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', timeZone: 'UTC' });
        const prev = i > 0 ? new Date(entries[i - 1].ts * 1000) : null;
        const newDay = !prev || prev.toISOString().slice(0, 10) !== dt.toISOString().slice(0, 10);
        return newDay ? [timeStr, dateStr] : timeStr;
    });

    // Inherit colours from Bootstrap CSS variables so that dark mode inherently works. We want bar colours that are not
    // quite as saturated as the Bootstrap success/warning/danger colours but not as desaturated as the "subtle"
    // versions, so use tinycolor to apply some transparency.
    const style = getComputedStyle(document.documentElement);
    const withAlpha = hex => tinycolor(hex).setAlpha(0.8).toRgbString();
    const colors = entries.map(e =>
        e.kp < 4.5 ? withAlpha(style.getPropertyValue('--bs-success').trim())
        : e.kp < 6.5 ? withAlpha(style.getPropertyValue('--bs-warning').trim())
        : withAlpha(style.getPropertyValue('--bs-danger').trim())
    );
    const textColor = style.getPropertyValue('--bs-body-color').trim() || '#666';
    const gridColor = style.getPropertyValue('--bs-border-color').trim() || 'rgba(128,128,128,0.3)';

    if (kpChart) { kpChart.destroy(); }

    kpChart = new Chart(document.getElementById('forecast-kp-chart'), {
        type: 'bar',
        data: {
            labels,
            datasets: [{
                data: entries.map(e => e.kp),
                backgroundColor: colors,
                borderWidth: 0,
            }]
        },
        options: {
            responsive: true,
            aspectRatio: 3,
            plugins: {
                legend: {
                    display: false
                },
                tooltip: {
                    enabled: false
                }
            },
            scales: {
                x: {
                    ticks: { color: textColor, maxRotation: 45, minRotation: 0 },
                    grid:  { color: gridColor },
                },
                y: {
                    min: 0,
                    max: 9,
                    title: { display: true, text: 'Kp', color: textColor },
                    // Include geomagnetic storm levels (Gx) on the y-axis as well as the Kp index
                    ticks: {
                        stepSize: 1,
                        color: textColor,
                        callback: v => v > 4 ? `(G${v - 4}) ${v}` : String(v),
                    },
                    grid:  { color: gridColor },
                }
            }
        }
    });
}

// Render the solar storm forecast table
function renderSolarStormForecast(data) {
    if (!data) return;

    const entries = Object.entries(data)
        .map(([tsStr, pct]) => ({ ts: parseFloat(tsStr), pct }))
        .sort((a, b) => a.ts - b.ts);

    // Header
    const headRow = $('#forecast-solar-storm-head').empty().append('<th></th>');
    entries.forEach(({ ts }) => {
        const label = new Date(ts * 1000)
            .toLocaleDateString('en-US', { day: '2-digit', month: 'short', timeZone: 'UTC' });
        headRow.append(`<th>${label}</th>`);
    });

    // Single data row: "S1 or greater" label + one cell per date
    const tr = $('<tr>').append('<td>S1 or greater</td>');
    entries.forEach(({ pct }) => {
        const td = $('<td>').text(pct + '%');
        td.addClass(pct < 50 ? 'bg-success-subtle' : pct < 75 ? 'bg-warning-subtle' : 'bg-danger-subtle');
        tr.append(td);
    });
    $('#forecast-solar-storm-tbody').empty().append(tr);
}

// Render the radio blackout forecast table
function renderBlackoutForecast(r1r2Data, r3Data) {
    if (!r1r2Data && !r3Data) return;

    const tsSet = new Set([
        ...Object.keys(r1r2Data || {}),
        ...Object.keys(r3Data || {})
    ]);
    const entries = [...tsSet]
        .map(tsStr => ({
            ts: parseFloat(tsStr),
            r1r2: r1r2Data ? r1r2Data[tsStr] : undefined,
            r3:   r3Data   ? r3Data[tsStr]   : undefined
        }))
        .sort((a, b) => a.ts - b.ts);

    // Header
    const headRow = $('#forecast-blackout-head').empty().append('<th></th>');
    entries.forEach(({ ts }) => {
        const label = new Date(ts * 1000)
            .toLocaleDateString('en-GB', { day: '2-digit', month: 'short', timeZone: 'UTC' });
        headRow.append(`<th>${label}</th>`);
    });

    // Two data rows: R1-R2 and R3+
    function makeRow(rowLabel, getValue) {
        const tr = $('<tr>').append(`<td>${rowLabel}</td>`);
        entries.forEach(entry => {
            const pct = getValue(entry);
            const td = $('<td>');
            if (pct !== undefined) {
                td.text(pct + '%');
                td.addClass(pct < 50 ? 'bg-success-subtle' : pct < 75 ? 'bg-warning-subtle' : 'bg-danger-subtle');
            }
            tr.append(td);
        });
        return tr;
    }

    $('#forecast-blackout-tbody').empty()
        .append(makeRow('R1-R2',        e => e.r1r2))
        .append(makeRow('R3 or greater', e => e.r3));
}

// Render the DX stats table for the currently selected DE continent
function renderDxStats() {
    if (!dxStatsData) { return; }
    const deContinent = $('#dxstats-de-continent').val();
    const deData = dxStatsData[deContinent];
    if (!deData) { return; }

    const cells = [];
    Object.entries(deData).forEach(function([dxContinent, bands]) {
        Object.entries(bands).forEach(function([band, count]) {
            const cell = $('#dxstats-' + dxContinent + '-' + band);
            cell.text(count);
            cells.push({ cell, count });
        });
    });

    const counts = cells.map(function(c) { return c.count; });
    const min = Math.min(...counts);
    const max = Math.max(...counts);
    const range = max - min;
    cells.forEach(function({ cell, count }) {
        const t = range > 0 ? (count - min) / range : 0;
        const cls = t === 0 ? 'bg-danger-subtle' : t < 0.05 ? 'bg-warning-subtle' : 'bg-success-subtle';
        cell.removeClass('bg-danger-subtle bg-warning-subtle bg-success-subtle').addClass(cls);
    });
}

// Called when the DE continent select changes
function dxStatsContientChanged() {
    saveSettings();
    renderDxStats();
}

// Fetch DX stats from the API and render
function loadDxStats() {
    $.getJSON('/api/v1/dxstats', function(jsonData) {
        dxStatsData = jsonData;
        renderDxStats();
    });
}

// Startup
$(document).ready(function() {
    loadSettings();
    loadSolarConditions();
    loadDxStats();
});
