/**
 * Modal base class — eliminates open/close/dirty-check boilerplate.
 *
 * Simple modals:   const modal = new Modal('my-modal-id');
 * Dirty-check:     class MyModal extends Modal { snapshotValues() { ... } }
 */

import { t } from './i18n.ts';
import { lockBody, unlockBody, setupBackdropClose, showConfirm, trapFocus, releaseFocus, isTouchDevice } from './ui.ts';

export class Modal {
    static _stack: Modal[] = [];

    el: HTMLElement | null;
    errorEl: HTMLElement | null;
    _lock: boolean;
    _backdrop: boolean;
    _initialValues: Record<string, any>;
    _closing: boolean;
    _previousFocus: Element | null = null;

    constructor(elementId: string, { backdrop = true, lock = true } = {}) {
        this.el = document.getElementById(elementId);
        this.errorEl = this.el?.querySelector('.modal-error') as HTMLElement | null;
        this._lock = lock;
        this._backdrop = backdrop;
        this._initialValues = {};
        this._closing = false;
    }

    get isOpen() {
        return this.el?.style.display === 'flex';
    }

    open() {
        this._previousFocus = document.activeElement;
        this.el!.style.display = 'flex';
        if (this._lock) lockBody();
        if (this._backdrop) setupBackdropClose(this.el!, () => this.close());
        trapFocus(this.el!);
        Modal._stack = Modal._stack.filter(m => m !== this);
        Modal._stack.push(this);
        // Auto-focus first visible input (skip on touch to avoid virtual keyboard)
        if (!isTouchDevice()) {
            requestAnimationFrame(() => {
                const input = this.el!.querySelector(
                    '.modal-body input:not([type="hidden"]):not([disabled]):not([style*="display:none"]):not([style*="display: none"]), .modal-body select:not([disabled]), .modal-body textarea:not([disabled])'
                ) as HTMLElement | null;
                if (input && input.offsetParent !== null) input.focus();
            });
        }
    }

    forceClose() {
        releaseFocus(this.el!);
        this.el!.style.display = 'none';
        if (this._lock) unlockBody();
        this._initialValues = {};
        this.hideError();
        this.onForceClose();
        Modal._stack = Modal._stack.filter(m => m !== this);
        if (this._previousFocus && typeof (this._previousFocus as HTMLElement).focus === 'function') {
            (this._previousFocus as HTMLElement).focus({ preventScroll: true });
            this._previousFocus = null;
        }
    }

    async close() {
        if (this._closing) return;
        if (this.isDirty()) {
            this._closing = true;
            const confirmed = await showConfirm(t('modal.discard_changes'));
            this._closing = false;
            if (!confirmed) return;
        }
        this.forceClose();
    }

    /** Override in subclass to define tracked fields for dirty checking. */
    snapshotValues() { return {}; }

    /** Override if current values differ from snapshot format. */
    currentValues() { return this.snapshotValues(); }

    snapshot() {
        this._initialValues = this.snapshotValues();
    }

    isDirty() {
        const cur = this.currentValues();
        return Object.keys(this._initialValues).some(k => this._initialValues[k] !== cur[k]);
    }

    showError(msg: string) {
        if (this.errorEl) {
            this.errorEl.textContent = msg;
            this.errorEl.style.display = 'block';
        }
    }

    hideError() {
        if (this.errorEl) this.errorEl.style.display = 'none';
    }

    /** Hook for subclass cleanup on force-close (canvas, observers, etc.). */
    onForceClose() {}

    $(id: string) {
        return document.getElementById(id);
    }

    static closeTopmost() {
        const top = Modal._stack[Modal._stack.length - 1];
        if (top) { top.close(); return true; }
        return false;
    }
}
