/**
 * SSHIA Website — Main JavaScript Entry Point
 *
 * Initialises Alpine.js with plugins and registers all global
 * interactive components for the site layout system.
 */

import Alpine from 'alpinejs';
import mask    from '@alpinejs/mask';
import focus   from '@alpinejs/focus';
import intersect from '@alpinejs/intersect';

// FontAwesome
import '@fortawesome/fontawesome-free/css/all.min.css';

// Flowbite
import 'flowbite';

// Chart.js — register globally so all blade @push('scripts') components can use it
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
window.Chart = Chart;

// ── Alpine plugins ────────────────────────────────────────────────────────────
Alpine.plugin(mask);
Alpine.plugin(focus);
Alpine.plugin(intersect);

// ── Global store ─────────────────────────────────────────────────────────────
/**
 * siteLayout store
 * Manages mobile-menu open/close state and scroll tracking.
 */
Alpine.store('siteLayout', {
    mobileMenuOpen: false,
    scrolled: false,

    init() {
        window.addEventListener('scroll', () => {
            this.scrolled = window.scrollY > 50;
        }, { passive: true });
    },

    toggleMobileMenu() {
        this.mobileMenuOpen = !this.mobileMenuOpen;
        document.body.style.overflow = this.mobileMenuOpen ? 'hidden' : '';
    },

    closeMobileMenu() {
        this.mobileMenuOpen = false;
        document.body.style.overflow = '';
    },
});

// ── siteHeader component ──────────────────────────────────────────────────────
/**
 * Manages:
 *  - Topbar notification ticker (rotation, pause on hover)
 *  - Desktop mega-menu open/close (click-based, NOT hover)
 *  - Mobile accordion sections
 */
Alpine.data('siteHeader', () => ({
    // Notification ticker
    notifications: [],
    currentNotificationIndex: 0,
    notificationTimer: null,
    notificationPaused: false,

    // Mega-menu — null means all closed
    megaMenuOpen: null,

    // Mobile accordion
    mobileExpanded: {},

    init() {
        // ── Parse notifications from data attribute ──────────────────
        const raw = this.$el.dataset.notifications;
        if (raw) {
            try {
                this.notifications = JSON.parse(raw);
                if (this.notifications.length > 1) {
                    this.startNotificationRotation();
                }
            } catch (e) {
                console.warn('siteHeader: failed to parse notifications', e);
            }
        }

        // ── Escape key closes everything ─────────────────────────────
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                this.closeMegaMenu();
                Alpine.store('siteLayout').closeMobileMenu();
            }
        });

        // ── Outside-click: close mega-menu ───────────────────────────
        // Capture phase so we catch the event before any child handler
        // can stop propagation.
        //
        // A click is "inside" if it lands on:
        //   (a) anywhere within <header> (this.$el), OR
        //   (b) anywhere within a [data-mega-panel] element
        //       (panels live outside <header> in the DOM but are still
        //        part of the logical navigation).
        //
        // Any other click is "outside" → close.
        document.addEventListener('click', (e) => {
            if (this.megaMenuOpen === null) return; // nothing open — skip

            // (a) click inside the header itself
            if (this.$el.contains(e.target)) return;

            // (b) click inside an open mega-menu panel
            const panels = document.querySelectorAll('[data-mega-panel]');
            for (const panel of panels) {
                if (panel.contains(e.target)) return;
            }

            // Outside — close
            this.closeMegaMenu();
        }, true); // capture phase
    },

    // ── Notification ticker ──────────────────────────────────────────
    startNotificationRotation() {
        this.notificationTimer = setInterval(() => {
            if (!this.notificationPaused) {
                this.currentNotificationIndex =
                    (this.currentNotificationIndex + 1) % this.notifications.length;
            }
        }, 4000);
    },

    pauseNotificationRotation()  { this.notificationPaused = true;  },
    resumeNotificationRotation() { this.notificationPaused = false; },

    getCurrentNotification() {
        return this.notifications[this.currentNotificationIndex] ?? null;
    },

    truncateText(text, wordLimit = 30) {
        if (!text) return '';
        const words = text.split(' ');
        return words.length <= wordLimit
            ? text
            : words.slice(0, wordLimit).join(' ') + '…';
    },

    // ── Mega-menu (click-based) ───────────────────────────────────────
    /**
     * Toggle a mega-menu panel by key.
     * Clicking the same trigger again closes it.
     * Clicking a different trigger switches to that one.
     */
    toggleMegaMenu(key) {
        this.megaMenuOpen = this.megaMenuOpen === key ? null : key;
    },

    closeMegaMenu() {
        this.megaMenuOpen = null;
    },

    /**
     * Returns the bottom edge (px from viewport top) of the [data-navbar] bar.
     * Mega-menu panels use this as their `top` so they always open flush
     * against the navbar bottom edge — whether the page is scrolled or not.
     *
     * Queried from document (not this.$el) because the panels are rendered
     * outside <header> in the DOM.
     */
    getNavbarBottom() {
        const navbar = document.querySelector('[data-navbar]');
        if (!navbar) return 56; // safe fallback
        return Math.round(navbar.getBoundingClientRect().bottom);
    },

    // ── Mobile accordion ─────────────────────────────────────────────
    toggleMobileSection(key) {
        this.mobileExpanded[key] = !this.mobileExpanded[key];
    },

    isMobileSectionExpanded(key) {
        return this.mobileExpanded[key] ?? false;
    },
}));

// ── newsletterForm component ──────────────────────────────────────────────────
/**
 * Handles the email subscription form in the middlebar.
 * POSTs to /subscribe with CSRF token.
 */
Alpine.data('newsletterForm', () => ({
    email:   '',
    loading: false,
    success: false,
    error:   '',

    async submit() {
        if (!this.isValidEmail(this.email)) {
            this.error = 'Please enter a valid email address.';
            return;
        }

        this.loading = true;
        this.error   = '';
        this.success = false;

        try {
            const response = await fetch('/subscribe', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
                },
                body: JSON.stringify({ email: this.email }),
            });

            if (!response.ok) {
                const data = await response.json().catch(() => ({}));
                throw new Error(data.message || 'Subscription failed. Please try again.');
            }

            this.success = true;
            this.email   = '';

            setTimeout(() => { this.success = false; }, 6000);
        } catch (err) {
            this.error = err.message || 'Failed to subscribe. Please try again.';
        } finally {
            this.loading = false;
        }
    },

    isValidEmail(email) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    },
}));

// ── chatWidget component ──────────────────────────────────────────────────────
/**
 * Floating chat support widget — fully self-contained modal.
 *
 * States:
 *   'intro'    — name / message intake form (before session starts)
 *   'chatting' — live session with polling for new messages
 *   'ended'    — conversation closed
 *
 * The widget never navigates away from the current page.
 * All communication is via JSON AJAX calls to /chat/api/*.
 *
 * z-index: 9999 — sits above sticky header and all page content.
 */
Alpine.data('chatWidget', () => ({
    // ── visibility ──────────────────────────────────────────────────
    open:       false,   // panel visible
    minimised:  false,   // panel collapsed to header bar only

    // ── state machine ───────────────────────────────────────────────
    // 'intro' | 'chatting' | 'ended'
    phase: 'intro',

    // ── intro form ──────────────────────────────────────────────────
    visitorName:    '',
    visitorEmail:   '',
    initialMessage: '',
    starting:       false,
    startError:     '',

    // ── chat session ────────────────────────────────────────────────
    sessionId:   null,
    messages:    [],      // all messages (initial + polled)
    lastId:      0,
    chatStatus:  'waiting',
    agentName:   null,
    pollTimer:   null,

    // ── send form ───────────────────────────────────────────────────
    outgoing:    '',
    sending:     false,

    // ── computed ────────────────────────────────────────────────────
    get isOpen()      { return this.open && !this.minimised; },
    get statusText() {
        if (this.agentName)              return this.agentName + ' is helping you';
        if (this.chatStatus === 'waiting') return 'Waiting for an agent…';
        if (this.chatStatus === 'active')  return 'Connected';
        return 'Session ended';
    },
    get statusDot() {
        return { active: 'bg-green-400', waiting: 'bg-yellow-400 animate-pulse',
                 resolved: 'bg-blue-400', closed: 'bg-gray-400', missed: 'bg-red-400' }
               [this.chatStatus] ?? 'bg-gray-400';
    },
    get canSend() {
        return (this.chatStatus === 'active' || this.chatStatus === 'waiting')
               && this.outgoing.trim().length > 0
               && !this.sending;
    },

    // ── lifecycle ───────────────────────────────────────────────────
    init() {
        // Restore an in-progress session from the cookie (set server-side)
        // by checking localStorage for a cached session_id
        const saved = this._loadSession();
        if (saved) {
            this.sessionId  = saved.sessionId;
            this.messages   = saved.messages  ?? [];
            this.lastId     = saved.lastId    ?? 0;
            this.chatStatus = saved.status    ?? 'waiting';
            this.agentName  = saved.agentName ?? null;
            this.phase      = (this.chatStatus === 'active' || this.chatStatus === 'waiting')
                              ? 'chatting' : 'ended';
            if (this.phase === 'chatting') this._startPolling();
        }
    },

    // ── panel controls ──────────────────────────────────────────────
    toggle() {
        if (this.open && !this.minimised) {
            this.minimised = true;
        } else {
            this.open      = true;
            this.minimised = false;
            this.$nextTick(() => this._scrollBottom());
        }
    },

    maximise() {
        this.minimised = false;
        this.$nextTick(() => this._scrollBottom());
    },

    close() {
        this.open      = false;
        this.minimised = false;
    },

    // ── start chat ──────────────────────────────────────────────────
    async startChat() {
        this.startError = '';
        if (!this.initialMessage.trim()) {
            this.startError = 'Please enter a message to start the chat.';
            return;
        }
        this.starting = true;
        try {
            const res = await fetch('/chat/api/start', {
                method:  'POST',
                headers: {
                    'Content-Type':     'application/json',
                    'Accept':           'application/json',
                    'X-CSRF-TOKEN':     document.querySelector('meta[name="csrf-token"]')?.content ?? '',
                    'X-Requested-With': 'XMLHttpRequest',
                },
                body: JSON.stringify({
                    visitor_name:    this.visitorName.trim()    || null,
                    visitor_email:   this.visitorEmail.trim()   || null,
                    initial_message: this.initialMessage.trim(),
                }),
            });
            const data = await res.json();
            if (!res.ok || !data.success) throw new Error(data.message ?? 'Failed to start chat.');

            this.sessionId  = data.session_id;
            this.messages   = data.messages ?? [];
            this.lastId     = this.messages.length ? this.messages[this.messages.length - 1].id : 0;
            this.chatStatus = data.status;
            this.agentName  = data.agent ?? null;
            this.phase      = 'chatting';
            this._saveSession();
            this._startPolling();
            this.$nextTick(() => this._scrollBottom());
        } catch (e) {
            this.startError = e.message || 'Something went wrong. Please try again.';
        } finally {
            this.starting = false;
        }
    },

    // ── send message ────────────────────────────────────────────────
    async sendMessage() {
        if (!this.canSend) return;
        const text = this.outgoing.trim();
        this.outgoing = '';
        this.sending  = true;

        // Optimistic UI — add immediately
        const optimistic = {
            id: Date.now(), sender_type: 'visitor', sender_name: 'You',
            message: text, time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
            is_system: false, _pending: true,
        };
        this.messages.push(optimistic);
        this.$nextTick(() => this._scrollBottom());

        try {
            const res = await fetch(`/chat/api/session/${this.sessionId}/message`, {
                method:  'POST',
                headers: {
                    'Content-Type':     'application/json',
                    'Accept':           'application/json',
                    'X-CSRF-TOKEN':     document.querySelector('meta[name="csrf-token"]')?.content ?? '',
                    'X-Requested-With': 'XMLHttpRequest',
                },
                body: JSON.stringify({ message: text }),
            });
            const data = await res.json();
            if (data.success && data.message) {
                // Replace optimistic with real message
                const idx = this.messages.findIndex(m => m._pending && m.message === text);
                if (idx !== -1) this.messages.splice(idx, 1, data.message);
                this.lastId = data.message.id;
                this._saveSession();
            }
        } catch (e) {
            // Keep optimistic message — it will be confirmed on next poll
        } finally {
            this.sending = false;
        }
    },

    // ── end chat ────────────────────────────────────────────────────
    async endChat() {
        if (!confirm('End this chat session?')) return;
        this._stopPolling();
        try {
            await fetch(`/chat/api/session/${this.sessionId}/end`, {
                method:  'POST',
                headers: {
                    'Content-Type':     'application/json',
                    'Accept':           'application/json',
                    'X-CSRF-TOKEN':     document.querySelector('meta[name="csrf-token"]')?.content ?? '',
                    'X-Requested-With': 'XMLHttpRequest',
                },
            });
        } catch (e) { /* ignore */ }
        this.chatStatus = 'closed';
        this.phase      = 'ended';
        this._clearSession();
    },

    // ── polling ─────────────────────────────────────────────────────
    _startPolling() {
        this._stopPolling();
        this.pollTimer = setInterval(() => this._poll(), 3000);
    },

    _stopPolling() {
        if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
    },

    async _poll() {
        if (!this.sessionId) return;
        try {
            const res = await fetch(`/chat/session/${this.sessionId}/poll?since=${this.lastId}`, {
                headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
            });
            if (!res.ok) return;
            const data = await res.json();

            if (data.messages?.length) {
                // Remove any pending optimistic messages that are now confirmed
                data.messages.forEach(m => {
                    const pendingIdx = this.messages.findIndex(p => p._pending && p.message === m.message && m.sender_type === 'visitor');
                    if (pendingIdx !== -1) this.messages.splice(pendingIdx, 1);
                    // Avoid duplicates
                    if (!this.messages.find(existing => existing.id === m.id)) {
                        this.messages.push(m);
                    }
                });
                this.lastId = data.messages[data.messages.length - 1].id;
                this._saveSession();
                if (!this.minimised) this.$nextTick(() => this._scrollBottom());
            }

            if (data.agent)  this.agentName  = data.agent;
            if (data.status) this.chatStatus = data.status;

            if (data.status !== 'active' && data.status !== 'waiting') {
                this._stopPolling();
                this.phase = 'ended';
                this._clearSession();
            }
        } catch (e) { /* retry next tick */ }
    },

    // ── session persistence (localStorage) ──────────────────────────
    _saveSession() {
        try {
            localStorage.setItem('sshia_chat', JSON.stringify({
                sessionId: this.sessionId,
                messages:  this.messages.filter(m => !m._pending),
                lastId:    this.lastId,
                status:    this.chatStatus,
                agentName: this.agentName,
            }));
        } catch (e) {}
    },

    _loadSession() {
        try {
            const raw = localStorage.getItem('sshia_chat');
            if (!raw) return null;
            const s = JSON.parse(raw);
            if (!s?.sessionId) return null;
            return s;
        } catch (e) { return null; }
    },

    _clearSession() {
        try { localStorage.removeItem('sshia_chat'); } catch (e) {}
    },

    // ── helpers ─────────────────────────────────────────────────────
    _scrollBottom() {
        const el = document.getElementById('cw-messages');
        if (el) el.scrollTop = el.scrollHeight;
    },

    handleEnter(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            this.sendMessage();
        }
    },
}));

// ── scrollToTop component ─────────────────────────────────────────────────────
Alpine.data('scrollToTop', () => ({
    visible: false,

    init() {
        window.addEventListener('scroll', () => {
            this.visible = window.scrollY > 300;
        }, { passive: true });
    },

    scrollToTop() {
        window.scrollTo({ top: 0, behavior: 'smooth' });
    },
}));

// ── Lazy image loading ────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
    if (!('IntersectionObserver' in window)) return;

    const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                const img = entry.target;
                if (img.dataset.src) {
                    img.src = img.dataset.src;
                    img.removeAttribute('data-src');
                }
                observer.unobserve(img);
            }
        });
    });

    document.querySelectorAll('img[loading="lazy"]').forEach((img) => observer.observe(img));
});

// ── appLayout store (shared sidebar/dark-mode state) ─────────────────────────
/**
 * appLayout store
 * Single source of truth for sidebar open/close, mobile detection, dark mode.
 * Used by: appLayoutData (root div), sidebarProfileData (aside), side-header.
 * Using a store ensures all components react to the same state without
 * needing $root hacks that break across component boundaries in Alpine v3.
 */
Alpine.store('appLayout', {
    sidebarOpen: true,
    isMobile: false,
    darkMode: false,

    init() {
        // Restore persisted sidebar state
        try {
            const s = localStorage.getItem('sshia_sidebar_open');
            if (s !== null) this.sidebarOpen = s === 'true';
        } catch (e) {}

        // Restore persisted dark mode
        try {
            const dm = localStorage.getItem('darkMode') ?? localStorage.getItem('sshia_dark_mode');
            if (dm !== null) this.darkMode = dm === 'true';
        } catch (e) {}

        // Initial mobile check
        this.checkMobile();

        // Debounced resize listener
        let t;
        window.addEventListener('resize', () => {
            clearTimeout(t);
            t = setTimeout(() => this.checkMobile(), 100);
        }, { passive: true });
    },

    checkMobile() {
        const mobile = window.innerWidth < 768;
        if (mobile !== this.isMobile) {
            this.isMobile = mobile;
            if (mobile) {
                this.sidebarOpen = false;
            } else {
                try {
                    const s = localStorage.getItem('sshia_sidebar_open');
                    this.sidebarOpen = s !== null ? s === 'true' : true;
                } catch (e) {
                    this.sidebarOpen = true;
                }
            }
        }
    },

    toggleSidebar() {
        this.sidebarOpen = !this.sidebarOpen;
        if (!this.isMobile) {
            try {
                localStorage.setItem('sshia_sidebar_open', String(this.sidebarOpen));
            } catch (e) {}
        }
    },

    toggleDarkMode() {
        this.darkMode = !this.darkMode;
        try {
            localStorage.setItem('darkMode', String(this.darkMode));
            localStorage.setItem('sshia_dark_mode', String(this.darkMode));
        } catch (e) {}
        document.documentElement.classList.toggle('dark', this.darkMode);
    },
});

// ── appLayoutData component ───────────────────────────────────────────────────
/**
 * Root Alpine scope for the authenticated app layout wrapper div.
 * Delegates all state to the appLayout store.
 */
Alpine.data('appLayoutData', () => ({
    get sidebarOpen() { return Alpine.store('appLayout').sidebarOpen; },
    get isMobile()    { return Alpine.store('appLayout').isMobile; },
    get darkMode()    { return Alpine.store('appLayout').darkMode; },

    init() {
        Alpine.store('appLayout').init();
    },

    toggleSidebar() {
        Alpine.store('appLayout').toggleSidebar();
    },
}));

// ── sidebarProfileData component ──────────────────────────────────────────────
/**
 * Alpine scope for the sidebar component (sidebar.blade.php).
 * Reads sidebarOpen / isMobile from the appLayout store.
 * Manages per-group collapse state via localStorage.
 */
Alpine.data('sidebarProfileData', () => ({
    groups: {},

    get sidebarOpen() { return Alpine.store('appLayout').sidebarOpen; },
    get isMobile()    { return Alpine.store('appLayout').isMobile; },

    toggleSidebar() {
        Alpine.store('appLayout').toggleSidebar();
    },

    init(activeGroupKey = null) {
        try {
            const stored = localStorage.getItem('sshia_sidebar_groups');
            if (stored) this.groups = JSON.parse(stored);
        } catch (e) {}

        if (activeGroupKey !== null) this.forceGroupOpen(activeGroupKey);
    },

    isGroupOpen(key) {
        return this.groups[key] ?? true;
    },

    toggleGroup(key) {
        this.groups[key] = !this.isGroupOpen(key);
        try {
            localStorage.setItem('sshia_sidebar_groups', JSON.stringify(this.groups));
        } catch (e) {}
    },

    forceGroupOpen(key) {
        this.groups[key] = true;
    },
}));

// ── Bootstrap Alpine ─────────────────────────────────────────────────────────
window.Alpine = Alpine;
Alpine.start();
