/**
 * Appointment Calendar / Schedule Module
 * Handles day, week and month calendar views for appointments, jobs, and maintenance
 */
const appointmentCalendar = {
    currentView: 'week',
    currentDate: new Date(),
    events: [],
    unscheduledJobs: [],
    sidebarCollapsed: false,

    // ──────────────────────────────────────────────────────────────────────────
    // Init
    // ──────────────────────────────────────────────────────────────────────────

    init: function(view = 'week', date = new Date()) {
        this.currentView = view;
        this.currentDate = new Date(date);
        this.updateViewButtons();
        this.attachEventListeners();
        this.loadAndRender();
        this.loadUnscheduledJobs();
        this.setupSidebarToggle();
        this.setupSidebarDropZone();
    },

    attachEventListeners: function() {
        document.getElementById('btnPrevious').addEventListener('click', () => this.goToPrevious());
        document.getElementById('btnNext').addEventListener('click', () => this.goToNext());
        document.getElementById('btnToday').addEventListener('click', () => this.goToToday());
        document.getElementById('btnDayView').addEventListener('click', () => this.switchView('day'));
        document.getElementById('btnWeekView').addEventListener('click', () => this.switchView('week'));
        document.getElementById('btnMonthView').addEventListener('click', () => this.switchView('month'));

        let resizeTimer;
        window.addEventListener('resize', () => {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(() => this.renderCalendar(), 250);
        });
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Load & Render
    // ──────────────────────────────────────────────────────────────────────────

    loadAndRender: async function() {
        const { start, end } = this.getDateRange();
        await this.loadEvents(start, end);
        this.renderCalendar();
        this.updateCurrentDateDisplay();
        this.updateURL();
    },

    loadEvents: async function(startDate, endDate) {
        try {
            const url = `/Appointments/GetCalendarEvents?start=${startDate.toISOString()}&end=${endDate.toISOString()}`;
            const response = await fetch(url);
            if (!response.ok) throw new Error('Failed to load events');
            this.events = await response.json();
        } catch (error) {
            console.error('Error loading events:', error);
            this.events = [];
        }
    },

    renderCalendar: function() {
        const container = document.getElementById('calendarContainer');
        if (this.currentView === 'day') {
            container.innerHTML = this.renderDayView();
        } else if (this.currentView === 'week') {
            container.innerHTML = this.renderWeekView();
        } else {
            container.innerHTML = this.renderMonthView();
        }
        this.attachEventClickHandlers();
        this.attachDropHandlers();
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Unscheduled Jobs Sidebar
    // ──────────────────────────────────────────────────────────────────────────

    loadUnscheduledJobs: async function() {
        try {
            const resp = await fetch('/Appointments/GetUnscheduledJobs');
            this.unscheduledJobs = await resp.json();
        } catch (e) {
            this.unscheduledJobs = [];
        }
        this.renderUnscheduledPanel();
    },

    renderUnscheduledPanel: function() {
        const panel = document.getElementById('unscheduledJobsPanel');
        const countEl = document.getElementById('unscheduledCount');
        if (!panel) return;

        const count = this.unscheduledJobs.length;
        if (countEl) countEl.textContent = count > 0 ? `${count} job${count === 1 ? '' : 's'}` : '';

        if (count === 0) {
            panel.innerHTML = '<div class="text-muted small text-center py-3"><i class="bi bi-check-circle me-1"></i>All jobs scheduled</div>';
            return;
        }

        let html = '';
        this.unscheduledJobs.forEach(job => {
            let dueLine = '';
            if (job.dueDate) {
                const cls = job.isOverdue ? 'text-danger fw-semibold' : 'text-muted';
                dueLine = `<div class="sj-due ${cls}"><i class="bi bi-calendar-event me-1"></i>${this.formatDateShort(job.dueDate)}${job.isOverdue ? ' ⚠' : ''}</div>`;
            }
            const previewData = JSON.stringify({
                jobNumber: job.jobNumber,
                customerName: job.customerName,
                statusName: job.statusName,
                color: job.color,
                dueDate: job.dueDate,
                isOverdue: job.isOverdue,
                quotedPrice: job.quotedPrice,
                specialInstructions: job.specialInstructions,
                items: job.items || [],
                itemCount: job.itemCount || 0
            });
            html += `
                <div class="sj-card" draggable="true"
                     data-job-id="${job.id}"
                     data-preview="${encodeURIComponent(previewData)}"
                     style="border-left:3px solid ${this.escapeHtml(job.color)}">
                    <div class="sj-number">${this.escapeHtml(job.jobNumber)}</div>
                    <div class="sj-customer">${this.escapeHtml(job.customerName)}</div>
                    <div class="sj-status" style="color:${this.escapeHtml(job.color)}">${this.escapeHtml(job.statusName)}</div>
                    ${dueLine}
                </div>`;
        });
        panel.innerHTML = html;
        this.attachSidebarDragHandlers();
        this.attachHoverPreviewHandlers();
    },

    attachHoverPreviewHandlers: function() {
        const popout = document.getElementById('sjPreviewCard');
        if (!popout) return;

        document.querySelectorAll('.sj-card').forEach(card => {
            card.addEventListener('mouseenter', (e) => {
                let data;
                try { data = JSON.parse(decodeURIComponent(card.dataset.preview)); } catch { return; }
                popout.innerHTML = this.buildPreviewHtml(data);
                popout.style.display = 'block';
                this.positionPreview(e);
            });
            card.addEventListener('mousemove', (e) => this.positionPreview(e));
            card.addEventListener('mouseleave', () => { popout.style.display = 'none'; });
            card.addEventListener('dragstart', () => { popout.style.display = 'none'; });
        });
    },

    positionPreview: function(e) {
        const popout = document.getElementById('sjPreviewCard');
        if (!popout) return;
        const offset = 14;
        const vw = window.innerWidth, vh = window.innerHeight;
        const pw = popout.offsetWidth || 240, ph = popout.offsetHeight || 160;
        let left = e.clientX + offset;
        let top  = e.clientY + offset;
        if (left + pw > vw - 8) left = e.clientX - pw - offset;
        if (top  + ph > vh - 8) top  = e.clientY - ph - offset;
        popout.style.left = left + 'px';
        popout.style.top  = top  + 'px';
    },

    buildPreviewHtml: function(d) {
        const dueLine = d.dueDate
            ? `<div class="sjp-row ${d.isOverdue ? 'sjp-overdue' : ''}">
                   <i class="bi bi-calendar-event"></i>
                   Due ${this.formatDateShort(d.dueDate)}${d.isOverdue ? ' — Overdue' : ''}
               </div>` : '';
        const priceLine = d.quotedPrice > 0
            ? `<div class="sjp-row"><i class="bi bi-currency-dollar"></i>Quoted $${Number(d.quotedPrice).toFixed(2)}</div>` : '';
        const notesLine = d.specialInstructions
            ? `<div class="sjp-notes">${this.escapeHtml(d.specialInstructions)}</div>` : '';

        let itemsHtml = '';
        if (d.items && d.items.length > 0) {
            itemsHtml = '<div class="sjp-items">';
            d.items.forEach(item => {
                const colorDot = item.colorName
                    ? `<span class="sjp-color-dot" title="${this.escapeHtml(item.colorName)}"></span><span class="sjp-color">${this.escapeHtml(item.colorName)}</span>`
                    : '';
                itemsHtml += `<div class="sjp-item"><span class="sjp-qty">×${item.quantity}</span><span class="sjp-item-desc">${this.escapeHtml(item.description)}</span>${colorDot ? colorDot : ''}</div>`;
            });
            if (d.itemCount > d.items.length) {
                itemsHtml += `<div class="sjp-item sjp-more">+ ${d.itemCount - d.items.length} more item${d.itemCount - d.items.length > 1 ? 's' : ''}</div>`;
            }
            itemsHtml += '</div>';
        }

        return `<div class="sjp-card">
            <div class="sjp-status-bar" style="background:${this.escapeHtml(d.color)}">${this.escapeHtml(d.statusName)}</div>
            <div class="sjp-header" style="border-left:3px solid ${this.escapeHtml(d.color)}">
                <span class="sjp-job">${this.escapeHtml(d.jobNumber)}</span>
            </div>
            <div class="sjp-customer">${this.escapeHtml(d.customerName)}</div>
            ${dueLine}${priceLine}${notesLine}
            ${itemsHtml}
        </div>`;
    },

    attachSidebarDragHandlers: function() {
        document.querySelectorAll('.sj-card').forEach(card => {
            card.addEventListener('dragstart', (e) => {
                e.dataTransfer.setData('text/plain', JSON.stringify({
                    type: 'job',
                    jobId: card.dataset.jobId,
                    fromDate: null
                }));
                e.dataTransfer.effectAllowed = 'move';
                card.classList.add('sj-dragging');
            });
            card.addEventListener('dragend', () => card.classList.remove('sj-dragging'));
        });
    },

    setupSidebarDropZone: function() {
        const panel = document.getElementById('unscheduledJobsPanel');
        if (!panel) return;
        const sidebar = document.getElementById('unscheduledSidebar');
        if (!sidebar) return;

        [panel, sidebar].forEach(el => {
            el.addEventListener('dragover', (e) => {
                e.preventDefault();
                sidebar.classList.add('sidebar-drag-over');
            });
            el.addEventListener('dragleave', () => sidebar.classList.remove('sidebar-drag-over'));
            el.addEventListener('drop', (e) => {
                e.preventDefault();
                sidebar.classList.remove('sidebar-drag-over');
                let data;
                try { data = JSON.parse(e.dataTransfer.getData('text/plain')); } catch { return; }
                if (data.type === 'job' && data.fromDate) {
                    this.scheduleJob(data.jobId, null);
                }
            });
        });
    },

    setupSidebarToggle: function() {
        const btn = document.getElementById('btnCollapseSidebar');
        if (!btn) return;
        btn.addEventListener('click', () => {
            this.sidebarCollapsed = !this.sidebarCollapsed;
            const sidebar = document.getElementById('unscheduledSidebar');
            const chevron = document.getElementById('sidebarChevron');
            const body = document.getElementById('unscheduledJobsPanel');
            const countEl = document.getElementById('unscheduledCount');
            const titleEl = document.getElementById('sidebarTitleText');
            if (this.sidebarCollapsed) {
                sidebar.classList.add('sidebar-collapsed');
                if (chevron) { chevron.classList.remove('bi-chevron-left'); chevron.classList.add('bi-chevron-right'); }
                if (body) body.style.display = 'none';
                if (countEl) countEl.style.display = 'none';
                if (titleEl) titleEl.style.display = 'none';
            } else {
                sidebar.classList.remove('sidebar-collapsed');
                if (chevron) { chevron.classList.remove('bi-chevron-right'); chevron.classList.add('bi-chevron-left'); }
                if (body) body.style.display = '';
                if (countEl) countEl.style.display = '';
                if (titleEl) titleEl.style.display = '';
            }
        });
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Drag & Drop — Drop Handlers
    // ──────────────────────────────────────────────────────────────────────────

    attachDropHandlers: function() {
        // Week view: day header columns
        document.querySelectorAll('.calendar-day-header[data-date]').forEach(cell => {
            this.makeDayDropTarget(cell);
        });
        // Month view: month cells
        document.querySelectorAll('.calendar-month-cell[data-date]').forEach(cell => {
            this.makeDayDropTarget(cell);
        });
        // Day view: single day header
        const dayHeader = document.querySelector('.calendar-day-header-single[data-date]');
        if (dayHeader) this.makeDayDropTarget(dayHeader);
    },

    makeDayDropTarget: function(cell) {
        cell.addEventListener('dragover', (e) => {
            e.preventDefault();
            e.stopPropagation();
            cell.classList.add('calendar-drag-over');
        });
        cell.addEventListener('dragleave', (e) => {
            if (!cell.contains(e.relatedTarget)) {
                cell.classList.remove('calendar-drag-over');
            }
        });
        cell.addEventListener('drop', (e) => {
            e.preventDefault();
            e.stopPropagation();
            cell.classList.remove('calendar-drag-over');
            let data;
            try { data = JSON.parse(e.dataTransfer.getData('text/plain')); } catch { return; }
            if (data.type !== 'job') return;
            const dateStr = cell.dataset.date;
            if (!dateStr) return;
            this.scheduleJob(data.jobId, new Date(dateStr));
        });
    },

    scheduleJob: async function(jobId, date) {
        const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;
        const params = new URLSearchParams({ id: jobId });
        if (date) params.append('date', date.toISOString().split('T')[0]);
        if (token) params.append('__RequestVerificationToken', token);

        try {
            const resp = await fetch('/Appointments/ScheduleJob', { method: 'POST', body: params });
            const result = await resp.json();

            if (result.success) {
                await this.loadAndRender();
                await this.loadUnscheduledJobs();
                if (result.removedFromBatch) {
                    this.showToast(`Job scheduled. Removed from oven batch ${this.escapeHtml(result.removedFromBatch)}.`, 'warning');
                } else if (date) {
                    this.showToast('Job scheduled.', 'success');
                } else {
                    this.showToast('Job unscheduled.', 'info');
                }
            } else {
                this.showToast(result.message || 'Failed to update job.', 'danger');
            }
        } catch (e) {
            this.showToast('Failed to update job.', 'danger');
        }
    },

    showToast: function(message, type = 'success') {
        const container = document.getElementById('scheduleToastContainer');
        if (!container) return;
        const id = 'toast-' + Date.now();
        const bgMap = { success: 'bg-success', warning: 'bg-warning text-dark', info: 'bg-info text-dark', danger: 'bg-danger' };
        const cls = bgMap[type] || 'bg-secondary';
        container.insertAdjacentHTML('beforeend', `
            <div id="${id}" class="toast align-items-center ${cls} border-0" role="alert">
                <div class="d-flex">
                    <div class="toast-body">${message}</div>
                    <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast"></button>
                </div>
            </div>`);
        const el = document.getElementById(id);
        if (el && window.bootstrap) new bootstrap.Toast(el, { delay: 4000 }).show();
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Day View
    // ──────────────────────────────────────────────────────────────────────────

    renderDayView: function() {
        const day = this.currentDate;
        const isToday = this.isToday(day);
        const allDayEvents = this.getAllDayEventsForDay(day);
        const totalEvents = this.getEventsForDay(day).length;

        let html = '<div class="calendar-day-view">';
        html += '<div class="calendar-day-view-header">';
        html += `<div class="calendar-day-header-single ${isToday ? 'today' : ''}" data-date="${day.toISOString()}">`;
        html += '<div class="day-header-top">';
        html += `<div class="day-name-long">${this.getDayName(day, 'long')}</div>`;
        html += `<div class="day-number-large">${day.getDate()}</div>`;
        html += `<div class="day-month-year">${this.getMonthName(day)} ${day.getFullYear()}</div>`;
        if (totalEvents > 0) {
            html += `<div class="day-event-count ms-auto"><span class="badge bg-primary">${totalEvents} event${totalEvents === 1 ? '' : 's'}</span></div>`;
        }
        html += '</div>';

        if (allDayEvents.length > 0) {
            html += '<div class="day-header-events">';
            allDayEvents.forEach(event => html += this.renderEventCard(event, 'week-header'));
            html += '</div>';
        }

        html += '</div></div>';

        html += '<div class="calendar-day-grid">';
        for (let hour = 6; hour < 21; hour++) {
            const hourEvents = this.getEventsForDayHour(day, hour);
            html += `<div class="calendar-day-time-row ${hourEvents.length > 0 ? 'has-events' : ''}">`;
            html += `<div class="time-label">${this.formatHour(hour)}</div>`;
            html += '<div class="calendar-day-time-cell">';
            hourEvents.forEach(event => html += this.renderEventCard(event, 'day'));
            html += '</div></div>';
        }
        html += '</div></div>';
        return html;
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Week View
    // ──────────────────────────────────────────────────────────────────────────

    renderWeekView: function() {
        const { start } = this.getDateRange();
        const days = [];
        for (let i = 0; i < 7; i++) {
            const day = new Date(start);
            day.setDate(start.getDate() + i);
            days.push(day);
        }

        let html = '<div class="calendar-week-view">';
        html += '<div class="calendar-week-header">';
        html += '<div class="calendar-week-header-spacer"></div>';

        days.forEach(day => {
            const isToday = this.isToday(day);
            const allDayEvents = this.getAllDayEventsForDay(day);

            html += `<div class="calendar-day-header ${isToday ? 'today' : ''}" data-date="${day.toISOString()}">`;
            html += '<div class="day-header-top">';
            html += `<div class="day-name">${this.getDayName(day, 'short')}</div>`;
            html += `<div class="day-number day-drill" data-day-date="${day.toISOString()}" title="View ${this.getDayName(day, 'long')} ${day.getDate()}">${day.getDate()}</div>`;
            html += '</div>';

            if (allDayEvents.length > 0) {
                html += '<div class="day-header-events">';
                allDayEvents.forEach(event => html += this.renderEventCard(event, 'week-header'));
                html += '</div>';
            }
            html += '</div>';
        });

        html += '</div>';
        html += '<div class="calendar-week-grid">';

        for (let hour = 6; hour < 20; hour++) {
            html += '<div class="calendar-time-row">';
            html += `<div class="time-label">${this.formatHour(hour)}</div>`;
            days.forEach(day => {
                const dayEvents = this.getEventsForDayHour(day, hour);
                html += `<div class="calendar-time-cell" data-date="${day.toISOString()}" data-hour="${hour}">`;
                dayEvents.forEach(event => html += this.renderEventCard(event, 'week'));
                html += '</div>';
            });
            html += '</div>';
        }

        html += '</div></div>';
        return html;
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Month View
    // ──────────────────────────────────────────────────────────────────────────

    renderMonthView: function() {
        const firstDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);
        const calendarStart = new Date(firstDay);
        calendarStart.setDate(calendarStart.getDate() - ((calendarStart.getDay() + 6) % 7));

        let html = '<div class="calendar-month-view">';
        html += '<div class="calendar-month-header">';
        ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach(name => {
            html += `<div class="calendar-day-name">${name}</div>`;
        });
        html += '</div>';
        html += '<div class="calendar-month-grid">';

        let currentDay = new Date(calendarStart);
        for (let week = 0; week < 6; week++) {
            html += '<div class="calendar-week-row">';
            for (let day = 0; day < 7; day++) {
                const isCurrentMonth = currentDay.getMonth() === this.currentDate.getMonth();
                const isToday = this.isToday(currentDay);
                const allEvents = this.getEventsForDay(currentDay);
                const dayIso = currentDay.toISOString();
                const allDayEvents = allEvents.filter(e => e.allDay);
                const timedEvents = allEvents.filter(e => !e.allDay);

                html += `<div class="calendar-month-cell ${isCurrentMonth ? '' : 'other-month'} ${isToday ? 'today' : ''}" data-date="${dayIso}">`;
                html += `<div class="cell-date day-drill" data-day-date="${dayIso}" title="View day">${currentDay.getDate()}</div>`;
                html += '<div class="cell-events">';

                allDayEvents.forEach(event => html += this.renderEventCard(event, 'month'));

                const maxEvents = 3;
                const remainingSlots = maxEvents - allDayEvents.length;
                timedEvents.slice(0, remainingSlots).forEach(event => html += this.renderEventCard(event, 'month'));

                const totalEvents = allEvents.length;
                if (totalEvents > maxEvents) {
                    html += `<div class="event-more day-drill" data-day-date="${dayIso}">+${totalEvents - maxEvents} more</div>`;
                }

                html += '</div></div>';
                currentDay.setDate(currentDay.getDate() + 1);
            }
            html += '</div>';
        }

        html += '</div></div>';
        return html;
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Event Card Rendering
    // ──────────────────────────────────────────────────────────────────────────

    renderEventCard: function(event, viewType) {
        const colorClass = this.getColorClass(event.backgroundColor);
        const time = event.allDay ? 'All Day' : new Date(event.start).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
        const eventType = event.eventType || 'Appointment';
        const allDayClass = event.allDay ? 'all-day-event' : '';
        const isJob = eventType === 'Job';
        const dragAttr = isJob ? `draggable="true" data-job-id="${event.id}"` : '';
        const fallbackAttr = isJob && event.isFallbackDate ? 'data-fallback="true"' : '';
        const fallbackStyle = isJob && event.isFallbackDate ? 'opacity:0.75;border-style:dashed;' : '';
        const jobIcon = isJob ? '<i class="bi bi-briefcase me-1" style="font-size:0.65rem"></i>' : '';
        const fallbackIcon = isJob && event.isFallbackDate ? ' <i class="bi bi-calendar-x" title="No scheduled date \u2014 showing on due date" style="font-size:0.65rem"></i>' : '';

        if (viewType === 'week-header') {
            return `<div class="calendar-event-header ${colorClass} ${allDayClass}"
                         data-event-id="${event.id}" data-event-type="${eventType}"
                         ${dragAttr} ${fallbackAttr}
                         style="${fallbackStyle}"
                         title="${this.escapeHtml(event.title)}">
                        ${jobIcon}<span class="event-text">${this.escapeHtml(isJob ? (event.jobNumber || event.title) : event.title)}</span>${fallbackIcon}
                    </div>`;
        } else if (viewType === 'day') {
            const locationHtml = event.location
                ? `<div class="event-location"><i class="bi bi-geo-alt me-1"></i>${this.escapeHtml(event.location)}</div>` : '';
            return `<div class="calendar-event calendar-event-day ${colorClass} ${allDayClass}"
                         data-event-id="${event.id}" data-event-type="${eventType}"
                         ${dragAttr} ${fallbackAttr}
                         style="${fallbackStyle}"
                         title="${this.escapeHtml(event.title)}">
                        <div class="event-time">${jobIcon}${time}</div>
                        <div class="event-title">${this.escapeHtml(event.title)}</div>
                        <div class="event-customer">${this.escapeHtml(event.customerName || '')}</div>
                        ${locationHtml}
                    </div>`;
        } else if (viewType === 'week') {
            return `<div class="calendar-event ${colorClass} ${allDayClass}"
                         data-event-id="${event.id}" data-event-type="${eventType}"
                         ${dragAttr} ${fallbackAttr}
                         style="${fallbackStyle}"
                         title="${this.escapeHtml(event.title)} \u2014 ${this.escapeHtml(event.customerName)}">
                        <div class="event-time">${jobIcon}${time}</div>
                        <div class="event-title">${this.escapeHtml(event.title)}</div>
                        <div class="event-customer">${this.escapeHtml(event.customerName)}</div>
                    </div>`;
        } else {
            // Month view
            return `<div class="calendar-event-month ${colorClass} ${allDayClass}"
                         data-event-id="${event.id}" data-event-type="${eventType}"
                         ${dragAttr} ${fallbackAttr}
                         title="${time} \u2014 ${this.escapeHtml(event.title)}">
                        <span class="event-dot"></span>
                        <span class="event-text">${jobIcon}${isJob ? this.escapeHtml(event.jobNumber || event.title) : (time + ' ' + this.escapeHtml(event.title))}</span>
                    </div>`;
        }
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Event Helpers
    // ──────────────────────────────────────────────────────────────────────────

    getEventsForDay: function(day) {
        const dayStart = new Date(day); dayStart.setHours(0, 0, 0, 0);
        const dayEnd = new Date(day); dayEnd.setHours(23, 59, 59, 999);
        return this.events.filter(event => {
            const eventStart = new Date(event.start);
            return eventStart >= dayStart && eventStart <= dayEnd;
        });
    },

    getAllDayEventsForDay: function(day) {
        const dayStart = new Date(day); dayStart.setHours(0, 0, 0, 0);
        const dayEnd = new Date(day); dayEnd.setHours(23, 59, 59, 999);
        return this.events.filter(event => {
            if (!event.allDay) return false;
            const eventStart = new Date(event.start);
            return eventStart >= dayStart && eventStart <= dayEnd;
        });
    },

    getEventsForDayHour: function(day, hour) {
        const hourStart = new Date(day); hourStart.setHours(hour, 0, 0, 0);
        const hourEnd = new Date(day); hourEnd.setHours(hour, 59, 59, 999);
        return this.events.filter(event => {
            if (event.allDay) return false;
            const eventStart = new Date(event.start);
            return eventStart >= hourStart && eventStart <= hourEnd;
        });
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Click Handlers
    // ──────────────────────────────────────────────────────────────────────────

    attachEventClickHandlers: function() {
        document.querySelectorAll('.calendar-event, .calendar-event-month, .calendar-event-header').forEach(el => {
            el.addEventListener('click', (e) => {
                e.stopPropagation();
                const eventId = el.dataset.eventId;
                const eventType = el.dataset.eventType || 'Appointment';
                if (eventType === 'Maintenance') {
                    window.location.href = `/Maintenance/Details/${eventId}`;
                } else if (eventType === 'Job') {
                    window.location.href = `/Jobs/Details/${eventId}`;
                } else {
                    window.location.href = `/Appointments/Details/${eventId}`;
                }
            });
        });

        document.querySelectorAll('.day-drill').forEach(el => {
            el.addEventListener('click', (e) => {
                e.stopPropagation();
                const date = new Date(el.dataset.dayDate);
                this.switchToDay(date);
            });
        });

        // Job event drag handlers (calendar → calendar reschedule)
        document.querySelectorAll('[data-event-type="Job"]').forEach(el => {
            el.setAttribute('draggable', 'true');
            el.addEventListener('dragstart', (e) => {
                e.stopPropagation();
                const cell = el.closest('[data-date]');
                e.dataTransfer.setData('text/plain', JSON.stringify({
                    type: 'job',
                    jobId: el.dataset.eventId,
                    fromDate: cell?.dataset.date || null
                }));
                e.dataTransfer.effectAllowed = 'move';
                el.classList.add('event-dragging');
            });
            el.addEventListener('dragend', () => el.classList.remove('event-dragging'));
        });
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Date Ranges & Navigation
    // ──────────────────────────────────────────────────────────────────────────

    getDateRange: function() {
        if (this.currentView === 'day')  return this.getDayRange(this.currentDate);
        if (this.currentView === 'week') return this.getWeekRange(this.currentDate);
        return this.getMonthRange(this.currentDate);
    },

    getDayRange: function(date) {
        const start = new Date(date); start.setHours(0, 0, 0, 0);
        const end = new Date(date); end.setHours(23, 59, 59, 999);
        return { start, end };
    },

    getWeekRange: function(date) {
        const start = new Date(date);
        const day = start.getDay();
        const diff = start.getDate() - day + (day === 0 ? -6 : 1);
        start.setDate(diff);
        start.setHours(0, 0, 0, 0);
        const end = new Date(start);
        end.setDate(start.getDate() + 7);
        end.setHours(23, 59, 59, 999);
        return { start, end };
    },

    getMonthRange: function(date) {
        const start = new Date(date.getFullYear(), date.getMonth(), 1);
        start.setHours(0, 0, 0, 0);
        const end = new Date(date.getFullYear(), date.getMonth() + 1, 0);
        end.setHours(23, 59, 59, 999);
        return { start, end };
    },

    goToPrevious: function() {
        if (this.currentView === 'day')        this.currentDate.setDate(this.currentDate.getDate() - 1);
        else if (this.currentView === 'week')  this.currentDate.setDate(this.currentDate.getDate() - 7);
        else                                   this.currentDate.setMonth(this.currentDate.getMonth() - 1);
        this.loadAndRender();
    },

    goToNext: function() {
        if (this.currentView === 'day')        this.currentDate.setDate(this.currentDate.getDate() + 1);
        else if (this.currentView === 'week')  this.currentDate.setDate(this.currentDate.getDate() + 7);
        else                                   this.currentDate.setMonth(this.currentDate.getMonth() + 1);
        this.loadAndRender();
    },

    goToToday: function() {
        this.currentDate = new Date();
        this.loadAndRender();
    },

    switchView: function(view) {
        this.currentView = view;
        this.updateViewButtons();
        this.loadAndRender();
    },

    switchToDay: function(date) {
        this.currentDate = new Date(date);
        this.currentView = 'day';
        this.updateViewButtons();
        this.loadAndRender();
    },

    // ──────────────────────────────────────────────────────────────────────────
    // UI State
    // ──────────────────────────────────────────────────────────────────────────

    updateViewButtons: function() {
        document.getElementById('btnDayView').classList.toggle('active', this.currentView === 'day');
        document.getElementById('btnWeekView').classList.toggle('active', this.currentView === 'week');
        document.getElementById('btnMonthView').classList.toggle('active', this.currentView === 'month');
    },

    updateCurrentDateDisplay: function() {
        const display = document.getElementById('currentDateDisplay');
        if (this.currentView === 'day') {
            display.textContent = `${this.getDayName(this.currentDate, 'long')}, ${this.getMonthName(this.currentDate)} ${this.currentDate.getDate()}, ${this.currentDate.getFullYear()}`;
        } else if (this.currentView === 'week') {
            const { start, end } = this.getWeekRange(this.currentDate);
            const endDisplay = new Date(end);
            endDisplay.setDate(endDisplay.getDate() - 1);
            if (start.getMonth() === endDisplay.getMonth()) {
                display.textContent = `${this.getMonthName(start)} ${start.getDate()}\u2013${endDisplay.getDate()}, ${start.getFullYear()}`;
            } else {
                display.textContent = `${this.getMonthName(start)} ${start.getDate()} \u2013 ${this.getMonthName(endDisplay)} ${endDisplay.getDate()}, ${start.getFullYear()}`;
            }
        } else {
            display.textContent = `${this.getMonthName(this.currentDate)} ${this.currentDate.getFullYear()}`;
        }
    },

    updateURL: function() {
        const url = new URL(window.location);
        url.searchParams.set('view', this.currentView);
        url.searchParams.set('date', this.currentDate.toISOString().split('T')[0]);
        window.history.replaceState({}, '', url);
    },

    // ──────────────────────────────────────────────────────────────────────────
    // Helpers
    // ──────────────────────────────────────────────────────────────────────────

    isToday: function(date) {
        const today = new Date();
        return date.getDate() === today.getDate() &&
               date.getMonth() === today.getMonth() &&
               date.getFullYear() === today.getFullYear();
    },

    getDayName: function(date, format = 'long') {
        return date.toLocaleDateString('en-US', { weekday: format });
    },

    getMonthName: function(date) {
        return date.toLocaleDateString('en-US', { month: 'long' });
    },

    formatHour: function(hour) {
        const period = hour >= 12 ? 'PM' : 'AM';
        const displayHour = hour > 12 ? hour - 12 : (hour === 0 ? 12 : hour);
        return `${displayHour} ${period}`;
    },

    formatDateShort: function(dateStr) {
        const d = new Date(dateStr + 'T00:00:00');
        return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
    },

    getColorClass: function(bgColor) {
        if (bgColor && bgColor.startsWith('#')) {
            const colorMap = {
                '#6f42c1': 'calendar-event-purple',
                '#198754': 'calendar-event-green',
                '#0d6efd': 'calendar-event-blue',
                '#fd7e14': 'calendar-event-orange',
                '#d63384': 'calendar-event-pink',
                '#0dcaf0': 'calendar-event-cyan',
                '#20c997': 'calendar-event-teal',
                '#6610f2': 'calendar-event-indigo',
                '#84cc16': 'calendar-event-lime',
                '#795548': 'calendar-event-brown',
                '#dc3545': 'calendar-event-red',
                '#ffc107': 'calendar-event-yellow',
                '#6c757d': 'calendar-event-gray',
                '#adb5bd': 'calendar-event-gray',
                '#212529': 'calendar-event-dark'
            };
            return colorMap[bgColor] || 'calendar-event-blue';
        }
        return bgColor ? `calendar-event-${bgColor}` : 'calendar-event-blue';
    },

    escapeHtml: function(text) {
        const div = document.createElement('div');
        div.textContent = text || '';
        return div.innerHTML;
    }
};

window.appointmentCalendar = appointmentCalendar;
window.loadCalendarEvents = () => appointmentCalendar.loadAndRender();
