/**
 * UI utilities — modal helpers, lightbox, toast, confirm.
 */

import { confirmResolve, setConfirmResolve } from './state.ts';
import { API_BASE, getHeaders } from './api.ts';
import { t } from './i18n.ts';

/** Returns true on touch devices where auto-focus would pop up the virtual keyboard */
export function isTouchDevice() {
    return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
}

/** Focus element only on non-touch devices (avoids virtual keyboard popup on mobile) */
export function desktopFocus(el: HTMLElement | null) {
    if (el && !isTouchDevice()) el.focus();
}

export function toggleHint(btn: HTMLElement) {
    const hint = btn.closest('.label-row')!.nextElementSibling as HTMLElement | null;
    if (hint && hint.classList.contains('input-hint')) {
        const visible = hint.style.display !== 'none';
        hint.style.display = visible ? 'none' : 'block';
        btn.classList.toggle('active', !visible);
        btn.setAttribute('aria-expanded', String(!visible));
    }
}

const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';

export function trapFocus(modal: any) {
    modal._trapHandler = (e: KeyboardEvent) => {
        if (e.key !== 'Tab') return;
        const focusable = [...modal.querySelectorAll(FOCUSABLE)].filter((el: HTMLElement) => el.offsetParent !== null);
        if (!focusable.length) return;
        const first = focusable[0];
        const last = focusable[focusable.length - 1];
        if (e.shiftKey) {
            if (document.activeElement === first) { e.preventDefault(); last.focus(); }
        } else {
            if (document.activeElement === last) { e.preventDefault(); first.focus(); }
        }
    };
    modal.addEventListener('keydown', modal._trapHandler);
}

export function releaseFocus(modal: any) {
    if (modal._trapHandler) {
        modal.removeEventListener('keydown', modal._trapHandler);
        modal._trapHandler = null;
    }
}

export function setupBackdropClose(modal: any, closeFn: () => void) {
    if (modal._backdropCloseSetup) {
        modal._backdropCloseFn = closeFn;
        return;
    }
    modal._backdropCloseFn = closeFn;
    let mouseDownTarget = null;
    modal.addEventListener('mousedown', (e) => { mouseDownTarget = e.target; });
    modal.addEventListener('mouseup', (e) => {
        if (mouseDownTarget === modal && e.target === modal && modal._backdropCloseFn) modal._backdropCloseFn();
        mouseDownTarget = null;
    });
    modal.onclick = null;
    modal._backdropCloseSetup = true;
}

let _lockCount = 0;

export function lockBody() {
    if (_lockCount === 0) {
        document.documentElement.classList.add('modal-open');
    }
    _lockCount++;
}

export function unlockBody() {
    if (_lockCount <= 0) return;
    _lockCount--;
    if (_lockCount === 0) {
        document.documentElement.classList.remove('modal-open');
    }
}

export function openLightbox(imageSrc: string, statsHtml?: string) {
    const lightbox = document.getElementById('image-lightbox')!;
    const img = document.getElementById('lightbox-image') as HTMLImageElement;
    const statsEl = document.getElementById('lightbox-stats')!;
    img.src = imageSrc;
    if (statsHtml) {
        statsEl.innerHTML = statsHtml;
        statsEl.style.display = '';
    } else {
        statsEl.style.display = 'none';
    }
    lightbox.classList.add('active');
    lockBody();
}

export function closeLightbox(event?: Event) {
    if (event && event.target && (event.target as HTMLElement).closest('.lightbox-content')) return;
    const lightbox = document.getElementById('image-lightbox')!;
    lightbox.classList.remove('active');
    const img = document.getElementById('lightbox-image') as HTMLImageElement;
    if (img.src.startsWith('blob:')) URL.revokeObjectURL(img.src);
    img.src = '';
    img.style.display = '';
    const content = lightbox.querySelector('.lightbox-content') as HTMLElement | null;
    if (content) content.style.width = '';  // Reset any custom width
    document.getElementById('lightbox-stats')!.style.display = 'none';
    const spinner = lightbox.querySelector('.lightbox-spinner') as HTMLElement | null;
    if (spinner) spinner.style.display = 'none';
    unlockBody();
}

let _toastTimer: ReturnType<typeof setTimeout> | null = null;

export function showToast(message: string, type = 'info') {
    if (_toastTimer) clearTimeout(_toastTimer);
    const toast = document.getElementById('toast')!;
    toast.textContent = message;
    toast.className = `toast ${type} show`;
    _toastTimer = setTimeout(() => {
        toast.className = 'toast';
        _toastTimer = null;
    }, 3000);
}

let _undoTimer: ReturnType<typeof setTimeout> | null = null;

/**
 * Show a toast with an Undo button. Executes `action` after `delay` ms
 * unless the user clicks Undo (which calls `undoFn`).
 */
export function showUndoToast(message: string, action: () => void, undoFn: () => void, delay = 5000) {
    if (_undoTimer) { clearTimeout(_undoTimer); _undoTimer = null; }

    const toast = document.getElementById('toast')!;
    toast.className = 'toast info show';
    toast.style.setProperty('--toast-duration', `${delay}ms`);
    toast.innerHTML = `<div class="toast-with-action">
        <span class="toast-message">${message}</span>
        <button class="toast-undo-btn">${t('common.undo')}</button>
    </div>
    <div class="toast-timer"></div>`;

    let cancelled = false;

    const undoBtn = toast.querySelector('.toast-undo-btn')!;
    undoBtn.addEventListener('click', () => {
        cancelled = true;
        if (_undoTimer) { clearTimeout(_undoTimer); _undoTimer = null; }
        undoFn();
        toast.className = 'toast';
        toast.innerHTML = '';
    }, { once: true });

    _undoTimer = setTimeout(() => {
        _undoTimer = null;
        if (!cancelled) action();
        toast.className = 'toast';
        toast.innerHTML = '';
    }, delay);
}

export function showConfirm(message: string, title: string | null = null) {
    return new Promise((resolve) => {
        setConfirmResolve(resolve);

        const modal = document.getElementById('confirm-modal')!;
        const titleEl = document.getElementById('confirm-modal-title')!;
        const messageEl = document.getElementById('confirm-message')!;
        const yesBtn = document.getElementById('confirm-yes-btn')!;
        const noBtn = document.getElementById('confirm-no-btn')!;

        titleEl.textContent = title || t('confirm.title');
        messageEl.textContent = message;
        yesBtn.textContent = t('confirm.yes');
        noBtn.textContent = t('confirm.no');

        modal.style.display = 'flex';
        lockBody();
        trapFocus(modal);
    });
}

export function closeConfirmModal(result: boolean) {
    const modal = document.getElementById('confirm-modal')!;
    releaseFocus(modal);
    modal.style.display = 'none';
    unlockBody();

    if (confirmResolve) {
        confirmResolve(result);
        setConfirmResolve(null);
    }
}

export async function openFullImageLightbox(imageSource: string) {
    try {
        const resp = await fetch(`${API_BASE}/picture-sources/full-image?source=${encodeURIComponent(imageSource)}`, {
            headers: getHeaders()
        });
        if (!resp.ok) return;
        const blob = await resp.blob();
        const blobUrl = URL.createObjectURL(blob);
        openLightbox(blobUrl);
    } catch (err) {
        console.error('Failed to load full image:', err);
    }
}

// Overlay spinner (used by capture/stream tests)
export function showOverlaySpinner(text: string, duration = 0) {
    const existing = document.getElementById('overlay-spinner');
    if (existing) {
        if (window.overlaySpinnerTimer) {
            clearInterval(window.overlaySpinnerTimer);
            window.overlaySpinnerTimer = null;
        }
        existing.remove();
    }

    // AbortController for cancelling in-flight fetch
    window._overlayAbortController = new AbortController();

    const overlay = document.createElement('div');
    overlay.id = 'overlay-spinner';
    overlay.className = 'overlay-spinner';

    // Close button
    const closeBtn = document.createElement('button');
    closeBtn.className = 'overlay-spinner-close';
    closeBtn.innerHTML = '&times;';
    closeBtn.title = 'Cancel';
    closeBtn.onclick = () => hideOverlaySpinner();
    overlay.appendChild(closeBtn);

    // ESC key handler
    function onEsc(e: KeyboardEvent) {
        if (e.key === 'Escape') hideOverlaySpinner();
    }
    window._overlayEscHandler = onEsc;
    document.addEventListener('keydown', onEsc);

    const progressContainer = document.createElement('div');
    progressContainer.className = 'progress-container';

    const radius = 56;
    const circumference = 2 * Math.PI * radius;

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '120');
    svg.setAttribute('height', '120');
    svg.setAttribute('class', 'progress-ring');

    const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    bgCircle.setAttribute('class', 'progress-ring-bg');
    bgCircle.setAttribute('cx', '60');
    bgCircle.setAttribute('cy', '60');
    bgCircle.setAttribute('r', String(radius));

    const progressCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    progressCircle.setAttribute('class', 'progress-ring-circle');
    progressCircle.setAttribute('cx', '60');
    progressCircle.setAttribute('cy', '60');
    progressCircle.setAttribute('r', String(radius));
    progressCircle.style.strokeDasharray = String(circumference);
    progressCircle.style.strokeDashoffset = String(circumference);

    svg.appendChild(bgCircle);
    svg.appendChild(progressCircle);

    const progressContent = document.createElement('div');
    progressContent.className = 'progress-content';
    const progressPercentage = document.createElement('div');
    progressPercentage.className = 'progress-percentage';
    progressPercentage.textContent = '0%';
    progressContent.appendChild(progressPercentage);

    progressContainer.appendChild(svg);
    progressContainer.appendChild(progressContent);

    const spinnerText = document.createElement('div');
    spinnerText.className = 'spinner-text';
    spinnerText.textContent = text;

    // Preview image (hidden until updateOverlayPreview is called)
    const previewImg = document.createElement('img');
    previewImg.id = 'overlay-preview-img';
    previewImg.className = 'overlay-preview-img';
    previewImg.style.display = 'none';

    // Preview stats (hidden until updateOverlayPreview is called)
    const previewStats = document.createElement('div');
    previewStats.id = 'overlay-preview-stats';
    previewStats.className = 'overlay-preview-stats';
    previewStats.style.display = 'none';

    overlay.appendChild(progressContainer);
    overlay.appendChild(spinnerText);
    overlay.appendChild(previewImg);
    overlay.appendChild(previewStats);
    document.body.appendChild(overlay);

    if (duration > 0) {
        const startTime = Date.now();
        window.overlaySpinnerTimer = setInterval(() => {
            const elapsed = (Date.now() - startTime) / 1000;
            const progress = Math.min(elapsed / duration, 1);
            const percentage = Math.round(progress * 100);
            const offset = circumference - (progress * circumference);
            progressCircle.style.strokeDashoffset = String(offset);
            progressPercentage.textContent = `${percentage}%`;
            if (progress >= 1) {
                clearInterval(window.overlaySpinnerTimer!);
                window.overlaySpinnerTimer = null;
            }
        }, 100);
    }
}

export function hideOverlaySpinner() {
    if (window.overlaySpinnerTimer) {
        clearInterval(window.overlaySpinnerTimer);
        window.overlaySpinnerTimer = null;
    }
    if (window._overlayEscHandler) {
        document.removeEventListener('keydown', window._overlayEscHandler);
        window._overlayEscHandler = null;
    }
    if (window._overlayAbortController) {
        window._overlayAbortController.abort();
        window._overlayAbortController = null;
    }
    const overlay = document.getElementById('overlay-spinner');
    if (overlay) overlay.remove();
}

/**
 * Update the overlay spinner with a live preview thumbnail and stats.
 * Call this while the spinner is open to show intermediate test frames.
 */
export function updateOverlayPreview(thumbnailSrc: string, stats: any) {
    const img = document.getElementById('overlay-preview-img') as HTMLImageElement | null;
    const statsEl = document.getElementById('overlay-preview-stats');
    if (!img || !statsEl) return;
    if (thumbnailSrc) {
        img.src = thumbnailSrc;
        img.style.display = '';
    }
    if (stats) {
        statsEl.textContent = `${t('test.frames')}: ${stats.frame_count}  |  ${t('test.fps')}: ${stats.fps}  |  ${t('test.avg_capture')}: ${stats.avg_capture_ms}ms`;
        statsEl.style.display = '';
    }
}

// ── Inline field validation ──

/** Mark a field as invalid with an error message. */
export function setFieldError(input: HTMLInputElement | HTMLSelectElement, message: string) {
    input.classList.add('field-invalid');
    clearFieldError(input); // remove existing
    if (message) {
        const msg = document.createElement('span');
        msg.className = 'field-error-msg';
        msg.textContent = message;
        input.parentElement?.appendChild(msg);
    }
}

/** Clear field error state. */
export function clearFieldError(input: HTMLInputElement | HTMLSelectElement) {
    input.classList.remove('field-invalid');
    const existing = input.parentElement?.querySelector('.field-error-msg');
    if (existing) existing.remove();
}

/** Validate a required field on blur. Returns true if valid. */
export function validateRequired(input: HTMLInputElement | HTMLSelectElement, errorMsg?: string): boolean {
    const value = input.value.trim();
    if (!value) {
        setFieldError(input, errorMsg || t('validation.required'));
        return false;
    }
    clearFieldError(input);
    return true;
}

/** Set up blur validation on required fields within a container. */
export function setupBlurValidation(container: HTMLElement) {
    const fields = container.querySelectorAll('input[required], select[required]') as NodeListOf<HTMLInputElement | HTMLSelectElement>;
    fields.forEach(field => {
        field.addEventListener('blur', () => validateRequired(field));
        field.addEventListener('input', () => {
            if (field.classList.contains('field-invalid')) clearFieldError(field);
        });
    });
}

/** Toggle the thin loading bar on a tab panel during data refresh.
 *  Delays showing the bar by 400ms so quick loads never flash it. */
const _refreshTimers: Record<string, ReturnType<typeof setTimeout>> = {};
export function setTabRefreshing(containerId: string, refreshing: boolean) {
    const panel = document.getElementById(containerId)?.closest('.tab-panel');
    if (!panel) return;
    if (refreshing) {
        _refreshTimers[containerId] = setTimeout(() => {
            panel.classList.add('refreshing');
        }, 400);
    } else {
        clearTimeout(_refreshTimers[containerId]);
        delete _refreshTimers[containerId];
        panel.classList.remove('refreshing');
    }
}

/** Format a large number compactly: 999 → "999", 1200 → "1.2K", 2500000 → "2.5M" */
export function formatCompact(n: number | null | undefined) {
    if (n == null || n < 0) return '-';
    if (n < 1000) return String(n);
    if (n < 1_000_000) {
        const v = n / 1000;
        return (v < 10 ? v.toFixed(1) : Math.round(v)) + 'K';
    }
    if (n < 1_000_000_000) {
        const v = n / 1_000_000;
        return (v < 10 ? v.toFixed(1) : Math.round(v)) + 'M';
    }
    const v = n / 1_000_000_000;
    return (v < 10 ? v.toFixed(1) : Math.round(v)) + 'B';
}

export function formatUptime(seconds: number | null | undefined) {
    if (!seconds || seconds <= 0) return '-';
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    const s = Math.floor(seconds % 60);
    if (h > 0) return t('time.hours_minutes', { h, m });
    if (m > 0) return t('time.minutes_seconds', { m, s });
    return t('time.seconds', { s });
}
