import './bootstrap';
import htmx from 'htmx.org';
import { handleEventModalAfterSwap, initEventModalGlobals, initEventModalUI } from './modules/event-modal';

const SELECTORS = {
    calendarToggle: '.calendar-toggle',
    calendarExpandToggle: '[data-calendar-expand]',
    colorPicker: '[data-colorpicker]',
    colorPickerColor: '[data-colorpicker-color]',
    colorPickerHex: '[data-colorpicker-hex]',
    colorPickerRandom: '[data-colorpicker-random]',
    monthDay: '.calendar.month .day',
    monthDayEvent: 'a.event',
    monthDayMore: '[data-day-more]',
    monthDayMoreWrap: '.more-events',
};

/**
 * htmx/global
 */

// make html globally visible to use the devtools and extensions
window.htmx = htmx;

// global htmx config
htmx.config.historyEnabled = true; // HX-Boost back/forward support
htmx.logger = console.log;         // verbose logging during dev

// csrf on htmx requests
document.addEventListener('htmx:configRequest', (evt) => {
    const token = document.querySelector('meta[name="csrf-token"]')?.content
    if (token) evt.detail.headers['X-CSRF-TOKEN'] = token
})


/**
 *
 * global auth expiry redirect (fetch/axios)
 */

const AUTH_REDIRECT_STATUSES = new Set([401, 419]);
const redirectToLogin = () => {
    if (window.location.pathname !== '/login') {
        window.location.assign('/login');
    }
};

if (window.fetch) {
    const originalFetch = window.fetch.bind(window);
    window.fetch = async (...args) => {
        const response = await originalFetch(...args);
        if (response && AUTH_REDIRECT_STATUSES.has(response.status)) {
            redirectToLogin();
        }
        return response;
    };
}

if (window.axios) {
    window.axios.interceptors.response.use(
        (response) => response,
        (error) => {
            const status = error?.response?.status;
            if (AUTH_REDIRECT_STATUSES.has(status)) {
                redirectToLogin();
            }
            return Promise.reject(error);
        }
    );
}

/**
 *
 * calendar ui improvements
 */

// progressive enhancement on html form with no JS
document.addEventListener('change', (event) => {
    const target = event.target;

    if (target?.matches(SELECTORS.calendarToggle)) {
        const slug = target.value;
        const show = target.checked;

        document
            .querySelectorAll(`[data-calendar="${slug}"]`)
            .forEach(el => el.classList.toggle('hidden', !show));
        return;
    }

    const form = target?.form;
    if (!form || form.id !== 'calendar-view') return;
    if (target.name !== 'view') return;

    form.requestSubmit();
});


/**
 *
 * auto-scroll time views to 8am on load (when daytime hours are disabled)
 */
function initTimeViewAutoScroll(root = document)
{
    // make sure we're on a time calendar
    const calendar = root.querySelector('.calendar.time');
    if (!calendar) return;

    // get out if we're autoscrolled or daytime hours is set
    if (calendar.dataset.autoscrolled === '1') return;
    if (calendar.dataset.daytimeHoursEnabled === '1') return;

    // find the target minute (8:00am)
    const target = calendar.querySelector('[data-slot-minutes="480"]');
    if (!target) return;

    // get the scroll container and offset
    const container = calendar.closest('article') || document.querySelector('article#calendar');
    if (!container) return;
    const header = container.querySelector('header');
    const headerOffset = header ? header.offsetHeight : 0;
    const containerRect = container.getBoundingClientRect();
    const targetRect = target.getBoundingClientRect();
    const top = targetRect.top - containerRect.top + container.scrollTop - headerOffset - 12;

    // scroll
    container.scrollTo({ top: Math.max(top, 0), behavior: 'auto' });
    calendar.dataset.autoscrolled = '1';
}


/**
 * calendar sidebar expand toggle
 */

document.addEventListener('click', (event) => {
    const toggle = event.target.closest(SELECTORS.calendarExpandToggle);
    if (!toggle) return;

    event.preventDefault();

    const main = toggle.closest('main');
    if (!main) return;

    const isExpanded = main.classList.toggle('expanded');
    toggle.setAttribute('aria-pressed', isExpanded ? 'true' : 'false');
});

/**
 *
 * color picker component
 * native <input type="color"> + hex + random palette)
 */

function initColorPickers(root = document) {
    const isHex = (v) => /^#?[0-9a-fA-F]{6}$/.test((v || '').trim());

    const normalize = (v) => {
        let s = (v || '').trim();
        if (!s) return null;
        if (!s.startsWith('#')) s = '#' + s;
        if (!isHex(s)) return null;
        return s.toUpperCase();
    };

    const pickRandom = (arr) => arr[Math.floor(Math.random() * arr.length)];

    const wire = (el) => {
        // avoid double-binding when htmx swaps
        if (el.__colorpickerWired) return;
        el.__colorpickerWired = true;

        const color = el.querySelector(SELECTORS.colorPickerColor);
        const hex = el.querySelector(SELECTORS.colorPickerHex);
        const btn = el.querySelector(SELECTORS.colorPickerRandom);

        if (!color || !hex) return;

        let palette = [];
        try {
            palette = JSON.parse(el.getAttribute('data-palette') || '[]');
        } catch {
            palette = [];
        }

        const setValue = (val) => {
            const n = normalize(val);
            if (!n) return false;

            color.value = n;
            hex.value = n;

            // bubble input/change for any listeners (htmx, previews, etc.)
            color.dispatchEvent(new Event('input', { bubbles: true }));
            color.dispatchEvent(new Event('change', { bubbles: true }));
            return true;
        };

        // init sync from native input
        hex.value = normalize(color.value) || '#000000';

        // native picker -> hex field
        color.addEventListener('input', () => {
            const n = normalize(color.value);
            if (n) hex.value = n;
        });

        // hex typing -> native picker (on blur + Enter)
        const commitHex = () => {
            const ok = setValue(hex.value);
            if (!ok) hex.value = normalize(color.value) || hex.value;
        };

        hex.addEventListener('blur', commitHex);
        hex.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                commitHex();
            }
        });

        // random button
        if (btn && palette.length) {
            btn.addEventListener('click', (e) => {
                e.preventDefault(); // defensive: never submit, never navigate
                e.stopPropagation();

                let next = pickRandom(palette);
                if (palette.length > 1) {
                    const current = normalize(color.value);
                    // avoid re-rolling the same number if possible
                    while (normalize(next) === current) next = pickRandom(palette);
                }
                setValue(next);
            });
        }
    };

    root.querySelectorAll(SELECTORS.colorPicker).forEach(wire);
}


/**
 *
 * month view overflow handling (progressive enhancement)
 */

function initMonthOverflow(root = document) {
    const days = root.querySelectorAll(SELECTORS.monthDay);
    days.forEach((day) => updateMonthOverflow(day));
}

function ensureDayMoreButton(dayEl) {
    let wrapper = dayEl.querySelector(SELECTORS.monthDayMoreWrap);
    if (!wrapper) {
        wrapper = document.createElement('div');
        wrapper.className = 'more-events';
        dayEl.appendChild(wrapper);
    }

    let button = wrapper.querySelector(SELECTORS.monthDayMore);
    if (!button) {
        button = document.createElement('button');
        button.type = 'button';
        button.className = 'day-more hidden';
        button.setAttribute('data-day-more', '');
        wrapper.appendChild(button);
    }

    return button;
}

function formatMoreLabel(dayEl, count) {
    const template = dayEl.getAttribute('data-more-label') || ':count more';
    return template.replace(':count', count);
}

function lessLabel(dayEl) {
    return dayEl.getAttribute('data-less-label') || 'Show less';
}

function updateMonthOverflow(dayEl) {
    if (!dayEl) return;

    const events = Array.from(dayEl.querySelectorAll(SELECTORS.monthDayEvent))
        .filter((el) => !el.classList.contains('hidden'));

    const moreButton = ensureDayMoreButton(dayEl);

    if (!events.length) {
        moreButton.textContent = '';
        moreButton.classList.add('hidden');
        moreButton.removeAttribute('aria-expanded');
        dayEl.classList.remove('day--event-overflow');
        dayEl.setAttribute('data-event-visible', '0');
        return;
    }

    if (dayEl.classList.contains('is-expanded')) {
        moreButton.textContent = lessLabel(dayEl);
        moreButton.classList.remove('hidden');
        moreButton.setAttribute('aria-expanded', 'true');
        dayEl.classList.remove('day--event-overflow');
        dayEl.setAttribute('data-event-visible', String(events.length));
        return;
    }

    const wrapper = moreButton.closest(SELECTORS.monthDayMoreWrap);
    let wrapperHeight = wrapper ? wrapper.getBoundingClientRect().height : 0;
    if (wrapperHeight === 0 && wrapper) {
        const wasHidden = moreButton.classList.contains('hidden');
        const prevVisibility = moreButton.style.visibility;

        if (wasHidden) {
            moreButton.classList.remove('hidden');
            moreButton.style.visibility = 'hidden';
        }

        wrapperHeight = wrapper.getBoundingClientRect().height || 0;

        if (wasHidden) {
            moreButton.classList.add('hidden');
            moreButton.style.visibility = prevVisibility;
        }
    }

    const prevVisibility = dayEl.style.visibility;
    dayEl.style.visibility = 'hidden';
    dayEl.removeAttribute('data-event-visible');
    dayEl.classList.remove('day--event-overflow');

    const availableHeight = dayEl.clientHeight - wrapperHeight;
    let hiddenCount = 0;
    events.forEach((eventEl) => {
        const bottom = eventEl.offsetTop + eventEl.offsetHeight;
        if (bottom > availableHeight + 0.5) {
            hiddenCount += 1;
        }
    });

    dayEl.style.visibility = prevVisibility;

    const visibleCount = Math.max(0, events.length - hiddenCount);
    dayEl.setAttribute('data-event-visible', String(visibleCount));

    if (hiddenCount > 0) {
        moreButton.textContent = formatMoreLabel(dayEl, hiddenCount);
        moreButton.classList.remove('hidden');
        moreButton.setAttribute('aria-expanded', 'false');
        dayEl.classList.add('day--event-overflow');
    } else {
        moreButton.textContent = '';
        moreButton.classList.add('hidden');
        moreButton.removeAttribute('aria-expanded');
        dayEl.classList.remove('day--event-overflow');
    }
}

// show more events in a month calendar day when some are hidden
document.addEventListener('click', (event) => {
    const button = event.target.closest(SELECTORS.monthDayMore);
    if (!button) return;

    const dayEl = button.closest(SELECTORS.monthDay);
    if (!dayEl) return;

    dayEl.classList.toggle('is-expanded');
    updateMonthOverflow(dayEl);
});

// month day resizer
let monthResizeTimer;
window.addEventListener('resize', () => {
    if (!document.querySelector(SELECTORS.monthDay)) return;
    window.clearTimeout(monthResizeTimer);
    monthResizeTimer = window.setTimeout(() => initMonthOverflow(), 100);
});



/**
 *
 * initialization
 */

function initUI() {
    initEventModalGlobals();
    initEventModalUI();
    initColorPickers();
    initTimeViewAutoScroll();
    initMonthOverflow();
}

// initial bind
document.addEventListener('DOMContentLoaded', initUI);

// rebind in htmx for swapped content
document.addEventListener('htmx:afterSwap', (e) => {
    const target = e.detail?.target || e.target;

    handleEventModalAfterSwap(target);

    initEventModalUI(target);
    initColorPickers(target);
    initTimeViewAutoScroll(target);
    initMonthOverflow(target);
});
