/**
 * Modul: Shared Modal-System
 * Zweck: Einheitliches Modal mit Focus-Trap, Escape-Handler, Overlay-Click,
 *        Focus-Restore, Scroll-Lock und aria-modal.
 *        Auf Mobile: Bottom Sheet mit Swipe-to-Close und Slide-out-Animation.
 * Abhängigkeiten: CSS-Klassen aus layout.css (.modal-overlay, .modal-panel, etc.)
 *                 i18n.js (t)
 *
 * API:
 *   openModal({ title, content, onSave, onDelete, size }) → void
 *   closeModal() → void
 */

import { t } from '/i18n.js';

let activeOverlay = null;
let previouslyFocused = null;
let focusTrapHandler = null;
let _initialFormSnapshot = null;
let _isClosing = false;

// Overlay-Dimming: theme-color abdunkeln im Standalone-Modus
const OVERLAY_THEME_COLOR = '#1A1A1A';

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

// --------------------------------------------------------
// Focus-Trap (Spec §5.2)
// --------------------------------------------------------

function trapFocus(container) {
  focusTrapHandler = (e) => {
    // Tab-Trap: Fokus innerhalb des Modals halten
    if (e.key === 'Tab') {
      const focusable = container.querySelectorAll(FOCUSABLE);
      if (!focusable.length) return;
      const first = focusable[0];
      const last  = focusable[focusable.length - 1];

      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
      return;
    }

    // Enter in einzeiligen Inputs/Selects → zum nächsten Feld springen
    if (e.key === 'Enter') {
      const active = document.activeElement;
      const isInput = active.tagName === 'INPUT' && active.type !== 'submit' && active.type !== 'button';
      const isSelect = active.tagName === 'SELECT';

      if (isInput || isSelect) {
        const focusable = Array.from(container.querySelectorAll(FOCUSABLE));
        const idx = focusable.indexOf(active);
        const next = focusable[idx + 1];

        if (next && next.tagName !== 'BUTTON') {
          e.preventDefault();
          next.focus();
        }
        // Beim letzten Feld oder wenn Next ein Button ist: Submit auslösen
        if (!next || next.tagName === 'BUTTON') {
          const submitBtn = container.querySelector('button[type="submit"], .btn--primary');
          if (submitBtn && !submitBtn.disabled) {
            e.preventDefault();
            submitBtn.click();
          }
        }
      }
    }
  };
  container.addEventListener('keydown', focusTrapHandler);

  // Virtual Keyboard: Focused Input in sichtbaren Bereich scrollen
  function onInputFocus(e) {
    const tag = e.target.tagName;
    if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') return;
    setTimeout(() => {
      e.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }, 300);
  }
  container.addEventListener('focusin', onInputFocus);
  container._onInputFocus = onInputFocus;

  // Focus first focusable element
  const first = container.querySelector(FOCUSABLE);
  if (first) {
    setTimeout(() => first.focus(), 50);
  }
}

// --------------------------------------------------------
// Dirty-Check Helpers
// --------------------------------------------------------

function serializeForm(container) {
  const inputs = container.querySelectorAll('input, select, textarea');
  return Array.from(inputs).map((el) => `${el.name || el.id}=${el.value}`).join('&');
}

function isFormDirty(container) {
  if (!_initialFormSnapshot) return false;
  return serializeForm(container) !== _initialFormSnapshot;
}

// --------------------------------------------------------
// Escape-Handler
// --------------------------------------------------------

function onEscape(e) {
  if (e.key === 'Escape') closeModal();
}

// --------------------------------------------------------
// Swipe-to-Close (Mobile)
// --------------------------------------------------------

function _wireSheetSwipe(panel) {
  let startY = 0;
  let dragging = false;

  // Scroll position is now on the body, not the panel itself
  const scrollBody = panel.querySelector('.modal-panel__body');

  panel.addEventListener('touchstart', (e) => {
    // Nur von der Handle-Zone (obere 48px) oder wenn Panel ganz oben → Swipe erlauben
    const touchY = e.touches[0].clientY;
    const rect = panel.getBoundingClientRect();
    const isHandleZone = touchY - rect.top < 48;
    const isScrolledToTop = (scrollBody ? scrollBody.scrollTop : panel.scrollTop) <= 0;
    if (!isHandleZone && !isScrolledToTop) return;
    startY = touchY;
    dragging = true;
  }, { passive: true });

  panel.addEventListener('touchmove', (e) => {
    if (!dragging) return;
    const dy = e.touches[0].clientY - startY;
    if (dy < 0) { panel.style.transform = 'translateY(0)'; return; } // Aufwärts: Panel zurücksetzen, dragging bleibt aktiv
    // Erst ab 10px Bewegung animieren: Verhindert winzige Transforms durch
    // normale Taps, die danach zurückgesetzt werden müssten.
    if (dy > 10) panel.style.transform = `translateY(${(dy - 10) * 0.6}px)`;
  }, { passive: true });

  panel.addEventListener('touchend', (e) => {
    if (!dragging) return;
    dragging = false;
    const dy = e.changedTouches[0].clientY - startY;
    if (dy > 80) {
      panel.style.transform = '';
      closeModal();
    } else {
      // Transform-Reset per rAF verzögern: DOM-Mutationen direkt in touchend
      // unterbrechen auf iOS WebKit die Touch→Click-Konvertierung – der click-Event
      // auf Child-Elementen (Buttons) wird gecancelt → Buttons reagieren nicht.
      requestAnimationFrame(() => { panel.style.transform = ''; });
    }
  });
}

// --------------------------------------------------------
// _doClose - gemeinsame Cleanup-Logik
// --------------------------------------------------------

function _doClose(overlayEl) {
  const target = overlayEl ?? activeOverlay;
  if (!target) return;

  target.remove();

  // Globalen State nur zurücksetzen wenn kein neues Modal zwischenzeitlich geöffnet wurde.
  // (activeOverlay !== target bedeutet: openModal hat bereits ein neues Modal registriert)
  if (activeOverlay === target) {
    activeOverlay = null;

    // Scroll-Lock aufheben
    document.body.style.overflow = '';

    // Focus-Restore
    if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
      previouslyFocused.focus();
      previouslyFocused = null;
    }

    // Standalone: Statusbar-Farbe zur aktuellen Route wiederherstellen
    if (window.oikos?.restoreThemeColor) {
      window.oikos.restoreThemeColor();
    }
  }
}

// --------------------------------------------------------
// openModal
// --------------------------------------------------------

/**
 * Öffnet ein Modal mit dem Shared-System.
 *
 * @param {Object}   opts
 * @param {string}   opts.title    - Titel im Modal-Header
 * @param {string}   opts.content  - HTML-String für den Modal-Body
 * @param {Function} [opts.onSave]   - Callback, wird nach Einfügen in DOM aufgerufen
 *                                      (zum Binden von Form-Events)
 * @param {Function} [opts.onDelete] - Falls vorhanden, wird ein Löschen-Button eingebaut
 * @param {string}   [opts.size='md'] - 'sm' | 'md' | 'lg'
 */
export function openModal({ title, content, onSave, onDelete, size = 'md' } = {}) {
  // Vorheriges Modal schließen (kein Stacking).
  // ID sofort entfernen damit getElementById() nach dem Einfügen des neuen Modals
  // nicht die noch animierende alte Instanz zurückgibt – sonst landen alle
  // Event-Listener am falschen Element und Buttons reagieren nicht.
  // force=true: kein Dirty-Check beim programmatischen Ersetzen (z.B. confirmModal öffnet sich).
  if (activeOverlay) {
    activeOverlay.removeAttribute('id');
    closeModal({ force: true });
  }

  // Focus-Restore vorbereiten
  previouslyFocused = document.activeElement;

  // Scroll-Lock
  document.body.style.overflow = 'hidden';

  const sizeClass = size !== 'md' ? ` modal-panel--${size}` : '';

  const html = `
    <div class="modal-overlay" id="shared-modal-overlay" aria-label="${t('modal.overlayLabel')}">
      <div class="modal-panel${sizeClass}" role="dialog" aria-modal="true"
           aria-labelledby="shared-modal-title">
        <div class="modal-panel__header">
          <h2 class="modal-panel__title" id="shared-modal-title">${title}</h2>
          <button class="modal-panel__close" data-action="close-modal" aria-label="${t('modal.closeLabel')}">
            <i data-lucide="x" style="width:16px;height:16px" aria-hidden="true"></i>
          </button>
        </div>
        <div class="modal-panel__body">
          ${content}
        </div>
      </div>
    </div>`;

  document.body.insertAdjacentHTML('beforeend', html);
  activeOverlay = document.getElementById('shared-modal-overlay');

  // Lucide-Icons rendern
  if (window.lucide) window.lucide.createIcons();

  // Focus-Trap
  const panel = activeOverlay.querySelector('.modal-panel');
  trapFocus(panel);

  // Snapshot für Dirty-Check (kurzer Delay: Felder könnten noch per JS befüllt werden)
  _initialFormSnapshot = null;
  setTimeout(() => {
    if (activeOverlay) {
      _initialFormSnapshot = serializeForm(activeOverlay.querySelector('.modal-panel') ?? activeOverlay);
    }
  }, 150);

  // Swipe-to-Close auf Mobile
  if (window.innerWidth < 768) {
    _wireSheetSwipe(panel);
  }

  // Overlay-Click schließt Modal
  activeOverlay.addEventListener('click', (e) => {
    if (e.target === activeOverlay) closeModal();
  });

  // iOS PWA: click-Events auf non-interactive divs sind unzuverlässig →
  // touchend als Fallback (passive, damit Scroll nicht blockiert wird)
  activeOverlay.addEventListener('touchend', (e) => {
    if (e.target === activeOverlay) closeModal();
  }, { passive: true });

  // Close-Button
  activeOverlay.querySelector('[data-action="close-modal"]')
    ?.addEventListener('click', () => closeModal());

  // Escape
  document.addEventListener('keydown', onEscape);

  // Callback für Aufrufer (Form-Events binden etc.)
  if (typeof onSave === 'function') onSave(panel);

  // Loading-State: btn--loading auf Submit-Button während async-Save.
  // rAF-Check: Validierung schlägt fehl → btn bleibt enabled → Loading sofort entfernen.
  // MutationObserver: Error-Pfad → btn wird re-enabled → Loading entfernen.
  panel.addEventListener('submit', (e) => {
    const btn = e.target.querySelector('[type="submit"], .btn--primary');
    if (!btn || btn.disabled) return;
    btn.classList.add('btn--loading');
    requestAnimationFrame(() => {
      if (!btn.disabled) { btn.classList.remove('btn--loading'); return; }
      const mo = new MutationObserver(() => {
        if (!btn.disabled) { btn.classList.remove('btn--loading'); mo.disconnect(); }
      });
      mo.observe(btn, { attributes: true, attributeFilter: ['disabled'] });
    });
  }, { capture: true });

  // Standalone: Statusbar abdunkeln (Overlay-Effekt)
  if (window.oikos?.setThemeColor) {
    window.oikos.setThemeColor(OVERLAY_THEME_COLOR, OVERLAY_THEME_COLOR);
  }
}

// --------------------------------------------------------
// closeModal
// --------------------------------------------------------

export async function closeModal({ force = false } = {}) {
  if (!activeOverlay || _isClosing) return;
  _isClosing = true;

  if (!force) {
    const panel = activeOverlay.querySelector('.modal-panel');
    if (panel && isFormDirty(panel)) {
      let confirmed;
      try {
        confirmed = await confirmModal(t('modal.unsavedChanges'), {
          danger: false,
          confirmLabel: t('modal.discardChanges'),
        });
      } catch (err) {
        _isClosing = false;
        throw err;
      }
      if (!confirmed) { _isClosing = false; return; }
    }
  }

  _initialFormSnapshot = null;

  document.removeEventListener('keydown', onEscape);

  // Overlay sofort sichern: Bei Mobile-Animation öffnet openModal() ein neues Modal
  // bevor animationend feuert. Ohne capturedOverlay würde _doClose() das neue Modal
  // statt des alten entfernen (Race Condition → Buttons im Confirm-Dialog reagieren nicht).
  const capturedOverlay = activeOverlay;
  const panel = capturedOverlay.querySelector('.modal-panel');

  // Focus-Trap-Handler und Virtual-Keyboard-Listener entfernen
  if (focusTrapHandler) {
    if (panel) panel.removeEventListener('keydown', focusTrapHandler);
    focusTrapHandler = null;
  }
  if (panel?._onInputFocus) {
    panel.removeEventListener('focusin', panel._onInputFocus);
  }

  // Sheet-Out-Animation auf Mobile, danach _doClose
  const isMobile = window.innerWidth < 768;
  if (isMobile && panel) {
    panel.classList.add('modal-panel--closing');
    // Fallback-Timer falls animationend nicht feuert (prefers-reduced-motion, Tab-Wechsel etc.)
    const fallback = setTimeout(() => { _isClosing = false; _doClose(capturedOverlay); }, 300);
    panel.addEventListener('animationend', () => {
      clearTimeout(fallback);
      _isClosing = false;
      _doClose(capturedOverlay);
    }, { once: true });
    return;
  }

  _isClosing = false;
  _doClose(capturedOverlay);
}

// --------------------------------------------------------
// promptModal - Ersatz für native prompt()
// --------------------------------------------------------

/**
 * Öffnet ein Modal mit Textfeld als Ersatz für native prompt().
 * Gibt ein Promise zurück: string bei OK, null bei Cancel/Escape.
 *
 * @param {string} label   - Beschriftung / Frage
 * @param {string} [defaultValue=''] - Vorausgefüllter Wert
 * @returns {Promise<string|null>}
 */
export function promptModal(label, defaultValue = '') {
  return new Promise((resolve) => {
    let resolved = false;

    function finish(value) {
      if (resolved) return;
      resolved = true;
      closeModal();
      resolve(value);
    }

    openModal({
      title: label,
      size: 'sm',
      content: `
        <form id="prompt-modal-form" class="form-stack">
          <div class="form-field">
            <input class="form-input" id="prompt-modal-input" type="text"
                   value="${defaultValue.replace(/"/g, '&quot;')}" autocomplete="off">
          </div>
          <div class="modal-actions">
            <button type="button" class="btn btn--ghost" id="prompt-modal-cancel">${t('common.cancel')}</button>
            <button type="submit" class="btn btn--primary" id="prompt-modal-ok">${t('common.save')}</button>
          </div>
        </form>`,
      onSave(panel) {
        const form  = panel.querySelector('#prompt-modal-form');
        const input = panel.querySelector('#prompt-modal-input');
        const cancel = panel.querySelector('#prompt-modal-cancel');

        form.addEventListener('submit', (e) => {
          e.preventDefault();
          finish(input.value.trim() || null);
        });

        cancel.addEventListener('click', () => finish(null));

        // Escape soll null liefern (closeModal wird über onEscape bereits ausgelöst)
        const escHandler = (e) => {
          if (e.key === 'Escape') {
            document.removeEventListener('keydown', escHandler);
            finish(null);
          }
        };
        document.addEventListener('keydown', escHandler);

        // Overlay-Click soll null liefern
        const overlay = panel.closest('.modal-overlay');
        if (overlay) {
          overlay.addEventListener('click', (e) => {
            if (e.target === overlay) finish(null);
          });
        }

        // Input fokussieren und Text selektieren
        setTimeout(() => {
          input.focus();
          input.select();
        }, 50);
      },
    });
  });
}

// --------------------------------------------------------
// selectModal - Ersatz für native prompt() mit Auswahlliste
// --------------------------------------------------------

/**
 * Öffnet ein Modal mit Select-Dropdown als Ersatz für native prompt() bei Listenauswahl.
 *
 * @param {string} label - Beschriftung / Frage
 * @param {{ value: string|number, label: string }[]} options - Auswahloptionen
 * @returns {Promise<string|number|null>}
 */
export function selectModal(label, options) {
  return new Promise((resolve) => {
    let resolved = false;

    function finish(value) {
      if (resolved) return;
      resolved = true;
      closeModal();
      resolve(value);
    }

    const optionsHtml = options
      .map((o) => `<option value="${String(o.value).replace(/"/g, '&quot;')}">${o.label}</option>`)
      .join('');

    openModal({
      title: label,
      size: 'sm',
      content: `
        <form id="select-modal-form" class="form-stack">
          <div class="form-field">
            <select class="form-input" id="select-modal-input">${optionsHtml}</select>
          </div>
          <div class="modal-actions">
            <button type="button" class="btn btn--ghost" id="select-modal-cancel">${t('common.cancel')}</button>
            <button type="submit" class="btn btn--primary" id="select-modal-ok">${t('common.save')}</button>
          </div>
        </form>`,
      onSave(panel) {
        const form   = panel.querySelector('#select-modal-form');
        const select = panel.querySelector('#select-modal-input');
        const cancel = panel.querySelector('#select-modal-cancel');

        form.addEventListener('submit', (e) => {
          e.preventDefault();
          finish(select.value);
        });

        cancel.addEventListener('click', () => finish(null));

        const escHandler = (e) => {
          if (e.key === 'Escape') {
            document.removeEventListener('keydown', escHandler);
            finish(null);
          }
        };
        document.addEventListener('keydown', escHandler);

        const overlay = panel.closest('.modal-overlay');
        if (overlay) {
          overlay.addEventListener('click', (e) => {
            if (e.target === overlay) finish(null);
          });
        }
      },
    });
  });
}

// --------------------------------------------------------
// confirmModal - Ersatz für native confirm()
// --------------------------------------------------------

/**
 * Zeigt ein Bestätigungs-Modal als Ersatz für native confirm().
 * Gibt ein Promise zurück: true bei OK, false bei Cancel/Escape/Overlay-Klick.
 *
 * @param {string} message   - Frage / Meldung im Titel
 * @param {Object} [opts]
 * @param {string}  [opts.confirmLabel]   - Text des Bestätigungs-Buttons (default: t('common.confirm'))
 * @param {boolean} [opts.danger=false]   - Roten Danger-Button statt Primary verwenden
 * @returns {Promise<boolean>}
 */
export function confirmModal(message, { confirmLabel, danger = false } = {}) {
  return new Promise((resolve) => {
    let resolved = false;

    function finish(value) {
      if (resolved) return;
      resolved = true;
      closeModal();
      resolve(value);
    }

    openModal({
      title: message,
      size: 'sm',
      content: `
        <div class="modal-actions">
          <button type="button" class="btn btn--ghost" id="confirm-modal-cancel">${t('common.cancel')}</button>
          <button type="button" class="btn ${danger ? 'btn--danger' : 'btn--primary'}" id="confirm-modal-ok">
            ${confirmLabel ?? t('common.confirm')}
          </button>
        </div>`,
      onSave(panel) {
        panel.querySelector('#confirm-modal-ok')?.addEventListener('click', () => finish(true));
        panel.querySelector('#confirm-modal-cancel')?.addEventListener('click', () => finish(false));

        const escHandler = (e) => {
          if (e.key === 'Escape') {
            document.removeEventListener('keydown', escHandler);
            finish(false);
          }
        };
        document.addEventListener('keydown', escHandler);

        const overlay = panel.closest('.modal-overlay');
        if (overlay) {
          overlay.addEventListener('click', (e) => {
            if (e.target === overlay) finish(false);
          });
        }
      },
    });
  });
}

// --------------------------------------------------------
// Inline Blur-Validierung
// --------------------------------------------------------

function _validateField(input) {
  const group = input.closest('.form-field') ?? input.parentElement;
  const hasValue = input.value.trim().length > 0;
  group?.classList.toggle('form-field--error', !hasValue);
  group?.classList.toggle('form-field--valid', hasValue);
  input.setAttribute('aria-invalid', String(!hasValue));
  return hasValue;
}

/**
 * Aktiviert Blur-Validierung für alle required-Inputs in einem Container.
 * @param {HTMLElement} formContainer
 */
export function wireBlurValidation(formContainer) {
  formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => {
    input.addEventListener('blur', () => _validateField(input));
  });
}

/**
 * Validiert alle required-Inputs sofort (z.B. beim Submit ohne vorangehendes Blur).
 * Markiert Fehler inline und fokussiert das erste ungültige Feld.
 *
 * @param {HTMLElement} formContainer
 * @returns {boolean} true wenn alle Felder valide sind
 */
export function validateAll(formContainer) {
  let firstInvalid = null;
  let allValid = true;

  formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => {
    const valid = _validateField(input);
    if (!valid && !firstInvalid) firstInvalid = input;
    if (!valid) allValid = false;
  });

  if (firstInvalid) firstInvalid.focus();
  return allValid;
}

// --------------------------------------------------------
// Submit-Feedback (Checkmark + Shake)
// --------------------------------------------------------

/**
 * Zeigt Erfolgs-Feedback auf einem Button (Checkmark für 700ms).
 * Respektiert prefers-reduced-motion: zeigt nur Farb-Feedback ohne Icon-Wechsel.
 * @param {HTMLButtonElement} btn
 * @param {string} [originalLabel]
 */
export function btnSuccess(btn, originalLabel) {
  btn.classList.remove('btn--loading');
  const label = originalLabel ?? btn.textContent;
  btn.classList.add('btn--success');
  const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (!reducedMotion) {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '16');
    svg.setAttribute('height', '16');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'currentColor');
    svg.setAttribute('stroke-width', '2.5');
    svg.setAttribute('aria-hidden', 'true');
    const poly = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
    poly.setAttribute('points', '20 6 9 17 4 12');
    svg.appendChild(poly);
    btn.replaceChildren(svg);
  }
  setTimeout(() => {
    btn.classList.remove('btn--success');
    btn.textContent = label;
  }, 700);
}

/**
 * Versetzt einen Button in den Lade-Zustand (Spinner, nicht klickbar).
 * Gibt eine Cleanup-Funktion zurück, die den Originalzustand wiederherstellt.
 *
 * @param {HTMLButtonElement} btn
 * @returns {() => void} cleanup
 */
export function btnLoading(btn) {
  btn.classList.add('btn--loading');
  btn.disabled = true;
  return () => {
    btn.classList.remove('btn--loading');
    btn.disabled = false;
  };
}

/**
 * Zeigt Fehler-Feedback auf einem Button (Shake-Animation).
 * Respektiert prefers-reduced-motion: kein visuelles Schütteln, nur Farb-Feedback.
 * @param {HTMLButtonElement} btn
 */
export function btnError(btn) {
  if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
    btn.classList.add('btn--error-static');
    setTimeout(() => btn.classList.remove('btn--error-static'), 700);
    return;
  }
  btn.classList.remove('btn--shaking');
  void btn.offsetWidth; // Reflow für Animation-Restart
  btn.classList.add('btn--shaking');
  btn.addEventListener('animationend', () => btn.classList.remove('btn--shaking'), { once: true });
}
