import './bootstrap';

import Alpine from 'alpinejs';
import { mountVueApps } from './vue/bootstrap';

window.Alpine = Alpine;

Alpine.start();

const SIDE_PANEL_ENTITY_PATTERN = /^\/(tasks|contacts|deals)\/\d+\/?$/;

const sidepanelIsEntityUrl = (url) => SIDE_PANEL_ENTITY_PATTERN.test(url.pathname);

const sidepanelNormalizeUrl = (href) => {
    try {
        return new URL(href, window.location.origin);
    } catch {
        return null;
    }
};

const setupHorizontalScrollFade = (scope = document) => {
    const root = scope instanceof Element || scope instanceof Document ? scope : document;
    const wrappers = root.querySelectorAll('[data-board-scroll-wrap]:not([data-scroll-fade-bound="1"])');

    wrappers.forEach((wrapper) => {
        const scrollNode = wrapper.querySelector('[data-board-scroll]');
        const rightFade = wrapper.querySelector('[data-board-scroll-fade-right]');

        if (!scrollNode || !rightFade) {
            return;
        }

        wrapper.dataset.scrollFadeBound = '1';

        const update = () => {
            const maxScrollLeft = scrollNode.scrollWidth - scrollNode.clientWidth;
            const canScroll = maxScrollLeft > 2;
            const remainingRight = maxScrollLeft - scrollNode.scrollLeft;
            const showRightFade = canScroll && remainingRight > 2;

            rightFade.classList.toggle('opacity-100', showRightFade);
            rightFade.classList.toggle('opacity-0', !showRightFade);
        };

        scrollNode.addEventListener('scroll', update, { passive: true });
        window.addEventListener('resize', update);

        if (window.ResizeObserver) {
            const observer = new ResizeObserver(update);
            observer.observe(scrollNode);
            const gridNode = scrollNode.querySelector('[data-board-grid]');
            if (gridNode) {
                observer.observe(gridNode);
            }
        }

        update();
    });
};

const setupTaskLiveForms = (scope = document) => {
    const root = scope instanceof Element || scope instanceof Document ? scope : document;
    const forms = root.querySelectorAll('[data-task-live-form]:not([data-live-bound="1"])');
    const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';

    forms.forEach((form) => {
        const updateUrl = form.dataset.updateUrl;
        if (!updateUrl) {
            return;
        }

        form.dataset.liveBound = '1';

        const statusNode = form.querySelector('[data-task-live-status]');
        const errorNode = form.querySelector('[data-task-live-error]');
        const taskId = form.dataset.taskId
            || (updateUrl.match(/\/tasks\/(\d+)/)?.[1] ?? '');
        const viewRoot = form.closest('[data-task-live-root]') ?? document;

        let projectStageMap = {};
        try {
            const parsedMap = JSON.parse(form.dataset.projectStageMap ?? '{}');
            if (parsedMap && typeof parsedMap === 'object') {
                projectStageMap = parsedMap;
            }
        } catch {
            projectStageMap = {};
        }

        const projectSelect = form.querySelector('select[name="project_id"]');
        const stageSelect = form.querySelector('select[name="project_stage_id"]');

        const setStatus = (text, tone = 'muted') => {
            if (!statusNode) {
                return;
            }

            statusNode.textContent = text;
            statusNode.classList.remove('text-slate-600', 'text-emerald-600', 'text-amber-600', 'text-red-600');

            if (tone === 'saved') {
                statusNode.classList.add('text-emerald-600');
                return;
            }

            if (tone === 'saving') {
                statusNode.classList.add('text-amber-600');
                return;
            }

            if (tone === 'error') {
                statusNode.classList.add('text-red-600');
                return;
            }

            statusNode.classList.add('text-slate-600');
        };

        const setError = (message = '') => {
            if (!errorNode) {
                return;
            }

            errorNode.textContent = message;
            errorNode.classList.toggle('hidden', message.trim() === '');
        };

        const renderProjectStages = (preferCurrent = true) => {
            if (!(projectSelect instanceof HTMLSelectElement) || !(stageSelect instanceof HTMLSelectElement)) {
                return;
            }

            const projectId = String(projectSelect.value || '');
            const stages = Array.isArray(projectStageMap[projectId]) ? projectStageMap[projectId] : [];
            const selectedStageId = preferCurrent ? String(stageSelect.value || '') : '';

            stageSelect.innerHTML = '';

            const emptyOption = document.createElement('option');
            emptyOption.value = '';
            emptyOption.textContent = 'No stage';
            stageSelect.appendChild(emptyOption);

            stages.forEach((stage) => {
                const option = document.createElement('option');
                option.value = String(stage?.id ?? '');
                option.textContent = String(stage?.name ?? '');
                if (option.value === selectedStageId) {
                    option.selected = true;
                }
                stageSelect.appendChild(option);
            });
        };

        const updateMetaLabels = () => {
            const titleInput = form.querySelector('[name="title"]');
            const statusInput = form.querySelector('[name="status"]');
            const priorityInput = form.querySelector('[name="priority"]');

            const title = titleInput instanceof HTMLInputElement
                ? titleInput.value.trim()
                : '';
            const statusText = statusInput instanceof HTMLSelectElement
                ? statusInput.options[statusInput.selectedIndex]?.textContent?.trim() ?? ''
                : '';
            const priorityText = priorityInput instanceof HTMLSelectElement
                ? priorityInput.options[priorityInput.selectedIndex]?.textContent?.trim() ?? ''
                : '';

            const titleText = [taskId ? `#${taskId}` : '', title].filter(Boolean).join(' · ');
            const metaText = [statusText, priorityText].filter(Boolean).join(' · ');

            const titleNodes = taskId
                ? document.querySelectorAll(`[data-task-live-title][data-task-id="${taskId}"]`)
                : viewRoot.querySelectorAll('[data-task-live-title]');
            const metaNodes = taskId
                ? document.querySelectorAll(`[data-task-live-meta][data-task-id="${taskId}"]`)
                : viewRoot.querySelectorAll('[data-task-live-meta]');

            titleNodes.forEach((node) => {
                node.textContent = titleText;
            });
            metaNodes.forEach((node) => {
                node.textContent = metaText;
            });
        };

        const collectPayload = () => {
            const payload = {};
            const data = new FormData(form);

            data.forEach((value, key) => {
                payload[key] = typeof value === 'string' ? value : '';
            });

            return payload;
        };

        let saveTimer = null;
        let saving = false;
        let queued = false;

        const saveNow = async () => {
            if (saving) {
                queued = true;
                return;
            }

            saving = true;
            queued = false;
            setStatus('Saving...', 'saving');
            setError('');

            try {
                const response = await window.fetch(updateUrl, {
                    method: 'PATCH',
                    headers: {
                        'Content-Type': 'application/json',
                        Accept: 'application/json',
                        'X-CSRF-TOKEN': csrfToken,
                        'X-Requested-With': 'XMLHttpRequest',
                    },
                    body: JSON.stringify(collectPayload()),
                });

                const json = await response.json().catch(() => ({}));

                if (!response.ok) {
                    const firstError = json?.errors
                        ? Object.values(json.errors).flat().find(Boolean)
                        : null;
                    setError(String(firstError || json?.message || 'Failed to save changes.'));
                    setStatus('Saving error', 'error');
                    return;
                }

                updateMetaLabels();
                setStatus('Saved', 'saved');
            } catch {
                setError('Network error. Try again.');
                setStatus('Network error', 'error');
            } finally {
                saving = false;

                if (queued) {
                    saveNow();
                }
            }
        };

        const queueSave = (delay = 280) => {
            if (saveTimer) {
                window.clearTimeout(saveTimer);
            }

            saveTimer = window.setTimeout(() => {
                saveNow();
            }, delay);
        };

        form.addEventListener('input', (event) => {
            const target = event.target;
            if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) {
                return;
            }

            queueSave(320);
        });

        form.addEventListener('change', (event) => {
            const target = event.target;
            if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) {
                return;
            }

            if (target.name === 'project_id') {
                renderProjectStages(false);
            }

            updateMetaLabels();
            queueSave(80);
        });

        form.addEventListener('submit', (event) => {
            event.preventDefault();
            queueSave(0);
        });

        renderProjectStages(true);
        updateMetaLabels();
        setStatus('Ready');
    });
};

const setupSidepanelForms = (scope = document) => {
    const root = scope instanceof Element || scope instanceof Document ? scope : document;
    const forms = root.querySelectorAll('form[data-sidepanel-submit]:not([data-sidepanel-submit-bound="1"])');

    forms.forEach((formNode) => {
        if (!(formNode instanceof HTMLFormElement)) {
            return;
        }

        formNode.dataset.sidepanelSubmitBound = '1';

        const statusNode = formNode.querySelector('[data-sidepanel-form-status]');
        const errorNode = formNode.querySelector('[data-sidepanel-form-error]');
        const submitButton = formNode.querySelector('[data-sidepanel-submit-button]');
        const fieldErrorNodes = Array.from(formNode.querySelectorAll('[data-sidepanel-error-for]'));

        const setStatus = (message = '', tone = 'muted') => {
            if (!(statusNode instanceof HTMLElement)) {
                return;
            }

            statusNode.textContent = message;
            statusNode.classList.remove('hidden', 'text-emerald-600', 'text-amber-600', 'text-slate-600');

            if (message.trim() === '') {
                statusNode.classList.add('hidden');
                return;
            }

            if (tone === 'saved') {
                statusNode.classList.add('text-emerald-600');
                return;
            }

            if (tone === 'saving') {
                statusNode.classList.add('text-amber-600');
                return;
            }

            statusNode.classList.add('text-slate-600');
        };

        const setError = (message = '') => {
            if (!(errorNode instanceof HTMLElement)) {
                return;
            }

            errorNode.textContent = message;
            errorNode.classList.toggle('hidden', message.trim() === '');
        };

        const clearFieldErrors = () => {
            fieldErrorNodes.forEach((node) => {
                if (!(node instanceof HTMLElement)) {
                    return;
                }

                node.textContent = '';
                node.classList.add('hidden');
            });
        };

        const renderFieldErrors = (errors = {}) => {
            clearFieldErrors();

            fieldErrorNodes.forEach((node) => {
                if (!(node instanceof HTMLElement)) {
                    return;
                }

                const key = String(node.dataset.sidepanelErrorFor || '').trim();
                if (key === '') {
                    return;
                }

                const messages = Array.isArray(errors[key]) ? errors[key] : [];
                if (messages.length === 0) {
                    return;
                }

                node.textContent = String(messages[0]);
                node.classList.remove('hidden');
            });
        };

        const toggleSubmitState = (disabled) => {
            if (!(submitButton instanceof HTMLButtonElement)) {
                return;
            }

            submitButton.disabled = disabled;
            submitButton.classList.toggle('opacity-60', disabled);
            submitButton.classList.toggle('cursor-not-allowed', disabled);
        };

        let submitting = false;

        formNode.addEventListener('submit', async (event) => {
            event.preventDefault();

            if (submitting) {
                return;
            }

            const actionUrl = formNode.getAttribute('action');
            if (!actionUrl) {
                return;
            }

            submitting = true;
            toggleSubmitState(true);
            setError('');
            clearFieldErrors();
            setStatus('Saving...', 'saving');

            try {
                const formData = new FormData(formNode);
                formData.set('sidepanel', '1');

                const response = await window.fetch(actionUrl, {
                    method: String(formNode.getAttribute('method') || 'POST').toUpperCase(),
                    headers: {
                        Accept: 'application/json',
                        'X-Requested-With': 'XMLHttpRequest',
                        'X-Sidepanel': '1',
                    },
                    body: formData,
                });

                const payload = await response.json().catch(() => ({}));

                if (!response.ok) {
                    if (response.status === 422 && payload?.errors && typeof payload.errors === 'object') {
                        renderFieldErrors(payload.errors);
                        setStatus('');
                        setError(payload?.message || 'Check the completed fields.');
                        return;
                    }

                    setStatus('');
                    setError(payload?.message || 'Failed to save.');
                    return;
                }

                setError('');
                setStatus(String(payload?.message || 'Saved'), 'saved');

                if ((formNode.dataset.sidepanelSuccessAction || '').toLowerCase() === 'reload') {
                    if (window.crmSidepanel && typeof window.crmSidepanel.close === 'function') {
                        window.crmSidepanel.close();
                    }

                    window.setTimeout(() => {
                        window.location.reload();
                    }, 120);
                    return;
                }

                if (typeof payload?.redirect_url === 'string' && payload.redirect_url.trim() !== '') {
                    window.location.assign(payload.redirect_url);
                    return;
                }
            } catch {
                setStatus('');
                setError('Network error. Try again.');
            } finally {
                submitting = false;
                toggleSubmitState(false);
            }
        });
    });
};

const setupDiskSpreadsheetPreview = async (scope = document) => {
    const root = scope instanceof Element || scope instanceof Document ? scope : document;
    const viewers = Array.from(root.querySelectorAll('[data-disk-spreadsheet-preview]:not([data-spreadsheet-bound="1"])'));

    if (viewers.length === 0) {
        return;
    }

    viewers.forEach((viewer) => {
        if (viewer instanceof HTMLElement) {
            viewer.dataset.spreadsheetBound = '1';
        }
    });

    let WorkbookClass = null;
    let papaParse = null;
    let TabulatorClass = null;

    try {
        const [excelJsModule, papaModule, tabulatorModule] = await Promise.all([
            import('exceljs'),
            import('papaparse'),
            import('tabulator-tables'),
        ]);

        WorkbookClass = excelJsModule?.default?.Workbook || excelJsModule?.Workbook || null;
        papaParse = papaModule?.default?.parse || papaModule?.parse || null;
        TabulatorClass = tabulatorModule?.TabulatorFull
            || tabulatorModule?.Tabulator
            || tabulatorModule?.default
            || null;
    } catch {
        WorkbookClass = null;
        papaParse = null;
        TabulatorClass = null;
    }

    const normalizeCellValue = (value) => {
        if (value === null || value === undefined) {
            return '';
        }

        if (typeof value === 'object') {
            if (typeof value.text === 'string') {
                return value.text;
            }

            if (value instanceof Date) {
                return value.toISOString().slice(0, 19).replace('T', ' ');
            }
        }

        return String(value);
    };

    const columnLabel = (index) => {
        let label = '';
        let current = index + 1;
        while (current > 0) {
            const remainder = (current - 1) % 26;
            label = String.fromCharCode(65 + remainder) + label;
            current = Math.floor((current - 1) / 26);
        }

        return label;
    };

    const parseXlsxSheets = async (buffer) => {
        if (!WorkbookClass) {
            return [];
        }

        const workbook = new WorkbookClass();
        await workbook.xlsx.load(buffer);

        return workbook.worksheets.map((worksheet) => {
            const rows = [];

            worksheet.eachRow({ includeEmpty: false }, (row) => {
                const values = Array.isArray(row.values) ? row.values : [];
                const parsedRow = [];

                for (let index = 1; index < values.length; index++) {
                    const cell = row.getCell(index);
                    const cellText = typeof cell?.text === 'string' && cell.text.trim() !== ''
                        ? cell.text
                        : normalizeCellValue(values[index]);
                    parsedRow.push(cellText);
                }

                while (parsedRow.length > 0 && String(parsedRow[parsedRow.length - 1]).trim() === '') {
                    parsedRow.pop();
                }

                rows.push(parsedRow);
            });

            return {
                name: String(worksheet.name || 'Sheet'),
                rows,
            };
        });
    };

    const parseCsvSheet = (text) => {
        if (!papaParse) {
            return [];
        }

        const parsed = papaParse(text, {
            skipEmptyLines: false,
        });

        const rows = Array.isArray(parsed?.data)
            ? parsed.data.map((row) => (Array.isArray(row) ? row.map((item) => normalizeCellValue(item)) : []))
            : [];

        return [{
            name: 'CSV',
            rows,
        }];
    };

    viewers.forEach(async (viewer) => {
        if (!(viewer instanceof HTMLElement)) {
            return;
        }

        const fileUrl = String(viewer.dataset.fileUrl || '').trim();
        const fileName = String(viewer.dataset.fileName || '').trim();
        const extension = String(viewer.dataset.extension || '').trim().toLowerCase();
        const sheetTabsNode = viewer.querySelector('[data-disk-spreadsheet-tabs]');
        const statusNode = viewer.querySelector('[data-disk-spreadsheet-status]');
        const metaNode = viewer.querySelector('[data-disk-spreadsheet-meta]');
        const gridNode = viewer.querySelector('[data-disk-spreadsheet-grid]');
        const hardRowLimit = 3000;
        const hardColumnLimit = 120;
        let tableInstance = null;

        const setStatus = (message, tone = 'muted') => {
            if (!(statusNode instanceof HTMLElement)) {
                return;
            }

            statusNode.textContent = String(message || '');
            statusNode.classList.remove('text-slate-500', 'text-red-600', 'text-emerald-600');
            statusNode.classList.remove('hidden');

            if (tone === 'error') {
                statusNode.classList.add('text-red-600');
                return;
            }

            if (tone === 'ok') {
                statusNode.classList.add('text-emerald-600');
                return;
            }

            statusNode.classList.add('text-slate-500');
        };

        const setMeta = (message) => {
            if (metaNode instanceof HTMLElement) {
                metaNode.textContent = String(message || '');
            }
        };

        const safeDestroyTable = () => {
            if (tableInstance && typeof tableInstance.destroy === 'function') {
                tableInstance.destroy();
            }
            tableInstance = null;
        };

        if (!WorkbookClass || !papaParse || !TabulatorClass) {
            setStatus('Failed to load spreadsheet viewer components.', 'error');
            return;
        }

        if (!(gridNode instanceof HTMLElement) || !fileUrl) {
            setStatus('Unable to prepare file preview.', 'error');
            return;
        }

        setStatus('Loading spreadsheet file...');

        try {
            const response = await window.fetch(fileUrl, {
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                },
            });

            if (!response.ok) {
                const message = await response.text().catch(() => '');
                throw new Error(message.trim() !== '' ? message.trim() : `HTTP ${response.status}`);
            }

            let sheets = [];

            if (extension === 'csv') {
                const rawText = await response.text();
                sheets = parseCsvSheet(rawText);
            } else {
                const buffer = await response.arrayBuffer();
                sheets = await parseXlsxSheets(buffer);
            }

            if (!Array.isArray(sheets) || sheets.length === 0) {
                throw new Error('No sheets found');
            }

            const renderSheet = (sheetName) => {
                const sheet = sheets.find((entry) => String(entry?.name) === String(sheetName)) || sheets[0];
                const rows = Array.isArray(sheet?.rows) ? sheet.rows : [];
                const totalRows = rows.length;
                const visibleRows = rows.slice(0, hardRowLimit).map((row) => (Array.isArray(row) ? row : []));

                let totalColumns = 0;
                rows.forEach((row) => {
                    if (Array.isArray(row) && row.length > totalColumns) {
                        totalColumns = row.length;
                    }
                });

                const visibleColumns = Math.min(Math.max(totalColumns, 1), hardColumnLimit);

                const data = visibleRows.map((row, rowIndex) => {
                    const record = {
                        __row: rowIndex + 1,
                    };

                    for (let columnIndex = 0; columnIndex < visibleColumns; columnIndex++) {
                        record[`c${columnIndex}`] = normalizeCellValue(row[columnIndex]);
                    }

                    return record;
                });

                if (data.length === 0) {
                    const emptyRow = { __row: 1 };
                    for (let columnIndex = 0; columnIndex < visibleColumns; columnIndex++) {
                        emptyRow[`c${columnIndex}`] = '';
                    }
                    data.push(emptyRow);
                }

                const columns = [{
                    title: '#',
                    field: '__row',
                    width: 66,
                    minWidth: 66,
                    maxWidth: 66,
                    headerSort: false,
                    hozAlign: 'right',
                    headerHozAlign: 'right',
                    cssClass: 'tabulator-row-number-cell',
                    frozen: true,
                }];

                for (let columnIndex = 0; columnIndex < visibleColumns; columnIndex++) {
                    columns.push({
                        title: columnLabel(columnIndex),
                        field: `c${columnIndex}`,
                        headerSort: false,
                        minWidth: 120,
                        formatter: 'plaintext',
                    });
                }

                safeDestroyTable();
                tableInstance = new TabulatorClass(gridNode, {
                    data,
                    columns,
                    height: '100%',
                    layout: 'fitDataTable',
                    resizableColumns: true,
                    movableColumns: false,
                    selectableRows: false,
                    columnHeaderVertAlign: 'middle',
                    rowHeight: 34,
                });

                const isRowsTrimmed = totalRows > visibleRows.length;
                const isColumnsTrimmed = totalColumns > visibleColumns;
                const trimNote = (isRowsTrimmed || isColumnsTrimmed)
                    ? ` · preview limit ${hardRowLimit}x${hardColumnLimit}`
                    : '';

                setMeta(`${fileName || sheet.name} · ${sheet.name} · rows ${visibleRows.length}/${totalRows} · columns ${visibleColumns}/${totalColumns}${trimNote}`);
                setStatus('Spreadsheet loaded.', 'ok');

                window.setTimeout(() => {
                    if (statusNode instanceof HTMLElement) {
                        statusNode.classList.add('hidden');
                    }
                }, 1200);
            };

            if (sheetTabsNode instanceof HTMLElement) {
                sheetTabsNode.innerHTML = '';
                sheets.forEach((sheet, index) => {
                    const button = document.createElement('button');
                    button.type = 'button';
                    button.className = 'inline-flex items-center rounded-md border border-slate-300 bg-white px-3 py-1 text-xs font-medium text-slate-700 hover:bg-slate-100';
                    button.textContent = String(sheet?.name || 'Sheet');
                    if (index === 0) {
                        button.classList.add('disk-spreadsheet-tab-active');
                    }

                    button.addEventListener('click', () => {
                        sheetTabsNode.querySelectorAll('button').forEach((node) => {
                            node.classList.remove('disk-spreadsheet-tab-active');
                        });
                        button.classList.add('disk-spreadsheet-tab-active');
                        setStatus('Switching sheet...');
                        renderSheet(String(sheet?.name || 'Sheet'));
                    });

                    sheetTabsNode.appendChild(button);
                });
            }

            renderSheet(String(sheets[0]?.name || 'Sheet'));
        } catch {
            safeDestroyTable();
            setStatus('Unable to render spreadsheet preview.', 'error');
            setMeta('');
        }
    });
};

const setupDiskWordPreview = async (scope = document) => {
    const root = scope instanceof Element || scope instanceof Document ? scope : document;
    const viewers = Array.from(root.querySelectorAll('[data-disk-word-preview]:not([data-word-bound="1"])'));

    if (viewers.length === 0) {
        return;
    }

    viewers.forEach((viewer) => {
        if (viewer instanceof HTMLElement) {
            viewer.dataset.wordBound = '1';
        }
    });

    let renderDocxAsync = null;

    try {
        const module = await import('docx-preview');
        renderDocxAsync = typeof module?.renderAsync === 'function' ? module.renderAsync : null;
    } catch {
        renderDocxAsync = null;
    }

    viewers.forEach(async (viewer) => {
        if (!(viewer instanceof HTMLElement)) {
            return;
        }

        const fileUrl = String(viewer.dataset.fileUrl || '').trim();
        const statusNode = viewer.querySelector('[data-disk-word-status]');
        const canvasNode = viewer.querySelector('[data-disk-word-canvas]');

        const setStatus = (message, tone = 'muted') => {
            if (!(statusNode instanceof HTMLElement)) {
                return;
            }

            statusNode.textContent = String(message || '');
            statusNode.classList.remove('hidden', 'text-slate-500', 'text-red-600', 'text-emerald-600');

            if (tone === 'error') {
                statusNode.classList.add('text-red-600');
                return;
            }

            if (tone === 'ok') {
                statusNode.classList.add('text-emerald-600');
                return;
            }

            statusNode.classList.add('text-slate-500');
        };

        if (!renderDocxAsync) {
            setStatus('Failed to load Word viewer components.', 'error');
            return;
        }

        if (!(canvasNode instanceof HTMLElement) || fileUrl === '') {
            setStatus('Unable to prepare Word preview.', 'error');
            return;
        }

        setStatus('Loading document...');

        try {
            const response = await window.fetch(fileUrl, {
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                },
            });

            if (!response.ok) {
                const message = await response.text().catch(() => '');
                throw new Error(message.trim() !== '' ? message.trim() : `HTTP ${response.status}`);
            }

            const buffer = await response.arrayBuffer();
            canvasNode.innerHTML = '';

            await renderDocxAsync(buffer, canvasNode, undefined, {
                className: 'docx',
                inWrapper: true,
                breakPages: true,
                ignoreWidth: false,
                ignoreHeight: false,
                renderHeaders: true,
                renderFooters: true,
                renderFootnotes: true,
                renderEndnotes: true,
                useBase64URL: true,
            });

            setStatus('Document loaded.', 'ok');
            window.setTimeout(() => {
                if (statusNode instanceof HTMLElement) {
                    statusNode.classList.add('hidden');
                }
            }, 1200);
        } catch (error) {
            const message = error instanceof Error && error.message.trim() !== ''
                ? error.message.trim()
                : 'Unable to render Word preview.';
            setStatus(message, 'error');
        }
    });
};

const setupTaskCalendar = async () => {
    const calendarRoot = document.querySelector('[data-task-calendar]');
    if (!(calendarRoot instanceof HTMLElement) || calendarRoot.dataset.calendarReady === '1') {
        return;
    }
    calendarRoot.dataset.calendarReady = '1';

    const eventsUrl = calendarRoot.dataset.eventsUrl || '';
    const updateUrlTemplate = calendarRoot.dataset.updateUrlTemplate || '';
    const createUrl = calendarRoot.dataset.createUrl || '/tasks/create';
    const initialView = calendarRoot.dataset.initialView || 'dayGridMonth';
    const filtersForm = document.getElementById('calendar-filters');
    const titleNode = document.querySelector('[data-calendar-title]');
    const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';

    if (!eventsUrl) {
        calendarRoot.innerHTML = '<div class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">Calendar endpoint not specified.</div>';
        return;
    }

    let Calendar = null;
    let interactionPlugin = null;
    let dayGridPlugin = null;
    let timeGridPlugin = null;
    let listPlugin = null;
    let ruLocale = null;

    try {
        const [
            coreModule,
            interactionModule,
            dayGridModule,
            timeGridModule,
            listModule,
            ruLocaleModule,
        ] = await Promise.all([
            import('@fullcalendar/core'),
            import('@fullcalendar/interaction'),
            import('@fullcalendar/daygrid'),
            import('@fullcalendar/timegrid'),
            import('@fullcalendar/list'),
            import('@fullcalendar/core/locales/ru'),
        ]);

        Calendar = coreModule.Calendar;
        interactionPlugin = interactionModule.default;
        dayGridPlugin = dayGridModule.default;
        timeGridPlugin = timeGridModule.default;
        listPlugin = listModule.default;
        ruLocale = ruLocaleModule.default;
    } catch {
        calendarRoot.innerHTML = '<div class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">Failed to load calendar module.</div>';
        return;
    }

    if (!Calendar || !interactionPlugin || !dayGridPlugin || !timeGridPlugin || !listPlugin) {
        calendarRoot.innerHTML = '<div class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">The calendar module was not loaded correctly.</div>';
        return;
    }

    const viewButtons = Array.from(document.querySelectorAll('[data-calendar-view]'));
    const actionButtons = Array.from(document.querySelectorAll('[data-calendar-action]'));

    const extractFilters = () => {
        if (!(filtersForm instanceof HTMLFormElement)) {
            return {};
        }

        const data = new FormData(filtersForm);
        const filters = {};

        data.forEach((rawValue, key) => {
            if (typeof rawValue !== 'string') {
                return;
            }

            const value = rawValue.trim();
            if (value === '') {
                return;
            }

            filters[key] = value;
        });

        if (!data.has('mine')) {
            delete filters.mine;
        }

        return filters;
    };

    const syncQueryString = (viewType) => {
        const params = new URLSearchParams();
        const filters = extractFilters();

        Object.entries(filters).forEach(([key, value]) => {
            params.set(key, String(value));
        });

        if (viewType) {
            params.set('view', viewType);
        }

        const search = params.toString();
        const nextUrl = search === ''
            ? window.location.pathname
            : `${window.location.pathname}?${search}`;

        window.history.replaceState({}, '', nextUrl);
    };

    const setActiveViewButton = (viewType) => {
        viewButtons.forEach((button) => {
            if (!(button instanceof HTMLElement)) {
                return;
            }

            const isActive = button.dataset.calendarView === viewType;
            button.classList.toggle('bg-indigo-600', isActive);
            button.classList.toggle('border-indigo-600', isActive);
            button.classList.toggle('text-white', isActive);
            button.classList.toggle('text-gray-700', !isActive);
            button.classList.toggle('border-gray-300', !isActive);
            button.classList.toggle('hover:bg-gray-50', !isActive);
        });
    };

    const toLocalDateTimeInputValue = (date) => {
        const localTime = date.getTime() - (date.getTimezoneOffset() * 60000);
        return new Date(localTime).toISOString().slice(0, 16);
    };

    const makeTimingUrl = (taskId) => {
        return updateUrlTemplate.replace('__TASK__', encodeURIComponent(String(taskId)));
    };

    let filterDebounce = null;
    const queueRefetch = (delay = 120) => {
        if (!calendar) {
            return;
        }

        if (filterDebounce) {
            window.clearTimeout(filterDebounce);
        }

        filterDebounce = window.setTimeout(() => {
            calendar.refetchEvents();
            syncQueryString(calendar.view.type);
        }, delay);
    };

    const persistTaskTiming = async (changeInfo) => {
        const event = changeInfo?.event;
        if (!event || !updateUrlTemplate) {
            changeInfo?.revert?.();
            return;
        }

        const taskId = Number(event.extendedProps?.task_id || 0);
        if (taskId <= 0) {
            changeInfo?.revert?.();
            return;
        }

        const start = event.start ? event.start.toISOString() : null;
        let end = event.end ? event.end.toISOString() : null;

        if (!end && event.start) {
            end = new Date(event.start.getTime() + (60 * 60 * 1000)).toISOString();
        }

        try {
            const response = await window.fetch(makeTimingUrl(taskId), {
                method: 'PATCH',
                headers: {
                    'Content-Type': 'application/json',
                    Accept: 'application/json',
                    'X-CSRF-TOKEN': csrfToken,
                    'X-Requested-With': 'XMLHttpRequest',
                },
                body: JSON.stringify({ start, end }),
            });

            const payload = await response.json().catch(() => ({}));
            if (!response.ok) {
                throw new Error(payload?.message || 'Failed to update event');
            }

            if (payload?.event?.start) {
                event.setStart(new Date(payload.event.start), { maintainDuration: false });
            }
            if (payload?.event?.end) {
                event.setEnd(new Date(payload.event.end));
            }
        } catch {
            changeInfo?.revert?.();
            window.alert('Failed to save new task deadlines.');
        }
    };

    calendarRoot.innerHTML = '';

    const calendar = new Calendar(calendarRoot, {
        plugins: [interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin],
        initialView,
        locale: 'ru',
        locales: ruLocale ? [ruLocale] : [],
        firstDay: 1,
        nowIndicator: true,
        navLinks: true,
        editable: true,
        eventStartEditable: true,
        eventDurationEditable: true,
        dayMaxEvents: true,
        eventResizableFromStart: true,
        slotMinTime: '06:00:00',
        slotMaxTime: '23:00:00',
        allDaySlot: true,
        height: 'auto',
        headerToolbar: false,
        eventTimeFormat: {
            hour: '2-digit',
            minute: '2-digit',
            hour12: false,
        },
        events: async (fetchInfo, successCallback, failureCallback) => {
            try {
                const params = new URLSearchParams();
                params.set('start', fetchInfo.startStr);
                params.set('end', fetchInfo.endStr);

                const filters = extractFilters();
                Object.entries(filters).forEach(([key, value]) => {
                    params.set(key, String(value));
                });

                const response = await window.fetch(`${eventsUrl}?${params.toString()}`, {
                    headers: {
                        Accept: 'application/json',
                        'X-Requested-With': 'XMLHttpRequest',
                    },
                });

                if (!response.ok) {
                    throw new Error(`Events request failed with status ${response.status}`);
                }

                const payload = await response.json().catch(() => ({}));
                const events = Array.isArray(payload?.events) ? payload.events : [];
                successCallback(events);
            } catch (error) {
                failureCallback(error);
            }
        },
        datesSet: (info) => {
            if (titleNode instanceof HTMLElement) {
                titleNode.textContent = info.view.title;
            }
            setActiveViewButton(info.view.type);
            syncQueryString(info.view.type);
        },
        eventDidMount: (info) => {
            const props = info.event.extendedProps || {};
            const tooltip = [
                props.status_label || null,
                props.priority_label || null,
                props.assignee_name || null,
                props.project_name || null,
            ].filter(Boolean).join(' · ');

            if (tooltip !== '') {
                info.el.setAttribute('title', tooltip);
            }
        },
        eventClick: (info) => {
            info.jsEvent.preventDefault();

            const eventUrl = info.event.url || '';
            if (!eventUrl) {
                return;
            }

            if (window.crmSidepanel && typeof window.crmSidepanel.open === 'function') {
                window.crmSidepanel.open(eventUrl, info.event.title);
                return;
            }

            window.location.assign(eventUrl);
        },
        dateClick: (info) => {
            const destination = new URL(createUrl, window.location.origin);
            const dateValue = info.allDay
                ? `${info.dateStr}T09:00`
                : toLocalDateTimeInputValue(info.date);

            destination.searchParams.set('due_at', dateValue);
            destination.searchParams.set('starts_at', dateValue);

            window.location.assign(destination.toString());
        },
        eventDrop: (info) => {
            persistTaskTiming(info);
        },
        eventResize: (info) => {
            persistTaskTiming(info);
        },
    });

    actionButtons.forEach((button) => {
        if (!(button instanceof HTMLElement)) {
            return;
        }

        button.addEventListener('click', () => {
            const action = button.dataset.calendarAction;
            if (action === 'prev') {
                calendar.prev();
                return;
            }
            if (action === 'next') {
                calendar.next();
                return;
            }
            calendar.today();
        });
    });

    viewButtons.forEach((button) => {
        if (!(button instanceof HTMLElement)) {
            return;
        }

        button.addEventListener('click', () => {
            const viewType = button.dataset.calendarView;
            if (!viewType) {
                return;
            }

            calendar.changeView(viewType);
        });
    });

    if (filtersForm instanceof HTMLFormElement) {
        filtersForm.addEventListener('submit', (event) => {
            event.preventDefault();
            queueRefetch(0);
        });

        filtersForm.querySelectorAll('input, select').forEach((element) => {
            if (!(element instanceof HTMLInputElement || element instanceof HTMLSelectElement)) {
                return;
            }

            const isTextLike = element instanceof HTMLInputElement
                && ['text', 'search'].includes(element.type);
            const eventName = isTextLike ? 'input' : 'change';
            const delay = isTextLike ? 220 : 60;

            element.addEventListener(eventName, () => {
                queueRefetch(delay);
            });
        });
    }

    calendar.render();
};

const setupEntitySidepanel = () => {
    const root = document.getElementById('crm-sidepanel');
    const overlay = document.getElementById('crm-sidepanel-overlay');
    const drawer = document.getElementById('crm-sidepanel-drawer');
    const titleNode = document.getElementById('crm-sidepanel-title');
    const openFullLink = document.getElementById('crm-sidepanel-open-full');
    const closeButton = document.getElementById('crm-sidepanel-close');
    const loadingNode = document.getElementById('crm-sidepanel-loading');
    const bodyNode = document.getElementById('crm-sidepanel-body');

    if (!root || !overlay || !drawer || !titleNode || !openFullLink || !closeButton || !loadingNode || !bodyNode) {
        return;
    }

    let isOpen = false;
    let requestCounter = 0;
    let hideTimer = null;
    const openLinkLabels = {
        default: openFullLink.dataset.labelDefault || 'Open page',
        chat: openFullLink.dataset.labelChat || 'Open chat',
        task: openFullLink.dataset.labelTask || 'Open task',
        deal: openFullLink.dataset.labelDeal || 'Open deal',
        contact: openFullLink.dataset.labelContact || 'Open contact',
        company: openFullLink.dataset.labelCompany || 'Open company',
        project: openFullLink.dataset.labelProject || 'Open project',
        user: openFullLink.dataset.labelUser || 'Open user',
        activity: openFullLink.dataset.labelActivity || 'Open activity',
        disk: openFullLink.dataset.labelDisk || 'Open disk',
        news: openFullLink.dataset.labelNews || 'Open news',
    };

    const clearHideTimer = () => {
        if (hideTimer) {
            window.clearTimeout(hideTimer);
            hideTimer = null;
        }
    };

    const setLoading = (value, message = 'Loading...') => {
        loadingNode.textContent = message;
        loadingNode.classList.toggle('hidden', !value);
    };

    const openPanelShell = () => {
        clearHideTimer();
        root.classList.remove('hidden');
        root.setAttribute('aria-hidden', 'false');
        document.body.classList.add('overflow-hidden');

        window.requestAnimationFrame(() => {
            overlay.classList.remove('opacity-0');
            drawer.classList.remove('translate-x-full');
        });

        isOpen = true;
    };

    const closePanel = () => {
        if (!isOpen) {
            return;
        }

        requestCounter += 1;
        clearHideTimer();
        overlay.classList.add('opacity-0');
        drawer.classList.add('translate-x-full');
        document.body.classList.remove('overflow-hidden');

        hideTimer = window.setTimeout(() => {
            bodyNode.querySelectorAll('[data-chat-component]').forEach((element) => {
                element.dispatchEvent(new CustomEvent('crm:chat-component:destroy'));
            });
            root.classList.add('hidden');
            root.setAttribute('aria-hidden', 'true');
            bodyNode.innerHTML = '';
            setLoading(false);
        }, 220);

        isOpen = false;
    };

    const isInsideSidepanelScope = (anchor) => {
        return Boolean(anchor.closest('[data-sidepanel-scope], #crm-sidepanel-body'));
    };

    const normalizeTitle = (rawTitle) => rawTitle.replace(/\s+/g, ' ').trim();

    const resolveOpenFullLabel = (url) => {
        const pathname = url.pathname.toLowerCase();

        if (/^\/chat\/sidebar\/\d+(\/|$)/.test(pathname) || /^\/chat\/conversations\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.chat;
        }

        if (/^\/tasks\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.task;
        }

        if (/^\/deals\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.deal;
        }

        if (/^\/contacts\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.contact;
        }

        if (/^\/companies\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.company;
        }

        if (/^\/projects\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.project;
        }

        if (/^\/users\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.user;
        }

        if (/^\/activities\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.activity;
        }

        if (/^\/disks\/\d+(\/|$)/.test(pathname)) {
            return openLinkLabels.disk;
        }

        if (/^\/news(\/|$)/.test(pathname)) {
            return openLinkLabels.news;
        }

        return openLinkLabels.default;
    };

    const openSidepanelForUrl = async (entityUrl, preferredTitle = 'Card', options = {}) => {
        const allowAny = Boolean(options?.allowAny);
        let url = typeof entityUrl === 'string' ? sidepanelNormalizeUrl(entityUrl) : entityUrl;
        if (!url) {
            return;
        }

        if (url.origin !== window.location.origin) {
            if (!allowAny) {
                return;
            }

            url = new URL(`${url.pathname}${url.search}${url.hash}`, window.location.origin);
        }

        if (!allowAny && !sidepanelIsEntityUrl(url)) {
            return;
        }

        const requestId = ++requestCounter;
        const panelUrl = new URL(url.toString());
        panelUrl.searchParams.set('sidepanel', '1');

        titleNode.textContent = normalizeTitle(preferredTitle) || 'Card';
        openFullLink.href = url.toString();
        openFullLink.textContent = resolveOpenFullLabel(url);
        bodyNode.innerHTML = '';
        setLoading(true);
        openPanelShell();

        try {
            const response = await window.fetch(panelUrl.toString(), {
                headers: {
                    Accept: 'text/html',
                    'X-Requested-With': 'XMLHttpRequest',
                    'X-Sidepanel': '1',
                },
            });

            if (!response.ok) {
                throw new Error(`Sidepanel request failed with status ${response.status}`);
            }

            const html = await response.text();
            if (requestId !== requestCounter) {
                return;
            }

            if (!html.trim()) {
                throw new Error('Sidepanel payload is empty');
            }

            bodyNode.innerHTML = html;
            if (window.Alpine && typeof window.Alpine.initTree === 'function') {
                window.Alpine.initTree(bodyNode);
            }
            setupHorizontalScrollFade(bodyNode);
            setupTaskLiveForms(bodyNode);
            setupSidepanelForms(bodyNode);
            setupDiskSpreadsheetPreview(bodyNode);
            setupDiskWordPreview(bodyNode);
            setLoading(false);
        } catch {
            if (requestId !== requestCounter) {
                return;
            }

            closePanel();
            window.location.assign(url.toString());
        }
    };

    closeButton.addEventListener('click', closePanel);
    overlay.addEventListener('click', closePanel);

    document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            closePanel();
        }
    });

    document.addEventListener('click', (event) => {
        if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
            return;
        }

        const target = event.target;
        if (!(target instanceof Element)) {
            return;
        }

        const anchor = target.closest('a[href]');
        if (!anchor || !isInsideSidepanelScope(anchor)) {
            return;
        }

        if (anchor.dataset.sidepanel === 'off' || anchor.getAttribute('target') === '_blank' || anchor.hasAttribute('download')) {
            return;
        }

        const href = anchor.getAttribute('href');
        if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('javascript:')) {
            return;
        }

        const url = sidepanelNormalizeUrl(href);
        if (!url || url.origin !== window.location.origin || !sidepanelIsEntityUrl(url)) {
            return;
        }

        event.preventDefault();

        const linkTitle = anchor.dataset.sidepanelTitle ?? anchor.textContent ?? '';
        openSidepanelForUrl(url, linkTitle);
    });

    window.crmSidepanel = {
        open(url, title = 'Card', options = {}) {
            return openSidepanelForUrl(url, title, options);
        },
        close() {
            closePanel();
        },
    };
};

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
        mountVueApps(document);
        setupEntitySidepanel();
        setupHorizontalScrollFade(document);
        setupTaskLiveForms(document);
        setupSidepanelForms(document);
        setupDiskSpreadsheetPreview(document);
        setupDiskWordPreview(document);
        setupTaskCalendar();
    });
} else {
    mountVueApps(document);
    setupEntitySidepanel();
    setupHorizontalScrollFade(document);
    setupTaskLiveForms(document);
    setupSidepanelForms(document);
    setupDiskSpreadsheetPreview(document);
    setupDiskWordPreview(document);
    setupTaskCalendar();
}
