let pollInterval = 500;
let chartMaxPoints = 300;
let lastTimestamp = 0;
let chart = null;
let firstTimestamp = null;
let lastApiError = '';
let actionBannerTimer = null;
let heartbeatMisses = 0;
let heartbeatInstanceId = null;
let controlsEnabled = true;
let deferredInstallPrompt = null;
let uiMode = 'nail1';
let activeNailId = 'nail1';
let historyBuffer = [];
let schedulerTimesByNail = { nail1: [], nail2: [] };
let states = { nail1: null, nail2: null };

function nailIdFromNum(num) {
    return String(num) === '2' ? 'nail2' : 'nail1';
}

function nailNumFromId(id) {
    return id === 'nail2' ? 2 : 1;
}

function currentNailId() {
    return activeNailId;
}

function nowHms() {
    return new Date().toLocaleTimeString();
}

function setLastAck(message, ok=true) {
    const el = document.getElementById('last-ack');
    if (!el) return;
    el.className = 'last-ack ' + (ok ? 'ok' : 'err');
    el.textContent = 'Last command: ' + message + ' at ' + nowHms();
}

function showAction(message, type='info', timeoutMs=3000) {
    const banner = document.getElementById('action-banner');
    const msg = document.getElementById('action-message');
    if (!banner || !msg) return;
    msg.textContent = message;
    banner.className = 'action-banner ' + type;
    if (actionBannerTimer) clearTimeout(actionBannerTimer);
    if (timeoutMs > 0) {
        actionBannerTimer = setTimeout(function() {
            banner.className = 'action-banner hidden';
        }, timeoutMs);
    }
}

function setControlsEnabled(enabled) {
    controlsEnabled = enabled;
    document.querySelectorAll('button').forEach(function(btn) {
        if (btn.id === 'autotune-stop-btn' && enabled) return;
        btn.disabled = !enabled;
    });
}

function setBackendStatus(mode, text) {
    const el = document.getElementById('backend-status');
    if (!el) return;
    el.className = 'backend-status ' + mode;
    el.textContent = text;
}

function setConnectionStatus(connected) {
    const dot = document.getElementById('connection-status');
    if (!dot) return;
    if (connected) {
        dot.className = 'status-dot connected';
        dot.title = 'Connected';
        setBackendStatus('online', 'Backend: Online');
    } else {
        dot.className = 'status-dot disconnected';
        dot.title = 'Disconnected';
        setBackendStatus('offline', 'Backend: Offline');
    }
}

function setUiMode(mode, persist=true) {
    if (mode === 'nail2') activeNailId = 'nail2';
    if (mode === 'nail1') activeNailId = 'nail1';
    uiMode = (mode === 'simple' || mode === 'nail2' || mode === 'nail1') ? mode : 'nail1';
    document.body.classList.remove('ui-simple', 'ui-nail1', 'ui-nail2');
    document.body.classList.add('ui-' + uiMode);

    ['simple', 'nail1', 'nail2'].forEach(function(key) {
        const btn = document.getElementById('mode-' + key + '-btn');
        if (btn) btn.classList.toggle('active', key === uiMode);
    });

    const label = document.getElementById('advanced-nail-label');
    if (label) label.textContent = 'Nail ' + nailNumFromId(activeNailId);

    if (persist) {
        try {
            localStorage.setItem('pinail_ui_mode', uiMode);
        } catch (e) {
            console.warn('Could not persist ui mode:', e);
        }
    }

    resetChartForNail();
}

function updateWallClock() {
    const el = document.getElementById('wall-clock');
    if (!el) return;
    const d = new Date();
    const hh = ('0' + d.getHours()).slice(-2);
    const mm = ('0' + d.getMinutes()).slice(-2);
    const ss = ('0' + d.getSeconds()).slice(-2);
    el.textContent = hh + ':' + mm + ':' + ss;
}

function normalizeTimeString(t) {
    const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec((t || '').trim());
    if (!m) return null;
    return ('0' + m[1]).slice(-2) + ':' + ('0' + m[2]).slice(-2);
}

function renderSchedulerTimes() {
    const id = currentNailId();
    const times = schedulerTimesByNail[id] || [];
    const container = document.getElementById('sched-time-list');
    if (!container) return;
    if (!times.length) {
        container.innerHTML = '<span class="autotune-status idle">No descent times configured.</span>';
        return;
    }
    container.innerHTML = times.map(function(t) {
        return '<span class="sched-time-item">' + t + '<button class="sched-del" onclick="removeSchedulerTime(\'' + t + '\')">x</button></span>';
    }).join('');
}

function addSchedulerTime() {
    const input = document.getElementById('sched-time-input');
    if (!input) return;
    const norm = normalizeTimeString(input.value);
    if (!norm) {
        showAction('Invalid time format. Use HH:MM.', 'error', 3000);
        return;
    }
    const id = currentNailId();
    if (!schedulerTimesByNail[id]) schedulerTimesByNail[id] = [];
    if (schedulerTimesByNail[id].indexOf(norm) === -1) {
        schedulerTimesByNail[id].push(norm);
        schedulerTimesByNail[id].sort();
        renderSchedulerTimes();
        saveSchedulerSettings();
    }
}

function removeSchedulerTime(t) {
    const id = currentNailId();
    schedulerTimesByNail[id] = (schedulerTimesByNail[id] || []).filter(function(x) { return x !== t; });
    renderSchedulerTimes();
    saveSchedulerSettings();
}

function schedulerEnabledChanged() {
    saveSchedulerSettings();
}

function updateSimpleCards() {
    ['nail1', 'nail2'].forEach(function(id) {
        const s = states[id];
        if (!s) return;
        const suffix = id === 'nail2' ? 'nail2' : 'nail1';
        const tEl = document.getElementById('simple-temp-' + suffix);
        const mEl = document.getElementById('simple-mode-' + suffix);
        const spEl = document.getElementById('simple-target-' + suffix);
        const pEl = document.getElementById('simple-power-' + suffix);
        const inEl = document.getElementById('simple-setpoint-' + suffix);
        if (tEl) tEl.textContent = s.temp.toFixed(1);
        if (mEl) mEl.textContent = s.mode || 'grounded';
        if (spEl) spEl.textContent = s.effective_setpoint.toFixed(0);
        if (inEl && document.activeElement !== inEl) inEl.value = s.setpoint;
        if (pEl) {
            pEl.textContent = s.enabled ? 'ON' : 'OFF';
            pEl.className = 'power-mini ' + (s.enabled ? 'on' : 'off');
        }
    });
}

function updateErrorStats() {
    const el = document.getElementById('error-stats');
    if (!el) return;
    if (!historyBuffer.length) {
        el.textContent = 'Err(3m): --';
        return;
    }
    const nowTs = historyBuffer[historyBuffer.length - 1].timestamp;
    const window = historyBuffer.filter(function(p) { return p.timestamp >= (nowTs - 180); });
    if (!window.length) {
        el.textContent = 'Err(3m): --';
        return;
    }
    let sumAbs = 0;
    let sumSq = 0;
    let maxAbs = 0;
    let sumSigned = 0;
    window.forEach(function(p) {
        const target = (typeof p.flight_setpoint === 'number') ? p.flight_setpoint : p.setpoint;
        const err = p.temp - target;
        const abs = Math.abs(err);
        sumAbs += abs;
        sumSq += err * err;
        sumSigned += err;
        if (abs > maxAbs) maxAbs = abs;
    });
    const n = window.length;
    const mae = sumAbs / n;
    const rmse = Math.sqrt(sumSq / n);
    const bias = sumSigned / n;
    el.textContent = 'Err(3m) MAE ' + mae.toFixed(1) + 'F | RMSE ' + rmse.toFixed(1) + 'F | Max ' + maxAbs.toFixed(1) + 'F | Bias ' + bias.toFixed(1) + 'F';
}

function setAutotuneUi(tune) {
    const statusEl = document.getElementById('autotune-status');
    const startBtn = document.getElementById('autotune-start-btn');
    const stopBtn = document.getElementById('autotune-stop-btn');
    const pill = document.getElementById('autotune-pill');
    if (!statusEl || !startBtn || !stopBtn) return;
    if (tune && tune.active) {
        statusEl.className = 'autotune-status running';
        const phase = tune.phase ? (' ' + tune.phase) : '';
        statusEl.textContent = 'Running' + phase + ' (' + tune.high_peaks + '/' + tune.cycles + ' peaks)';
        if (pill) {
            pill.className = 'autotune-pill running';
            pill.textContent = 'Autotune: Running' + phase;
        }
        startBtn.disabled = true;
        stopBtn.disabled = false;
        return;
    }
    startBtn.disabled = false;
    stopBtn.disabled = true;
    if (tune && tune.last_result) {
        statusEl.className = 'autotune-status done';
        statusEl.textContent = 'Complete: kP ' + tune.last_result.kP + ', kI ' + tune.last_result.kI + ', kD ' + tune.last_result.kD;
        if (pill) {
            pill.className = 'autotune-pill done';
            pill.textContent = 'Autotune: Complete';
        }
        return;
    }
    statusEl.className = 'autotune-status idle';
    statusEl.textContent = (tune && tune.message) ? tune.message : 'Idle';
    if (pill) {
        pill.className = 'autotune-pill idle';
        pill.textContent = 'Autotune: Idle';
    }
}

function initChart() {
    const ctx = document.getElementById('temp-chart').getContext('2d');
    chart = new Chart(ctx, {
        type: 'line',
        data: { datasets: [
            { label: 'Temperature (F)', borderColor: '#ff6b35', backgroundColor: 'rgba(255, 107, 53, 0.1)', borderWidth: 2, pointRadius: 0, fill: true, data: [], yAxisID: 'y' },
            { label: 'Target Setpoint (F)', borderColor: '#4ecdc4', borderWidth: 1.5, borderDash: [5, 5], pointRadius: 0, fill: false, data: [], yAxisID: 'y' },
            { label: 'Flight Trajectory (F)', borderColor: '#f39c12', borderWidth: 2, pointRadius: 0, fill: false, data: [], yAxisID: 'y' },
            { label: 'Output', borderColor: '#45b7d1', backgroundColor: 'rgba(69, 183, 209, 0.1)', borderWidth: 1, pointRadius: 0, fill: true, data: [], yAxisID: 'y1' },
        ]},
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: false,
            interaction: { mode: 'index', intersect: false },
            scales: {
                x: { type: 'linear', ticks: { color: '#888', callback: function(value) { return Math.round(value) + 's'; }, maxTicksLimit: 10 }, grid: { color: 'rgba(255,255,255,0.05)' } },
                y: { type: 'linear', position: 'left', title: { display: true, text: 'Temperature (F)', color: '#aaa' }, ticks: { color: '#ff6b35' }, grid: { color: 'rgba(255,255,255,0.05)' }, suggestedMin: 0, suggestedMax: 700 },
                y1: { type: 'linear', position: 'right', title: { display: true, text: 'PID Output', color: '#aaa' }, ticks: { color: '#45b7d1' }, grid: { drawOnChartArea: false }, suggestedMin: 0 },
            },
            plugins: { legend: { labels: { color: '#ccc', boxWidth: 12 } } },
        },
    });
}

function resetChartForNail() {
    firstTimestamp = null;
    lastTimestamp = 0;
    historyBuffer = [];
    if (chart) {
        chart.data.datasets.forEach(function(ds) { ds.data = []; });
        chart.update('none');
    }
    updateErrorStats();
}

function addChartData(points) {
    if (!chart || !points.length) return;
    if (firstTimestamp === null) firstTimestamp = points[0].timestamp;
    points.forEach(function(p) {
        const x = p.timestamp - firstTimestamp;
        const inRamp = p.mode === 'takeoff' || p.mode === 'descent';
        const flightSp = inRamp && (typeof p.flight_setpoint === 'number') ? p.flight_setpoint : null;
        chart.data.datasets[0].data.push({ x: x, y: p.temp });
        chart.data.datasets[1].data.push({ x: x, y: p.setpoint });
        chart.data.datasets[2].data.push({ x: x, y: flightSp });
        chart.data.datasets[3].data.push({ x: x, y: p.output });
        historyBuffer.push(p);
    });
    const newestTs = historyBuffer.length ? historyBuffer[historyBuffer.length - 1].timestamp : 0;
    historyBuffer = historyBuffer.filter(function(p) { return p.timestamp >= (newestTs - 600); });
    chart.data.datasets.forEach(function(ds) {
        if (ds.data.length > chartMaxPoints) ds.data = ds.data.slice(ds.data.length - chartMaxPoints);
    });
    chart.update('none');
    updateErrorStats();
}

async function fetchJSON(url, options) {
    try {
        const resp = await fetch(url, options);
        const payload = await resp.json();
        if (!resp.ok) throw new Error(payload.error || ('HTTP ' + resp.status));
        lastApiError = '';
        return payload;
    } catch (e) {
        console.error('API error:', url, e);
        lastApiError = String(e);
        if (String(e).indexOf('HTTP') < 0) setConnectionStatus(false);
        return null;
    }
}

async function apiPost(path, payload, nailId) {
    const body = Object.assign({}, payload || {}, { nail: nailId || currentNailId() });
    return fetchJSON(path, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
    });
}

function renderPresets(presets) {
    const container = document.getElementById('presets-container');
    if (!container) return;
    const buttons = Object.entries(presets || {}).map(function(entry) {
        const name = entry[0];
        const temp = entry[1];
        return '<button class="preset-btn" onclick="applyPreset(\'' + name.replace(/'/g, "\\'") + '\')">' + name + ' (' + temp + '&deg;F)</button>';
    }).join('');
    container.innerHTML = buttons;
}

function renderAdvanced(status) {
    if (!status) return;
    const tempEl = document.getElementById('current-temp');
    tempEl.textContent = status.temp.toFixed(1);
    const delta = Math.abs(status.temp - status.setpoint);
    if (!status.enabled) tempEl.className = 'temp-value';
    else if (delta < 15) tempEl.className = 'temp-value temp-good';
    else if (delta < 50) tempEl.className = 'temp-value temp-warming';
    else tempEl.className = 'temp-value temp-cold';

    const effective = (typeof status.effective_setpoint === 'number') ? status.effective_setpoint : status.setpoint;
    document.getElementById('current-setpoint').textContent = effective.toFixed(0);

    const mode = status.mode || 'grounded';
    const modePill = document.getElementById('mode-pill');
    modePill.className = 'mode-pill ' + mode;
    modePill.textContent = 'Mode: ' + mode;

    const modeEta = document.getElementById('mode-eta');
    if ((mode === 'takeoff' || mode === 'descent') && typeof status.mode_eta_seconds === 'number') {
        const sec = Math.max(0, Math.round(status.mode_eta_seconds));
        const mm = Math.floor(sec / 60);
        const ss = sec % 60;
        modeEta.textContent = (mode === 'takeoff' ? 'Time to cruise: ' : 'Time to grounded: ') + mm + 'm ' + ss + 's';
    } else {
        modeEta.textContent = 'ETA: --';
    }

    const schedEta = document.getElementById('sched-eta');
    const schedEnabled = !!(status.scheduler && status.scheduler.enabled);
    if (schedEnabled && typeof status.next_cutoff_seconds === 'number') {
        const sec = Math.max(0, Math.round(status.next_cutoff_seconds));
        const hh = Math.floor(sec / 3600);
        const mm = Math.floor((sec % 3600) / 60);
        const ss = sec % 60;
        schedEta.textContent = 'Time to descent: ' + hh + 'h ' + mm + 'm ' + ss + 's';
    } else if (schedEnabled) schedEta.textContent = 'Time to descent: --';
    else schedEta.textContent = 'Scheduler: off';

    const powerBtn = document.getElementById('power-btn');
    powerBtn.textContent = status.enabled ? 'ON' : 'OFF';
    powerBtn.className = 'power-btn ' + (status.enabled ? 'on' : 'off');

    const banner = document.getElementById('safety-banner');
    if (status.safety_tripped) {
        banner.classList.remove('hidden');
        document.getElementById('safety-message').textContent = 'SAFETY TRIP: ' + status.safety_reason;
    } else banner.classList.add('hidden');

    document.getElementById('status-output').textContent = status.output.toFixed(1) + ' / ' + (status.config.loop_size_ms || '?');
    const relayEl = document.getElementById('status-relay');
    relayEl.textContent = status.relay_on ? 'ON' : 'OFF';
    relayEl.className = 'value ' + (status.relay_on ? 'relay-on' : 'relay-off');
    document.getElementById('status-uptime').textContent = status.uptime_seconds !== null ? (Math.floor(status.uptime_seconds / 60) + 'm ' + Math.floor(status.uptime_seconds % 60) + 's') : '--';
    document.getElementById('status-loops').textContent = status.loop_count;
    const tcEl = document.getElementById('status-tc');
    tcEl.textContent = status.thermocouple_connected ? 'OK' : 'DISCONNECTED';
    tcEl.className = 'value ' + (status.thermocouple_connected ? 'tc-ok' : 'tc-err');

    if (document.activeElement.id !== 'setpoint-input') document.getElementById('setpoint-input').value = status.setpoint;
    if (document.activeElement.id !== 'pid-kp') document.getElementById('pid-kp').value = status.pid.kP;
    if (document.activeElement.id !== 'pid-ki') document.getElementById('pid-ki').value = status.pid.kI;
    if (document.activeElement.id !== 'pid-kd') document.getElementById('pid-kd').value = status.pid.kD;
    if (document.activeElement.id !== 'pid-pmode') document.getElementById('pid-pmode').value = status.pid.proportional_mode || (status.pid.proportional_on_measurement ? 'measurement' : 'error');
    if (document.activeElement.id !== 'control-loop-size') document.getElementById('control-loop-size').value = status.config.loop_size_ms;
    if (document.activeElement.id !== 'control-sleep-time') document.getElementById('control-sleep-time').value = status.config.sleep_time;

    const flight = status.flight || {};
    if (document.activeElement.id !== 'flight-takeoff-seconds') document.getElementById('flight-takeoff-seconds').value = flight.takeoff_seconds || 90;
    if (document.activeElement.id !== 'flight-descent-seconds') document.getElementById('flight-descent-seconds').value = flight.descent_seconds || 90;
    if (document.activeElement.id !== 'flight-descent-target') document.getElementById('flight-descent-target').value = flight.descent_target_f || 120;
    const fm = document.getElementById('flight-mode-status');
    if (fm) fm.textContent = 'Current mode: ' + (status.mode || 'grounded') + ' (Power button handles Grounded/Cruise)';

    if (document.activeElement.id !== 'sched-enabled') document.getElementById('sched-enabled').value = status.scheduler.enabled ? 'true' : 'false';
    const incoming = (status.scheduler.cutoff_times || []).slice().sort();
    if (JSON.stringify(incoming) !== JSON.stringify(schedulerTimesByNail[currentNailId()] || [])) {
        schedulerTimesByNail[currentNailId()] = incoming;
        renderSchedulerTimes();
    }

    setAutotuneUi(status.autotune || {});
}

async function pollStatus() {
    const payload = await fetchJSON('/api/status/all');
    if (!payload || !payload.nails) return;
    setConnectionStatus(true);
    states.nail1 = payload.nails.nail1 || states.nail1;
    states.nail2 = payload.nails.nail2 || states.nail2;
    if (payload.presets) renderPresets(payload.presets);
    updateSimpleCards();
    renderAdvanced(states[currentNailId()]);
}

async function pollHistory() {
    const nailNum = nailNumFromId(currentNailId());
    const data = await fetchJSON('/api/history?since=' + lastTimestamp + '&nail=' + nailNum);
    if (!data || !data.length) return;
    lastTimestamp = data[data.length - 1].timestamp;
    addChartData(data);
}

async function pollHeartbeat() {
    const hb = await fetchJSON('/api/heartbeat?ts=' + Date.now());
    if (!hb || !hb.ok) {
        heartbeatMisses += 1;
        if (heartbeatMisses >= 2) {
            setBackendStatus('reconnecting', 'Backend: Reconnecting...');
            setControlsEnabled(false);
        }
        return;
    }
    if (heartbeatMisses >= 2) showAction('Backend reconnected.', 'success', 2500);
    heartbeatMisses = 0;
    setConnectionStatus(true);
    setControlsEnabled(true);
    if (heartbeatInstanceId === null) heartbeatInstanceId = hb.instance_id;
    else if (heartbeatInstanceId !== hb.instance_id) {
        showAction('Backend restarted. Reloading UI...', 'info', 1500);
        setTimeout(function() { window.location.reload(); }, 1200);
    }
}

async function togglePower() {
    const id = currentNailId();
    const s = states[id];
    const target = !(s && s.enabled);
    const result = await apiPost('/api/power', { enabled: target }, id);
    if (!result) return setLastAck('power failed', false);
    setLastAck('power ' + (result.enabled ? 'ON' : 'OFF') + ' (' + id + ')', true);
}

async function simpleTogglePower(num) {
    const id = nailIdFromNum(num);
    const s = states[id];
    const target = !(s && s.enabled);
    const result = await apiPost('/api/power', { enabled: target }, id);
    if (!result) return setLastAck('power failed', false);
    setLastAck('power ' + (result.enabled ? 'ON' : 'OFF') + ' (' + id + ')', true);
}

async function applySetpoint() {
    const value = parseFloat(document.getElementById('setpoint-input').value);
    if (isNaN(value)) return;
    const result = await apiPost('/api/setpoint', { setpoint: value }, currentNailId());
    if (!result) return setLastAck('setpoint failed', false);
    setLastAck('setpoint ' + value + 'F (' + currentNailId() + ')', true);
}

async function simpleApplySetpoint(num) {
    const id = nailIdFromNum(num);
    const input = document.getElementById('simple-setpoint-' + id);
    const value = parseFloat(input.value);
    if (isNaN(value)) return;
    const result = await apiPost('/api/setpoint', { setpoint: value }, id);
    if (!result) return setLastAck('setpoint failed', false);
    setLastAck('setpoint ' + value + 'F (' + id + ')', true);
}

function adjustSetpoint(delta) {
    const input = document.getElementById('setpoint-input');
    input.value = parseFloat(input.value) + delta;
    applySetpoint();
}

async function applyPID() {
    const kp = parseFloat(document.getElementById('pid-kp').value);
    const ki = parseFloat(document.getElementById('pid-ki').value);
    const kd = parseFloat(document.getElementById('pid-kd').value);
    const pMode = document.getElementById('pid-pmode').value;
    if (isNaN(kp) || isNaN(ki) || isNaN(kd)) return;
    const result = await apiPost('/api/pid', { kP: kp, kI: ki, kD: kd, proportional_mode: pMode }, currentNailId());
    if (!result) return setLastAck('PID apply failed', false);
    setLastAck('PID applied (' + currentNailId() + ')', true);
}

async function applyControlTiming() {
    const loopSize = parseInt(document.getElementById('control-loop-size').value, 10);
    const sleepTime = parseFloat(document.getElementById('control-sleep-time').value);
    if (isNaN(loopSize) || isNaN(sleepTime)) return;
    let payload = { loop_size_ms: loopSize, sleep_time: sleepTime };
    let result = await apiPost('/api/control', payload, currentNailId());
    if (!result && (lastApiError || '').indexOf('confirm_extreme') >= 0) {
        if (window.confirm('This timing is EXTREME and can overheat quickly. Continue anyway?')) {
            payload.confirm_extreme = true;
            result = await apiPost('/api/control', payload, currentNailId());
        }
    }
    if (!result) {
        setLastAck('timing apply failed', false);
        showAction(lastApiError || 'Timing update failed', 'error', 4000);
        return;
    }
    setLastAck('timing updated (' + currentNailId() + ')', true);
}

function applyTimingProfile(profile) {
    if (profile === 'conservative') {
        document.getElementById('control-loop-size').value = 3000;
        document.getElementById('control-sleep-time').value = 0.4;
    } else if (profile === 'balanced') {
        document.getElementById('control-loop-size').value = 2500;
        document.getElementById('control-sleep-time').value = 0.25;
    } else if (profile === 'responsive') {
        document.getElementById('control-loop-size').value = 1800;
        document.getElementById('control-sleep-time').value = 0.2;
    }
    applyControlTiming();
}

async function setFlightMode(mode) {
    const takeoff = parseFloat(document.getElementById('flight-takeoff-seconds').value);
    const descent = parseFloat(document.getElementById('flight-descent-seconds').value);
    const descentTarget = parseFloat(document.getElementById('flight-descent-target').value);
    const result = await apiPost('/api/flight', {
        mode: mode,
        takeoff_seconds: takeoff,
        descent_seconds: descent,
        descent_target_f: descentTarget,
    }, currentNailId());
    if (!result) {
        setLastAck('mode ' + mode + ' failed', false);
        return;
    }
    setLastAck('mode ' + mode + ' (' + currentNailId() + ')', true);
}

async function saveSchedulerSettings() {
    const id = currentNailId();
    const enabled = document.getElementById('sched-enabled').value === 'true';
    const times = (schedulerTimesByNail[id] || []).slice();
    const result = await apiPost('/api/scheduler', { enabled: enabled, cutoff_times: times }, id);
    if (!result) {
        setLastAck('scheduler update failed', false);
        return;
    }
    schedulerTimesByNail[id] = (result.scheduler && result.scheduler.cutoff_times) ? result.scheduler.cutoff_times.slice() : times;
    renderSchedulerTimes();
    setLastAck('scheduler updated (' + id + ')', true);
}

async function resetPID() {
    const result = await apiPost('/api/pid/reset', {}, currentNailId());
    if (!result) return setLastAck('PID reset failed', false);
    setLastAck('PID reset (' + currentNailId() + ')', true);
}

async function startAutotune() {
    const target = parseFloat(document.getElementById('setpoint-input').value);
    const result = await apiPost('/api/autotune/start', { setpoint: target }, currentNailId());
    if (!result) return setLastAck('autotune start failed', false);
    setLastAck('autotune started (' + currentNailId() + ')', true);
}

async function stopAutotune() {
    const result = await apiPost('/api/autotune/stop', {}, currentNailId());
    if (!result) return setLastAck('autotune stop failed', false);
    setLastAck('autotune stopped (' + currentNailId() + ')', true);
}

async function applyPreset(name) {
    const result = await apiPost('/api/preset/' + encodeURIComponent(name), {}, currentNailId());
    if (!result) return setLastAck('preset failed', false);
    setLastAck('preset ' + name + ' (' + currentNailId() + ')', true);
}

async function resetSafety() {
    const result = await apiPost('/api/safety/reset', {}, currentNailId());
    if (!result) return setLastAck('safety reset failed', false);
    setLastAck('safety reset (' + currentNailId() + ')', true);
}

function isStandalone() {
    return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
}

function showInstallButton(show) {
    const btn = document.getElementById('install-btn');
    if (!btn) return;
    btn.hidden = !show;
}

async function installApp() {
    if (!deferredInstallPrompt) {
        showAction('Install prompt unavailable. Use your browser menu to install.', 'info', 5000);
        return;
    }
    deferredInstallPrompt.prompt();
    deferredInstallPrompt = null;
    showInstallButton(false);
}

document.addEventListener('DOMContentLoaded', function() {
    const setpointInput = document.getElementById('setpoint-input');
    if (setpointInput) {
        setpointInput.addEventListener('keydown', function(e) {
            if (e.key === 'Enter') applySetpoint();
        });
    }

    let savedMode = null;
    try {
        savedMode = localStorage.getItem('pinail_ui_mode');
    } catch (e) {
        savedMode = null;
    }
    if (savedMode === 'advanced') savedMode = 'nail1';
    setUiMode(savedMode || 'nail1', false);

    const schedInput = document.getElementById('sched-time-input');
    if (schedInput) {
        schedInput.addEventListener('keydown', function(e) {
            if (e.key === 'Enter') {
                e.preventDefault();
                addSchedulerTime();
            }
        });
        schedInput.addEventListener('blur', function() {
            const n = normalizeTimeString(schedInput.value);
            if (n) schedInput.value = n;
        });
    }

    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/static/sw.js').catch(function(err) {
            console.error('Service worker register failed:', err);
        });
    }

    if (isStandalone()) showInstallButton(false);
});

window.addEventListener('beforeinstallprompt', function(e) {
    e.preventDefault();
    deferredInstallPrompt = e;
    if (!isStandalone()) showInstallButton(true);
});

window.addEventListener('appinstalled', function() {
    deferredInstallPrompt = null;
    showInstallButton(false);
    showAction('piNail2 installed.', 'success', 4000);
});

initChart();
setInterval(pollStatus, pollInterval);
setInterval(pollHistory, pollInterval);
setInterval(pollHeartbeat, 2000);
setInterval(updateWallClock, 1000);
pollStatus();
pollHistory();
pollHeartbeat();
updateWallClock();
