import './bootstrap';

import focus from '@alpinejs/focus';
import intersect from '@alpinejs/intersect';
import collapse from '@alpinejs/collapse';
import { createIcons, Rocket, Mail, Users, Send, GitBranch, Briefcase, Brain, CreditCard, Plug, Wrench } from 'lucide';
import registerAutosave from './autosave';
import Quill from 'quill';
import 'quill/dist/quill.snow.css';

// Register Alpine plugins and custom components via Livewire's Alpine instance.
// Livewire 4 bundles Alpine internally — importing it separately causes
// "Detected multiple instances of Alpine running" console warnings.
document.addEventListener('livewire:init', () => {
    const Alpine = window.Alpine;

    // Register plugins
    Alpine.plugin(focus);
    Alpine.plugin(intersect);
    Alpine.plugin(collapse);

    // Register custom data components
    registerAutosave(Alpine);

    // Quill rich text editor component
    Alpine.data('quillEditor', (wireModelName = 'body') => ({
        quill: null,
        init() {
            const Font = Quill.import('attributors/class/font');
            Font.whitelist = ['roboto', 'serif', 'monospace', 'sans-serif'];
            Quill.register(Font, true);

            this.quill = new Quill(this.$refs.editor, {
                theme: 'snow',
                placeholder: this.$refs.editor.dataset.placeholder || 'Type here...',
                modules: {
                    toolbar: this.$refs.toolbar,
                },
            });

            // Set initial content from Livewire
            const initial = this.$wire.get(wireModelName);
            if (initial) {
                this.quill.root.innerHTML = initial;
            }

            // Sync editor content to Livewire on every change. MUST be live
            // (not deferred) — the ComposeEmail.updatedBody() lifecycle hook
            // watches $this->body to open the canned-response dropdown when
            // the user types "/", and that hook only fires on actual server
            // commits.
            this.quill.on('text-change', () => {
                const html = this.quill.root.innerHTML;
                this.$wire.set(wireModelName, html === '<p><br></p>' ? '' : html);
            });

            // Flush hook for the Send button.
            //
            // The race we're guarding against: user types, Quill's text-change
            // queues a $wire.set commit. Before that commit lands, user clicks
            // Send. Send's commit ALSO queues — and if Livewire processes Send
            // ahead of the body set, server-side validation rejects "body
            // required" and the send silently dies (matches the customer's
            // "Log::info('ComposeEmail: sending') never appears" report).
            //
            // The flush is synchronous on the JS side — it writes the editor's
            // current HTML into the Livewire component's PENDING state before
            // sendMessage queues. Any subsequent commit batches the set with
            // sendMessage, so the body is guaranteed present server-side.
            this.$el.addEventListener('mb-quill-flush', () => {
                if (!this.quill) return;
                const html = this.quill.root.innerHTML;
                this.$wire.set(wireModelName, html === '<p><br></p>' ? '' : html, false);
            });

            // Listen for external content updates (e.g. AI suggestions)
            this.$watch('$wire.' + wireModelName, (val) => {
                if (val !== this.quill.root.innerHTML && val !== undefined) {
                    this.quill.root.innerHTML = val || '';
                }
            });
        },
        getContent() { return this.quill?.root.innerHTML || ''; },
        setContent(html) { if (this.quill) this.quill.root.innerHTML = html; },
        clear() { if (this.quill) this.quill.setText(''); },
    }));
});

// ─────────────────────────────────────────────────────────────────
// TEMPLATE-PICKER → QUILL BRIDGE
// ─────────────────────────────────────────────────────────────────
//
// The TemplatePicker (App\Livewire\Templates\TemplatePicker::pick())
// dispatches a `template-inserted` Livewire event carrying the rendered
// HTML body. The parent composer (ComposeEmail / ReplyComposer) has a
// #[On('template-inserted')] handler that updates $body server-side,
// and the $watch('$wire.body', ...) above propagates that into Quill.
//
// That chain works on paper but customers reported the editor staying
// blank after picking a template. To make this bulletproof we wire
// THREE listeners — any one of them on its own would fix the bug, but
// having all three means a Livewire version quirk, a payload shape
// change, or a re-render race can't all break at once:
//
//   1. window event listener on `template-inserted`
//   2. Livewire.on('template-inserted', ...) once Livewire boots
//   3. (server-side) the parent's #[On] handler keeps $body authoritative
//
// All three call the same updateQuill() helper, which is idempotent —
// firing twice in the same tick just sets innerHTML to the same value
// twice (no perceived flicker).

/**
 * Extract `html` from any of the payload shapes Livewire 3 may use
 * (object, array-of-object, positional array).
 */
function _extractTemplateHtml(detail) {
    if (!detail) return '';
    // Most common shape: object with named keys
    if (typeof detail === 'object' && !Array.isArray(detail) && 'html' in detail) {
        return detail.html || '';
    }
    // Array shape: usually [{ html, subject, templateId }]
    if (Array.isArray(detail)) {
        if (detail[0] && typeof detail[0] === 'object' && 'html' in detail[0]) {
            return detail[0].html || '';
        }
        // Positional array — html is the first arg
        if (typeof detail[0] === 'string') return detail[0];
    }
    return '';
}

/**
 * Find the currently visible Quill editor instance(s) and replace
 * their content with `html`. Also pushes the value back into the
 * Livewire model so the next Send carries the new body.
 *
 * IMPORTANT: we use quill.clipboard.dangerouslyPasteHTML() instead of
 * the obvious `quill.root.innerHTML = html`. Quill keeps an internal
 * Delta model separate from the rendered DOM — setting innerHTML
 * updates what the user SEES but leaves the model empty. The next
 * keystroke / re-render syncs the DOM from the (empty) model and
 * silently wipes the pasted template. The .dangerouslyPasteHTML API
 * updates both the Delta and the DOM atomically, fires text-change
 * once, and survives subsequent re-renders. Behaviour confirmed
 * against Quill 1.x; for Quill 2.x we fall back to the
 * convert+setContents path.
 */
function _updateVisibleQuill(html, source) {
    console.log('[template-bridge]', source, '→ html length:', html.length);

    const wrappers = document.querySelectorAll('.quill-wrapper');
    if (!wrappers.length) {
        console.warn('[template-bridge] no .quill-wrapper found in DOM');
        return;
    }

    let updated = 0;
    wrappers.forEach((wrapper, idx) => {
        // offsetParent === null on display:none / detached elements.
        // Both compose-email and reply-composer are siblings in the
        // inbox DOM, so only the visible composer gets the update.
        if (wrapper.offsetParent === null) {
            console.log('[template-bridge] wrapper', idx, 'skipped (not visible)');
            return;
        }
        try {
            const alpineData = Alpine.$data(wrapper);
            if (!alpineData) {
                console.warn('[template-bridge] wrapper', idx, 'has no Alpine data');
                return;
            }
            const quill = alpineData.quill;
            if (!quill) {
                console.warn('[template-bridge] wrapper', idx, 'has no quill instance');
                return;
            }

            // Update Delta + DOM atomically. dangerouslyPasteHTML is
            // the Quill 1.x API; if we ever upgrade to Quill 2.x the
            // convert+setContents branch picks up automatically.
            if (typeof quill.clipboard?.dangerouslyPasteHTML === 'function') {
                quill.clipboard.dangerouslyPasteHTML(html, 'silent');
                console.log('[template-bridge] wrapper', idx, 'used dangerouslyPasteHTML');
            } else if (typeof quill.clipboard?.convert === 'function') {
                // Quill 2.x path
                const delta = quill.clipboard.convert({ html });
                quill.setContents(delta, 'silent');
                console.log('[template-bridge] wrapper', idx, 'used convert+setContents');
            } else {
                // Last-resort fallback. Same behaviour as the previous
                // implementation — visible immediately but loses
                // content on the next keystroke. Warn loudly so we
                // know if a build ever lands without these APIs.
                quill.root.innerHTML = html;
                console.warn('[template-bridge] wrapper', idx, 'FALLBACK: set innerHTML directly — content may be wiped by next keystroke');
            }

            // Sync back to Livewire's $body so the next Send carries
            // the template. Using `false` as the third arg defers the
            // commit (batches with the user's next action).
            if (alpineData.$wire) {
                alpineData.$wire.set('body', html, false);
            }
            updated++;
            console.log('[template-bridge] wrapper', idx, 'updated successfully');
        } catch (err) {
            console.error('[template-bridge] wrapper', idx, 'update failed', err);
        }
    });

    if (!updated) {
        console.warn('[template-bridge] no editors were updated — none visible?');
    }
}

// Listener 1 — browser CustomEvent on window. Livewire 3 dispatches
// arrive here when the picker calls $this->dispatch('template-inserted').
window.addEventListener('template-inserted', (e) => {
    const html = _extractTemplateHtml(e.detail);
    if (!html) {
        console.warn('[template-bridge] window event fired but extracted html is empty', e.detail);
        return;
    }
    _updateVisibleQuill(html, 'window-event');
});

// Listener 2 — Livewire's own pubsub. Catches the same event but
// through Livewire's internal channel, surviving any version quirks
// in how browser CustomEvents are named/scoped.
document.addEventListener('livewire:initialized', () => {
    if (typeof Livewire === 'undefined') {
        console.warn('[template-bridge] Livewire not on window at livewire:initialized — skipping pubsub listener');
        return;
    }
    Livewire.on('template-inserted', (payload) => {
        const html = _extractTemplateHtml(payload);
        if (!html) {
            console.warn('[template-bridge] Livewire.on payload had no html', payload);
            return;
        }
        _updateVisibleQuill(html, 'livewire-on');
    });
    console.log('[template-bridge] Livewire.on(template-inserted) wired');
});

// Initialize Lucide icons on first load
document.addEventListener('DOMContentLoaded', () => {
    createIcons({ icons: { Rocket, Mail, Users, Send, GitBranch, Briefcase, Brain, CreditCard, Plug, Wrench } });
});

// Re-initialize Lucide icons after Livewire SPA navigations
document.addEventListener('livewire:navigated', () => {
    createIcons({ icons: { Rocket, Mail, Users, Send, GitBranch, Briefcase, Brain, CreditCard, Plug, Wrench } });
});
