<?php
namespace Components\Ui;

use Components\Base;
use Components\Button;
use Components\ButtonGroup;
use Components\Router;
use DateInterval;
use DateTimeImmutable;
use DateTimeZone;

class Calendar
{
    private const VIEW_WEEK = 'week';
    private const VIEW_MONTH = 'month';
    private const VIEW_QUARTER = 'quarter';
    private const VIEW_YEAR = 'year';

    /**
     * @return array<string,mixed>
     */
    public static function resolveStateFromRequest(): array
    {
        $viewRaw = strtolower((string)mh_get('view', self::VIEW_MONTH, 'string'));
        $view = in_array($viewRaw, [self::VIEW_WEEK, self::VIEW_MONTH, self::VIEW_QUARTER, self::VIEW_YEAR], true)
            ? $viewRaw
            : self::VIEW_MONTH;

        $tzName = (string)date_default_timezone_get();
        $tz = new DateTimeZone($tzName !== '' ? $tzName : 'UTC');
        $now = new DateTimeImmutable('now', $tz);

        $anchorRaw = (string)mh_get('date', '', 'string');
        $anchor = self::parseYmd($anchorRaw, $tz) ?? $now;
        $anchor = $anchor->setTime(0, 0, 0);

        if ($view === self::VIEW_WEEK) {
            $anchor = self::startOfWeek($anchor);
        } elseif ($view === self::VIEW_YEAR) {
            $anchor = $anchor->setDate((int)$anchor->format('Y'), 1, 1);
        } else {
            $anchor = $anchor->modify('first day of this month');
        }

        [$rangeStart, $rangeEnd] = self::resolveRange($view, $anchor);

        $filterRaw = strtolower((string)mh_get('filter', 'all', 'string'));
        $filter = in_array($filterRaw, ['all', 'newsletter', 'automations'], true) ? $filterRaw : 'all';

        return [
            'view' => $view,
            'filter' => $filter,
            'anchor' => $anchor,
            'now' => $now,
            'tz' => $tz,
            'locale' => self::resolveLocaleCode(),
            'range_start' => $rangeStart,
            'range_end' => $rangeEnd,
            'title' => self::buildTitle($view, $anchor),
            'subtitle' => self::rangeLabel($rangeStart, $rangeEnd),
            'week_label' => mh_lng('UI_CALENDAR_WEEK', 'Week') . ' ' . $anchor->format('W'),
            'page_url' => Router::url('calendar'),
            'endpoint_url' => Router::url('calendar_view', ['partial' => 1]),
            'create_base_url' => Router::url('newsletter'),
        ];
    }

    /**
     * @param array<string,mixed> $state
     */
    public static function renderCalendar(array $state): string
    {
        return self::render($state, self::sampleData($state));
    }

    /**
     * @param array<string,mixed> $state
     * @return array<int,array<string,mixed>>
     */
    public static function sampleData(array $state): array
    {
        return self::sampleEvents($state);
    }

    /**
     * @param array<string,mixed> $state
     * @return array<string,mixed>
     */
    public static function resolveActionOptions(array $state): array
    {
        $modalUrl = Router::url('newsletter_audience_selection_modal', ['partial' => 1, 'step' => 'existing']);

        return [
            'create_action' => [
                'mode' => 'htmx',
                'attrs' => [
                    'command' => 'show-modal',
                    'commandfor' => 'config-modal',
                    'data-title' => (string)\mh_lng('MAILBEEZ_NEWSLETTER_ITEM_MODAL_NEW_TITLE', 'New Newsletter Item'),
                    'data-size' => '5xl',
                    'hx-get' => $modalUrl . '&date={date}',
                    'hx-target' => '#config-modal-body',
                    'hx-swap' => 'innerHTML',
                    'hx-push-url' => 'false',
                    'data-no-global-loader' => 'true',
                ],
            ],
            'event_action' => [
                'mode' => 'link',
                'attrs' => [],
            ],
            // Keep top-right "Add event" button as direct navigation in calendar shell.
            'toolbar_create_url' => (string)$state['create_base_url'],
        ];
    }

    /**
     * @param array<string,mixed> $state
     * @param array<int,array<string,mixed>> $events
     * @param array<string,mixed> $options
     */
    public static function render(array $state, array $events, array $options = []): string
    {
        $options = self::resolveRenderOptions($state, $options);
        $toolbarCreateUrl = (string)($options['toolbar_create_url'] ?? $state['create_base_url']);
        $cfg = [
            'view' => (string)$state['view'],
            'filter' => (string)($state['filter'] ?? 'all'),
            'anchorDate' => self::isoDate($state['anchor']),
            'todayDate' => self::isoDate($state['now'] instanceof DateTimeImmutable ? $state['now'] : new DateTimeImmutable('now')),
            'endpointUrl' => (string)$state['endpoint_url'],
            'pageUrl' => (string)$state['page_url'],
            'createBaseUrl' => $toolbarCreateUrl,
            'adaptive' => (bool)($options['adaptive'] ?? true),
            'locale' => (string)($state['locale'] ?? self::resolveLocaleCode()),
            'weekLabelPrefix' => mh_lng('UI_CALENDAR_WEEK', 'Week'),
            'viewLabels' => [
                'week' => mh_lng('UI_CALENDAR_VIEW_WEEK', 'Week'),
                'month' => mh_lng('UI_CALENDAR_VIEW_MONTH', 'Month'),
                'quarter' => mh_lng('UI_CALENDAR_VIEW_3_MONTHS', '3 Months'),
                'year' => mh_lng('UI_CALENDAR_VIEW_YEAR', 'Year'),
            ],
            'payload' => self::buildPayload($state, $events, $options),
            'svgs' => [
                'plus' => Base::SVG_PLUS,
                'chevronLeft' => Base::SVG_CHEVRON_LEFT,
                'chevronRight' => Base::SVG_CHEVRON_RIGHT,
            ],
            'labels' => [
                'events' => mh_lng('UI_CALENDAR_EVENTS', 'events'),
                'add_newsletter' => mh_lng('UI_CALENDAR_ADD_NEWSLETTER', 'Add Newsletter'),
                'past_date' => mh_lng('UI_CALENDAR_PAST_DATE', 'Past Date'),
            ]
        ];

        $xData = Base::h(json_encode($cfg, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), false);

        $filterPills = FilterPills::render(['newsletter', 'automations'], [
            'all_value' => 'all',
            'labels_map' => [
                'all' => mh_lng('UI_CALENDAR_FILTER_ALL', 'All'),
                'newsletter' => mh_lng('UI_CALENDAR_FILTER_NEWSLETTER', 'Newsletter'),
                'automations' => mh_lng('UI_CALENDAR_FILTER_AUTOMATIONS', 'Automations'),
            ],
            'button_opts' => [
                'size' => 'sm',
            ]
        ]);

        $navPrev = Button::render('', [
            'variant' => 'ghost',
            'size' => 'md',
            'icon' => Base::SVG_CHEVRON_LEFT,
            'attrs' => [
                '@click' => 'shift(-1)',
                'aria-label' => mh_lng('UI_CALENDAR_PREVIOUS', 'Previous'),
            ],
        ]);

        $navToday = Button::render(mh_lng('UI_CALENDAR_TODAY', 'Today'), [
            'variant' => 'ghost',
            'size' => 'md',
            'attrs' => [
                '@click' => 'setToday()',
            ],
        ]);

        $navNext = Button::render('', [
            'variant' => 'ghost',
            'size' => 'md',
            'icon' => Base::SVG_CHEVRON_RIGHT,
            'attrs' => [
                '@click' => 'shift(1)',
                'aria-label' => mh_lng('UI_CALENDAR_NEXT', 'Next'),
            ],
        ]);

        $refreshButton = Button::render('', [
            'variant' => 'ghost',
            'size' => 'md',
            'icon' => Base::SVG_REFRESH,
            'attrs' => [
                '@click' => 'reload()',
                'aria-label' => mh_lng('UI_CALENDAR_REFRESH', 'Refresh'),
                ':class' => "isLoading ? 'animate-spin' : ''",
            ],
        ]);

        $viewGroup = TabsGroup::render([
            [
                'label' => mh_lng('UI_CALENDAR_VIEW_WEEK', 'Week'),
                'value' => 'week',
                'click' => "setView('week')",
                'class' => 'min-w-20',
            ],
            [
                'label' => mh_lng('UI_CALENDAR_VIEW_MONTH', 'Month'),
                'value' => 'month',
                'click' => "setView('month')",
                'class' => 'min-w-20',
            ],
            [
                'label' => mh_lng('UI_CALENDAR_VIEW_3_MONTHS', '3 Months'),
                'value' => 'quarter',
                'click' => "setView('quarter')",
                'class' => 'min-w-24',
            ],
            [
                'label' => mh_lng('UI_CALENDAR_VIEW_YEAR', 'Year'),
                'value' => 'year',
                'click' => "setView('year')",
                'class' => 'min-w-20',
            ],
        ], [
            'model' => 'view',
            'size' => 'sm',
            'class' => 'ring-divider',
            'attrs' => [
                'aria-label' => mh_lng('UI_CALENDAR_VIEW_MODE', 'Calendar view mode'),
            ]
        ]);
/*

        $addEventButton = Button::render([
            'label' => mh_lng('UI_CALENDAR_ADD_EVENT', 'Add event'),
            'variant' => 'primary',
            'size' => 'md',
            'as' => 'a',
            'href' => '#',
            'icon' => Base::SVG_PLUS,
            'iconPosition' => 'front',
            'attrs' => [
                ':href' => 'createUrl(anchorDate)',
            ],
        ]);
*/
        ob_start();
        ?>
        <div class="cal-shell" x-cloak
             data-no-global-loader="true"
             x-data="calendarShell(<?= $xData; ?>)"
             @automation:newsletter_item:updated.window="reload(false)"
             @automation:newsletter_item:created.window="reload(false)"
             @automation:newsletter_item:deleted.window="reload(false)"
             @automation:newsletter_item:expandable:refresh.window="reload(false)"
             @automation:newsletter_item:collections:refresh.window="reload(false)"
             @automation:timeframe:updated.window="reload(false)"
             @automation:campaigns:refresh.window="reload(false)"
             @automation:campaigns:updated.window="reload(false)"
             @automation:campaigns:created.window="reload(false)"
             @automation:campaigns:deleted.window="reload(false)"
             @newsletter:newsletter_item:updated.window="reload(false)"
             @newsletter:newsletter_item:created.window="reload(false)"
             @newsletter:newsletter_item:deleted.window="reload(false)"
             @newsletter:newsletter_item:expandable:refresh.window="reload(false)"
             @newsletter:newsletter_item:collections:refresh.window="reload(false)"
             @newsletter:timeframe:updated.window="reload(false)"
             @newsletter:campaigns:refresh.window="reload(false)"
             @newsletter:campaigns:updated.window="reload(false)"
             @newsletter:campaigns:created.window="reload(false)"
             @newsletter:campaigns:deleted.window="reload(false)"
        >
            <div class="cal-toolbar">
                <template x-teleport="#calendar-title-target">
                    <span x-init="[...$el.parentElement.childNodes].forEach(n => n !== $el && n.remove())" x-text="headerTitle()">
                        <div class="bg-muted rounded-full w-28 h-6 my-1"></div>
                    </span>
                </template>

                <template x-teleport="#calendar-view-mode-target">
                    <?= $viewGroup; ?>
                </template>

                <template x-teleport="#calendar-right-target">
                    <?= $refreshButton; ?>
                </template>
                <div class="flex items-center  gap-1 h-8!">
                    <?= $filterPills; ?>
                    <div class="flex-1"></div>
                    <div class="flex items-center justify-end gap-1" aria-label="<?= mh_lng('UI_CALENDAR_NAVIGATION', 'Calendar navigation') ?>" role="group">
                        <?= $navToday; ?>
                        <?= $navPrev; ?>
                        <?= $navNext; ?>
                    </div>
                </div>
            </div>
            <div id="calendar-body"
                 x-ref="body"
                 class="cal-body flex flex-col"
                 :class="isLoading ? 'cal-body--loading' : ''"
                 :style="adaptive ? { height: bodyHeight } : {}">
                <?= self::renderSkeletons(); ?>

                <div x-show="!isLoading && payload" class="flex-1 flex flex-col">
                    <?= self::renderAlpineTemplates(); ?>
                </div>

                <?= self::renderLegend($options['footer_right'] ?? ''); ?>
            </div>
        </div>

        <script>
            (function () {
                if (window.calendarShell) {
                    return;
                }

                function toDate(iso) {
                    return new Date(iso + 'T00:00:00');
                }

                function toIso(d) {
                    const year = d.getFullYear();
                    const month = String(d.getMonth() + 1).padStart(2, '0');
                    const day = String(d.getDate()).padStart(2, '0');
                    return `${year}-${month}-${day}`;
                }

                function startOfWeek(d) {
                    const date = new Date(d.getFullYear(), d.getMonth(), d.getDate());
                    const day = date.getDay() || 7;
                    date.setDate(date.getDate() - day + 1);
                    return date;
                }

                function endOfWeek(d) {
                    const date = startOfWeek(d);
                    date.setDate(date.getDate() + 6);
                    return date;
                }

                window.calendarShell = function (cfg) {
                    return {
                        view: cfg.view || 'month',
                        filter: cfg.filter || 'all',
                        isLoading: false,
                        adaptive: cfg.adaptive || false,
                        bodyHeight: 'auto',
                        _loadingTimer: null,
                        _reloadTimerDebounce: null,
                        _resizeHandler: null,
                        _visibilityHandler: null,
                        _visibilityObserver: null,
                        _heightFrame: null,
                        _heightRetryTimer: null,
                        anchorDate: cfg.anchorDate,
                        todayDate: cfg.todayDate,
                        endpointUrl: cfg.endpointUrl,
                        pageUrl: cfg.pageUrl,
                        createBaseUrl: cfg.createBaseUrl,
                        locale: cfg.locale || (window.APP_LOCALE || 'en'),
                        weekLabelPrefix: cfg.weekLabelPrefix || 'Week',
                        payload: cfg.payload || null,
                        svgs: cfg.svgs || {},
                        labels: cfg.labels || {},
                        init() {
                            if (this.adaptive) {
                                this.setupAdaptiveHeight();
                                this.$watch('view', () => this.scheduleHeightUpdate());
                            }

                            const reproc = () => {
                                this.$nextTick(() => {
                                    if (window.htmx && typeof window.htmx.process === 'function') {
                                        window.htmx.process(this.$el);
                                    }
                                });
                            };
                            this.$watch('payload', reproc);
                            this.$watch('view', reproc);

                            reproc();
                        },
                        destroy() {
                            if (this._resizeHandler) {
                                window.removeEventListener('resize', this._resizeHandler);
                                window.removeEventListener('orientationchange', this._resizeHandler);
                                window.removeEventListener('pageshow', this._resizeHandler);
                                window.removeEventListener('focus', this._resizeHandler);
                            }
                            if (this._visibilityHandler) {
                                document.removeEventListener('htmx:afterSwap', this._visibilityHandler, true);
                                document.removeEventListener('htmx:afterSettle', this._visibilityHandler, true);
                                document.removeEventListener('htmx:load', this._visibilityHandler, true);
                                window.removeEventListener('elements:ready', this._visibilityHandler);
                            }
                            if (this._visibilityObserver) {
                                this._visibilityObserver.disconnect();
                                this._visibilityObserver = null;
                            }
                            if (this._heightFrame) {
                                cancelAnimationFrame(this._heightFrame);
                                this._heightFrame = null;
                            }
                            if (this._heightRetryTimer) {
                                clearTimeout(this._heightRetryTimer);
                                this._heightRetryTimer = null;
                            }
                        },
                        setupAdaptiveHeight() {
                            this._resizeHandler = () => this.scheduleHeightUpdate();
                            this._visibilityHandler = () => this.scheduleHeightUpdate();

                            window.addEventListener('resize', this._resizeHandler);
                            window.addEventListener('orientationchange', this._resizeHandler);
                            window.addEventListener('pageshow', this._resizeHandler);
                            window.addEventListener('focus', this._resizeHandler);
                            document.addEventListener('htmx:afterSwap', this._visibilityHandler, true);
                            document.addEventListener('htmx:afterSettle', this._visibilityHandler, true);
                            document.addEventListener('htmx:load', this._visibilityHandler, true);
                            window.addEventListener('elements:ready', this._visibilityHandler);
                            this.observeVisibilityContext();
                            this.scheduleHeightUpdate();
                        },
                        observeVisibilityContext() {
                            if (!window.MutationObserver || !this.$root) return;

                            const observer = new MutationObserver(() => this.scheduleHeightUpdate());
                            let node = this.$root;
                            while (node && node.nodeType === 1) {
                                observer.observe(node, {
                                    attributes: true,
                                    attributeFilter: ['class', 'style', 'hidden', 'open', 'aria-hidden']
                                });
                                if (node === document.body) break;
                                node = node.parentElement;
                            }
                            this._visibilityObserver = observer;
                        },
                        scheduleHeightUpdate() {
                            if (!this.adaptive) return;

                            if (this._heightFrame) {
                                cancelAnimationFrame(this._heightFrame);
                            }
                            this._heightFrame = requestAnimationFrame(() => {
                                this._heightFrame = requestAnimationFrame(() => {
                                    this._heightFrame = null;
                                    this.updateHeight();
                                });
                            });

                            if (this._heightRetryTimer) {
                                clearTimeout(this._heightRetryTimer);
                            }
                            this._heightRetryTimer = setTimeout(() => {
                                this._heightRetryTimer = null;
                                this.updateHeight();
                            }, 180);
                        },
                        isVisible() {
                            if (!this.$root || !this.$root.isConnected) return false;
                            if (!this.$root.getClientRects().length) return false;

                            let node = this.$root;
                            while (node && node.nodeType === 1) {
                                if (node.hidden || node.getAttribute('aria-hidden') === 'true') return false;
                                const styles = window.getComputedStyle(node);
                                if (styles.display === 'none' || styles.visibility === 'hidden') return false;
                                if (node === document.body) break;
                                node = node.parentElement;
                            }
                            return true;
                        },
                        updateHeight() {
                            if (!this.adaptive || (this.view !== 'month' && this.view !== 'week')) {
                                this.bodyHeight = 'auto';
                                return;
                            }
                            this.$nextTick(() => {
                                if (!this.isVisible()) {
                                    this.bodyHeight = 'auto';
                                    return;
                                }
                                const body = this.$refs.body;
                                if (!body) return;
                                const rect = body.getBoundingClientRect();
                                const vh = window.innerHeight;
                                const padding = 20;
                                const h = vh - rect.top - padding;
                                this.bodyHeight = h > 300 ? h + 'px' : 'auto';
                            });
                        },
                        viewOptions: [
                            {value: 'week', label: (cfg.viewLabels && cfg.viewLabels.week) ? cfg.viewLabels.week : 'Week'},
                            {value: 'month', label: (cfg.viewLabels && cfg.viewLabels.month) ? cfg.viewLabels.month : 'Month'},
                            {value: 'quarter', label: (cfg.viewLabels && cfg.viewLabels.quarter) ? cfg.viewLabels.quarter : '3 Months'},
                            {value: 'year', label: (cfg.viewLabels && cfg.viewLabels.year) ? cfg.viewLabels.year : 'Year'}
                        ],
                        localDate(dateValue, options) {
                            return dateValue.toLocaleDateString(this.locale || undefined, options || undefined);
                        },
                        isFilter(v) {
                            return this.filter === v;
                        },
                        setFilter(v) {
                            this.filter = v;
                            window.dispatchEvent(new CustomEvent('calendar-filter-changed', { detail: { filter: v } }));
                            this.reload();
                        },
                        shift(direction) {
                            const d = toDate(this.anchorDate);
                            if (this.view === 'year') {
                                d.setFullYear(d.getFullYear() + direction);
                                d.setMonth(0, 1);
                            } else if (this.view === 'quarter') {
                                d.setMonth(d.getMonth() + direction, 1);
                            } else if (this.view === 'week') {
                                d.setDate(d.getDate() + (direction * 7));
                            } else {
                                d.setMonth(d.getMonth() + direction, 1);
                            }
                            this.anchorDate = toIso(d);
                            this.reload();
                        },
                        setToday() {
                            this.anchorDate = this.todayDate;
                            this.reload();
                        },
                        setView(next) {
                            this.view = next;
                            const d = toDate(this.anchorDate);
                            if (next === 'week') {
                                this.anchorDate = toIso(startOfWeek(d));
                            } else if (next === 'year') {
                                d.setMonth(0, 1);
                                this.anchorDate = toIso(d);
                            } else if (next === 'month' || next === 'quarter') {
                                d.setDate(1);
                                this.anchorDate = toIso(d);
                            }
                            this.reload();
                        },
                        openMonth(isoDate) {
                            this.view = 'month';
                            this.anchorDate = isoDate;
                            this.reload();
                        },
                        openWeek(isoDate) {
                            this.view = 'week';
                            this.anchorDate = toIso(startOfWeek(toDate(isoDate)));
                            this.reload();
                        },
                        createUrl(isoDate) {
                            return this.createBaseUrl + '&demo_create_date=' + encodeURIComponent(isoDate);
                        },
                        resolveAction(action, context) {
                            if (!action) return {};
                            const attrs = { ...(action.attrs || {}) };

                            // Replace tokens in all attributes
                            for (const key in attrs) {
                                if (typeof attrs[key] === 'string') {
                                    attrs[key] = attrs[key].replace(/\{(\w+)\}/g, (m, p1) => {
                                        return context[p1] !== undefined ? context[p1] : m;
                                    });
                                }
                            }

                            if (action.mode === 'htmx') {
                                if (!attrs.type) attrs.type = 'button';
                            } else {
                                let href = action.href_template || '';
                                if (!href && context.url) href = context.url;
                                if (href) {
                                    attrs.href = href.replace(/\{(\w+)\}/g, (m, p1) => {
                                        return context[p1] !== undefined ? context[p1] : m;
                                    });
                                }
                            }
                            return attrs;
                        },
                        reload(updateHistory) {
                            if (updateHistory === undefined || updateHistory instanceof Event) {
                                updateHistory = true;
                            }
                            if (this._reloadTimerDebounce) clearTimeout(this._reloadTimerDebounce);
                            this._reloadTimerDebounce = setTimeout(() => {
                                this._actualReload(updateHistory);
                            }, 50);
                        },
                        _actualReload(updateHistory) {
                            if (updateHistory === undefined) {
                                updateHistory = true;
                            }
                            const url = this.endpointUrl + '&view=' + encodeURIComponent(this.view) + '&date=' + encodeURIComponent(this.anchorDate) + '&filter=' + encodeURIComponent(this.filter) + '&format=json';

                            if (this._loadingTimer) {
                                clearTimeout(this._loadingTimer);
                            }

                            this.isLoading = true;
                            this.scheduleHeightUpdate();

                            fetch(url)
                                .then(res => res.json())
                                .then(data => {
                                    if (this._loadingTimer) {
                                        clearTimeout(this._loadingTimer);
                                        this._loadingTimer = null;
                                    }
                                    this.payload = data;
                                    this.isLoading = false;
                                    this.scheduleHeightUpdate();
                                })
                                .catch(err => {
                                    console.error('Calendar reload failed:', err);
                                    this.isLoading = false;
                                });

                            if (updateHistory && window.history && window.history.replaceState) {
                                // Only update history if the calendar is currently visible to the user
                                const isVisible = !!(this.$root && (this.$root.offsetWidth || this.$root.offsetHeight || this.$root.getClientRects().length));
                                if (isVisible) {
                                    window.history.replaceState({}, '', this.pageUrl);
                                }
                            }
                        },
                        headerTitle() {
                            if (this.payload && this.payload.title) return this.payload.title;
                            const d = toDate(this.anchorDate);
                            if (this.view === 'year') {
                                return String(d.getFullYear());
                            }
                            if (this.view === 'quarter') {
                                const end = new Date(d.getFullYear(), d.getMonth() + 2, 1);
                                return this.localDate(d, {month: 'short', year: 'numeric'}) + ' - ' + this.localDate(end, {
                                    month: 'short',
                                    year: 'numeric'
                                });
                            }
                            if (this.view === 'week') {
                                const start = startOfWeek(d);
                                const end = endOfWeek(d);
                                return this.weekLabel()

                                    /*
                                    + ': ' + this.localDate(start, {
                                    month: 'short',
                                    day: 'numeric'
                                })
                                    + ' - ' + this.localDate(end, {
                                    month: 'short',
                                    day: 'numeric',
                                    year: 'numeric'
                                })
                                */

                                    ;
                            }
                            return this.localDate(d, {month: 'long', year: 'numeric'});
                        },
                        headerSubtitle() {
                            if (this.payload && this.payload.subtitle) return this.payload.subtitle;
                            const d = toDate(this.anchorDate);
                            if (this.view === 'year') {
                                const start = new Date(d.getFullYear(), 0, 1);
                                const end = new Date(d.getFullYear(), 11, 31);
                                return this.localDate(start) + ' - ' + this.localDate(end);
                            }
                            if (this.view === 'quarter') {
                                const start = new Date(d.getFullYear(), d.getMonth(), 1);
                                const end = new Date(d.getFullYear(), d.getMonth() + 3, 0);
                                return this.localDate(start) + ' - ' + this.localDate(end);
                            }
                            if (this.view === 'week') {
                                const start = startOfWeek(d);
                                const end = endOfWeek(d);
                                return this.localDate(start) + ' - ' + this.localDate(end);
                            }
                            const start = new Date(d.getFullYear(), d.getMonth(), 1);
                            const end = new Date(d.getFullYear(), d.getMonth() + 1, 0);
                            return this.localDate(start) + ' - ' + this.localDate(end);
                        },
                        weekLabel() {
                            const d = toDate(this.anchorDate);
                            const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
                            const dayNum = date.getUTCDay() || 7;
                            date.setUTCDate(date.getUTCDate() + 4 - dayNum);
                            const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
                            const weekNo = Math.ceil((((date - yearStart) / 86400000) + 1) / 7);
                            return this.weekLabelPrefix + ' ' + weekNo;
                        },
                        locate(event) {
                            if (!event || !event.open_key) return;
                            const params = {};
                            let group = '';

                            if (event.newsletter_campaign_id > 0) {
                                group = 'campaign-' + event.newsletter_campaign_id;
                                params.group = group;
                            }

                            const rootId = event.open_key.split(':').pop();
                            params.open_id = rootId;

                            if (event.active === 'trigger') {
                                params.open_module = rootId;
                                params.page_mode = 'automation';
                                params['<?= \Components\Router::subtabParam() ?>'] = 'custom';
                            } else {
                                params.page_mode = 'newsletter';
                            }

                            const detail = {
                                active: event.active || 'newsletter',
                                params: params,
                                openKey: event.open_key,
                                group: group,
                                type: event.type
                            };
                            window.dispatchEvent(new CustomEvent('mb:navigate', { detail: detail }));
                        }
                    }
                }
            })();
        </script>
        <?php
        return (string)ob_get_clean();
    }

    /**
     * @param array<string,mixed> $state
     * @param array<int,array<string,mixed>>|null $events
     * @param array<string,mixed> $options
     */
    public static function buildPayload(array $state, ?array $events = null, array $options = []): array
    {
        $events = $events ?? self::sampleData($state);
        $options = self::resolveRenderOptions($state, $options);

        // Prepare campaign identity map (reusing logic from renderBody)
        $campaignIdentityMap = [];
        if (self::canLoadNewsletterItemModel()) {
            try {
                $raw = \Components\Models\NewsletterItem::campaignOptions();
                foreach ($raw as $cid => $opt) {
                    if (!(is_int($cid) || (is_string($cid) && ctype_digit($cid)))) continue;
                    $cidInt = (int)$cid;
                    if ($cidInt <= 0) continue;
                    $campaignIdentityMap[$cidInt] = [
                        'label'     => (string)($opt['label'] ?? ('Campaign ' . $cidInt)),
                        'dot_class' => (string)($opt['dot_class'] ?? 'bg-gray-400'),
                    ];
                }
            } catch (\Throwable $e) {}
        }
        if (empty($campaignIdentityMap)) {
            $campaignIdentityMap[1] = ['label' => 'Spring 26', 'dot_class' => 'bg-green-500'];
            $campaignIdentityMap[2] = ['label' => 'VIP Booster', 'dot_class' => 'bg-purple-500'];
        }
        $options['campaign_identity_map'] = $campaignIdentityMap;

        $view = (string)$state['view'];
        $payload = [
            'view' => $view,
            'title' => self::buildTitle($view, $state['anchor']),
            'subtitle' => self::rangeLabel($state['range_start'], $state['range_end']),
            'create_action' => $options['create_action'],
            'event_action' => $options['event_action'],
        ];

        if ($view === self::VIEW_WEEK) {
            $payload['data'] = self::getWeekData($state, $events, $options);
        } elseif ($view === self::VIEW_MONTH) {
            $payload['data'] = self::getMonthData($state, $events, $options);
        } elseif ($view === self::VIEW_QUARTER) {
            $payload['data'] = self::getQuarterData($state, $events, $options);
        } elseif ($view === self::VIEW_YEAR) {
            $payload['data'] = self::getYearData($state, $events, $options);
        } else {
            $payload['data'] = [];
        }

        return $payload;
    }

    private static function getMonthData(array $state, array $events, array $options): array
    {
        $monthStart = $state['anchor'];
        $monthEnd = $monthStart->modify('last day of this month')->setTime(23, 59, 59);

        $gridStart = self::startOfWeek($monthStart);
        $gridEnd = self::endOfWeek($monthEnd);

        $eventsInGrid = self::eventsInRange($events, $gridStart, $gridEnd);
        $weeks = [];
        $cursor = $gridStart;
        while ($cursor <= $gridEnd) {
            $weekStart = $cursor;
            $weekEnd = $weekStart->add(new DateInterval('P6D'))->setTime(23, 59, 59);
            $weekLanes = self::buildWeekLanes($eventsInGrid, $weekStart, $weekEnd);
            $laneSlots = self::buildWeekLaneSlots($weekLanes);
            $days = [];

            for ($i = 0; $i < 7; $i++) {
                $day = $weekStart->add(new DateInterval('P' . $i . 'D'));
                $days[] = [
                    'iso' => self::isoDate($day),
                    'day' => (int)$day->format('j'),
                    'in_month' => $day->format('m') === $monthStart->format('m'),
                    'is_today' => self::isoDate($day) === self::isoDate($state['now']),
                    'is_past' => self::isoDate($day) < self::isoDate($state['now']),
                    'lane_slots' => self::cleanLaneSlots($laneSlots[$i + 1] ?? [], $options, $weekStart, $weekEnd),
                ];
            }

            $weeks[] = [
                'start_iso' => self::isoDate($weekStart),
                'end_iso' => self::isoDate($weekEnd),
                'week_number' => (int)$weekStart->format('W'),
                'days' => $days,
            ];

            $cursor = $cursor->add(new DateInterval('P7D'));
        }

        return [
            'weeks' => $weeks,
            'day_names' => self::weekDayNamesShort(),
        ];
    }

    private static function getWeekData(array $state, array $events, array $options): array
    {
        $weekStart = self::startOfWeek($state['anchor']);
        $weekEnd = self::endOfWeek($weekStart);
        $eventsInWeek = self::eventsInRange($events, $weekStart, $weekEnd);
        $weekLanes = self::buildWeekLanes($eventsInWeek, $weekStart, $weekEnd);
        $laneSlots = self::buildWeekLaneSlots($weekLanes);
        $weekDays = self::weekDayNamesShort();

        $days = [];
        for ($i = 0; $i < 7; $i++) {
            $day = $weekStart->add(new DateInterval('P' . $i . 'D'));
            $days[] = [
                'iso' => self::isoDate($day),
                'day' => (int)$day->format('j'),
                'weekday' => $weekDays[$i] ?? '',
                'is_today' => self::isoDate($day) === self::isoDate($state['now']),
                'is_past' => self::isoDate($day) < self::isoDate($state['now']),
                'events_count' => count(self::eventsForDay($eventsInWeek, $day)),
                'lane_slots' => self::cleanLaneSlots($laneSlots[$i + 1] ?? [], $options, $weekStart, $weekEnd),
            ];
        }

        return [
            'days' => $days,
            'lane_count' => count($weekLanes),
            'min_height_rem' => 6 + (max(1, count($weekLanes)) * 2.75),
        ];
    }

    private static function cleanLaneSlots(array $slots, array $options, DateTimeImmutable $rangeStart, DateTimeImmutable $rangeEnd): array
    {
        $clean = [];
        foreach ($slots as $slot) {
            $segment = $slot['segment'];
            $evt = $segment['event'];

            $cleanEvt = $evt;
            if ($evt['start'] instanceof DateTimeImmutable) {
                $cleanEvt['start_iso'] = self::isoDate($evt['start']);
                $cleanEvt['start_time'] = self::eventTimeLabel($evt['start']);
            }
            if ($evt['end'] instanceof DateTimeImmutable) {
                $cleanEvt['end_iso'] = self::isoDate($evt['end']);
            }
            unset($cleanEvt['start'], $cleanEvt['end']);

            // Add status classes
            $status = (string)$evt['status'];
            $cleanEvt['status_dot_class'] = self::statusDotClass($status);
            $cleanEvt['status_bar_class'] = self::statusBarClass($status);
            $cleanEvt['status_badge_class'] = self::statusBadgeClass($status);

            // Add schedule type UI
            if ($stUi = self::resolveScheduleTypeUi($evt)) {
                $cleanEvt['schedule_type_ui'] = $stUi;
            }

            // Add identity
            $campaignId = (int)($evt['newsletter_campaign_id'] ?? 0);
            $campaignMap = $options['campaign_identity_map'] ?? [];
            if ($campaignId > 0 && isset($campaignMap[$campaignId])) {
                $cleanEvt['identity'] = $campaignMap[$campaignId];
            }

            $cleanEvt['continues_before'] = $evt['start'] instanceof DateTimeImmutable && $evt['start'] < $rangeStart;
            $cleanEvt['continues_after'] = $evt['end'] instanceof DateTimeImmutable && $evt['end'] > $rangeEnd;

            $clean[] = [
                'type' => $slot['type'],
                'segment' => [
                    'span' => $segment['span'],
                    'event' => $cleanEvt
                ]
            ];
        }
        return $clean;
    }

    private static function getQuarterData(array $state, array $events, array $options): array
    {
        $months = [];
        for ($i = 0; $i < 3; $i++) {
            $month = $state['anchor']->add(new DateInterval('P' . $i . 'M'));
            $months[] = self::getMiniMonthData($month, $events, true, $options, $state['now']);
        }
        return ['months' => $months];
    }

    private static function getYearData(array $state, array $events, array $options): array
    {
        $months = [];
        $year = (int)$state['anchor']->format('Y');
        for ($i = 0; $i < 12; $i++) {
            $month = $state['anchor']->setDate($year, $i + 1, 1);
            $months[] = self::getMiniMonthData($month, $events, false, $options, $state['now']);
        }
        return ['months' => $months];
    }

    private static function getMiniMonthData(DateTimeImmutable $month, array $events, bool $showStats, array $options, DateTimeImmutable $now): array
    {
        $monthStart = $month->modify('first day of this month')->setTime(0, 0, 0);
        $monthEnd = $monthStart->modify('last day of this month')->setTime(23, 59, 59);
        $eventsInMonth = self::eventsInRange($events, $monthStart, $monthEnd);

        $counts = ['completed' => 0, 'active' => 0, 'scheduled' => 0];
        foreach ($eventsInMonth as $event) {
            $status = self::normalizeStatus((string)$event['status']);
            if (isset($counts[$status])) $counts[$status]++;
        }

        $gridStart = self::startOfWeek($monthStart);
        $gridEnd = self::endOfWeek($monthEnd);
        $days = [];
        $cursor = $gridStart;
        while ($cursor <= $gridEnd) {
            $dayEvents = self::eventsForDay($eventsInMonth, $cursor);
            $inMonth = $cursor->format('m') === $monthStart->format('m');
            $dayStatuses = [];
            foreach ($dayEvents as $evt) {
                $s = self::normalizeStatus((string)$evt['status']);
                if (!in_array($s, $dayStatuses, true)) $dayStatuses[] = $s;
            }
            usort($dayStatuses, static function($a, $b) {
                $prio = ['active' => 1, 'scheduled' => 2, 'completed' => 3];
                return ($prio[$a] ?? 99) <=> ($prio[$b] ?? 99);
            });

            $days[] = [
                'iso' => self::isoDate($cursor),
                'day' => (int)$cursor->format('j'),
                'in_month' => $inMonth,
                'is_today' => $inMonth && self::isoDate($cursor) === self::isoDate($now),
                'is_past' => self::isoDate($cursor) < self::isoDate($now),
                'statuses' => $dayStatuses
            ];
            $cursor = $cursor->add(new DateInterval('P1D'));
        }

        return [
            'name' => self::monthName($monthStart),
            'iso' => self::isoDate($monthStart),
            'events_count' => count($eventsInMonth),
            'days' => $days,
            'counts' => $counts,
            'show_stats' => $showStats,
            'day_letters' => self::weekDayLettersVeryShort()
        ];
    }

    public static function renderLegend(string $footerRight = ''): string
    {
        $defs = self::smartCollectionDefs();
        $legendMap = [
            'completed' => 'previous',
            'active'    => 'active',
            'scheduled' => 'scheduled',
        ];
        ob_start();
        ?>
        <div class="cal-legend mt-4 flex items-center justify-between w-full">
            <div class="flex flex-wrap items-center gap-2">
                <?php foreach ($legendMap as $defKey => $statusKey):
                    if (!isset($defs[$defKey])) continue;
                    $palette = self::statusPalette($statusKey);
                    $label = mh_lng('UI_CALENDAR_STATUS_' . strtoupper($statusKey), $statusKey);
                ?>
                    <span class="cal-badge <?= Base::h($palette['badge']); ?>">
                        <span class="cal-dot <?= Base::h($palette['dot']); ?>"></span>
                        <?= Base::h($label); ?>
                    </span>
                <?php endforeach; ?>
            </div>
            <?php if ($footerRight): ?>
                <div class="flex items-center">
                    <?= $footerRight; ?>
                </div>
            <?php endif; ?>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    private static function renderSkeletons(): string
    {
        ob_start();
        ?>
        <div x-show="isLoading || !payload" class="flex-1 flex flex-col">
            <template x-if="view === 'month'">
                <?= self::renderMonthSkeleton(); ?>
            </template>
            <template x-if="view === 'week'">
                <?= self::renderWeekSkeleton(); ?>
            </template>
            <template x-if="view === 'quarter' || view === 'year'">
                <?= self::renderGridSkeleton(); ?>
            </template>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    private static function renderMonthSkeleton(): string
    {
        ob_start();
        ?>
        <div class="cal-grid h-full flex flex-col animate-pulse">
            <div class="cal-month-header-row" style="grid-template-rows: 2rem;">
                <?php for ($i = 0; $i < 7; $i++): ?>
                    <div class="cal-header-cell border-b-0! <?= $i === 6 ? '' : 'border-r border-divider'; ?>">
                        <div class="h-4 w-8 bg-muted rounded mx-auto"></div>
                    </div>
                <?php endfor; ?>
            </div>
            <div class="flex-1 flex flex-col min-h-0">
                <?php for ($w = 0; $w < 5; $w++): ?>
                    <div class="cal-month-week">
                        <div class="cal-month-week-grid">
                            <?php for ($d = 0; $d < 7; $d++): ?>
                                <div class="cal-cell <?= $d === 6 ? '' : 'border-r border-divider'; ?>">
                                    <div class="cal-month-cell-head">
                                        <div class="flex-1"></div>
                                        <div class="h-6 w-6 bg-muted rounded-full"></div>
                                    </div>
                                    <div class="cal-month-events p-1">
                                        <div class="h-6 w-3/4 bg-muted rounded"></div>
                                        <div class="h-6 w-1/2 bg-muted rounded"></div>
                                    </div>
                                </div>
                            <?php endfor; ?>
                        </div>
                    </div>
                <?php endfor; ?>
            </div>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    private static function renderWeekSkeleton(): string
    {
        ob_start();
        ?>
        <div class="cal-week-view flex-1 min-h-0 overflow-x-auto animate-pulse">
            <div class="cal-grid flex h-full min-h-full flex-col" style="min-width: 56rem;">
                <div class="grid grid-cols-7 border-b border-divider shrink-0">
                    <?php for ($i = 0; $i < 7; $i++): ?>
                        <div class="cal-week-header flex min-h-16 flex-col gap-1 bg-canvas px-3 py-2 <?= $i === 6 ? '' : 'border-r border-divider'; ?>">
                            <div class="h-3 w-12 bg-muted rounded"></div>
                            <div class="flex items-center justify-between gap-2">
                                <div class="h-8 w-8 bg-muted rounded-full"></div>
                                <div class="h-4 w-16 bg-muted rounded"></div>
                            </div>
                        </div>
                    <?php endfor; ?>
                </div>
                <div class="grid flex-1 grid-cols-7">
                    <?php for ($i = 0; $i < 7; $i++): ?>
                        <div class="cal-week-day cal-cell <?= $i === 6 ? '' : 'border-r border-divider'; ?> p-2 space-y-2">
                            <div class="h-12 w-full bg-muted rounded"></div>
                            <div class="h-12 w-5/6 bg-muted rounded"></div>
                            <div class="h-12 w-4/6 bg-muted rounded"></div>
                        </div>
                    <?php endfor; ?>
                </div>
            </div>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    private static function renderGridSkeleton(): string
    {
        ob_start();
        ?>
        <div class="min-h-80 overflow-y-auto  animate-pulse">
            <div class="mx-auto grid grid-cols-1 gap-4 md:grid-cols-3 h-full">
                <?php for ($m = 0; $m < 3; $m++): ?>
                    <div class="cal-mini-card h-80">
                        <div class="cal-mini-titlebar">
                            <div class="h-5 w-24 bg-muted rounded"></div>
                            <div class="h-4 w-16 bg-muted rounded"></div>
                        </div>
                        <div class="p-4 space-y-4">
                            <div class="grid grid-cols-7 gap-2">
                                <?php for ($i = 0; $i < 7; $i++): ?>
                                    <div class="h-3 w-3 bg-muted rounded mx-auto"></div>
                                <?php endfor; ?>
                            </div>
                            <div class="grid grid-cols-7 gap-2 gap-y-4">
                                <?php for ($i = 0; $i < 35; $i++): ?>
                                    <div class="h-4 w-6 bg-muted rounded mx-auto"></div>
                                <?php endfor; ?>
                            </div>
                        </div>
                    </div>
                <?php endfor; ?>
            </div>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    private static function renderAlpineTemplates(): string
    {
        ob_start();
        ?>
        <!-- Alpine Month View -->
        <template x-if="payload && view === 'month'">
            <div class="cal-grid h-full flex flex-col">
                <div class="cal-month-header-row" style="grid-template-rows: 2rem;">
                    <template x-for="(wd, index) in payload.data.day_names">
                        <div class="cal-header-cell border-b-0!" :class="index === 6 ? '' : ' border-r border-divider'" x-text="wd"></div>
                    </template>
                </div>
                <div class="flex-1 flex flex-col min-h-0 overflow-y-auto">
                    <template x-for="(week, weekIndex) in payload.data.weeks" :key="week.start_iso">
                        <div class="cal-month-week">
                            <div class="cal-month-week-grid">
                                <template x-for="(day, dayIndex) in week.days" :key="day.iso">
                                    <div class="cal-cell group" :class="[
                                        day.in_month ? 'text-secondary' : 'cal-cell-muted',
                                        day.is_past ? 'cal-cell-past' : '',
                                        dayIndex === 6 ? '' : ' border-r border-divider'
                                    ]" :style="'z-index: ' + (7 - dayIndex)">
                                        <div class="cal-month-cell-head">
                                            <template x-if="dayIndex === 0">
                                                <button type="button" class="cal-week-badge" @click="openWeek(day.iso)" x-text="week.week_number"></button>
                                            </template>
                                            <div class="flex-1"></div>
                                            <button type="button"
                                                    class="cal-day-number"
                                                    :class="day.is_today ? 'bg-brand text-white' : 'text-secondary' + (day.in_month ? '' : ' opacity-50')"
                                                    @click="openWeek(day.iso)"
                                                    x-text="day.day"></button>
                                        </div>

                                        <div class="cal-month-events">
                                            <template x-for="slot in day.lane_slots">
                                                <div :class="[
                                                    'cal-month-event-slot h-6',
                                                    slot.type === 'placeholder' ? 'pointer-events-none invisible' : '',
                                                    (slot.type === 'start' && (slot.segment.span > 1 || slot.segment.event.continues_before || slot.segment.event.continues_after)) ? 'relative z-[1]' : ''
                                                ]" :style="(slot.type === 'start' && slot.segment.span > 1) ? 'width: calc(' + (slot.segment.span * 100) + '% + (' + ((slot.segment.span - 1) * 4) + ' * var(--spacing)) + ' + (slot.segment.span - 1) + 'px)' : ''">
                                                    <template x-if="slot.type === 'start'">
                                                        <div class="cal-event-wrapper relative h-full">
                                                            <template x-if="payload.event_action.mode === 'htmx'">
                                                                <button x-bind="resolveAction(payload.event_action, slot.segment.event)" 
                                                                        @click.stop.prevent="locate(slot.segment.event)"
                                                                        class="cal-event h-full" 
                                                                        :class="[
                                                                            slot.segment.event.status_bar_class,
                                                                            slot.segment.event.continues_before ? 'pl-7' : '',
                                                                            slot.segment.event.continues_after ? 'pr-7' : ''
                                                                        ]">
                                                                    <div class="cal-event-inner !justify-start">
                                                                        <template x-if="slot.segment.event.identity">
                                                                            <div class="cal-event-identity-base cal-event-identity--month">
                                                                                <span class="inline-flex shrink-0 items-center justify-center size-4"><span :class="slot.segment.event.identity.dot_class" class="block shrink-0 size-2 rounded-full"></span></span>
                                                                                <span class="truncate" x-text="slot.segment.event.identity.label"></span>
                                                                            </div>
                                                                        </template>
                                                                        <template x-if="slot.segment.event.schedule_type_ui">
                                                                            <div class="cal-event-type-base cal-event-type--month">
                                                                                <span class="inline-flex shrink-0 items-center justify-center size-4 rounded-sm [&_svg]:size-3 [&_svg]:stroke-2" :class="slot.segment.event.schedule_type_ui.bg_class" x-html="slot.segment.event.schedule_type_ui.icon"></span>
                                                                            </div>
                                                                        </template>
                                                                        <span class="flex-1 truncate" x-text="slot.segment.event.title"></span>
                                                                        <template x-if="slot.type === 'start' && slot.segment.span === 1 && slot.segment.event.start_time">
                                                                            <time class="text-xs shrink-0" x-text="slot.segment.event.start_time"></time>
                                                                        </template>
                                                                    </div>
                                                                </button>
                                                            </template>
                                                            <template x-if="payload.event_action.mode !== 'htmx'">
                                                                <a x-bind="resolveAction(payload.event_action, slot.segment.event)" 
                                                                   @click.stop.prevent="locate(slot.segment.event)"
                                                                   class="cal-event h-full" 
                                                                   :class="[
                                                                       slot.segment.event.status_bar_class,
                                                                       slot.segment.event.continues_before ? 'pl-7' : '',
                                                                       slot.segment.event.continues_after ? 'pr-7' : ''
                                                                   ]">
                                                                    <div class="cal-event-inner !justify-start">
                                                                        <template x-if="slot.segment.event.identity">
                                                                            <div class="cal-event-identity-base cal-event-identity--month">
                                                                                <span class="inline-flex shrink-0 items-center justify-center size-4"><span :class="slot.segment.event.identity.dot_class" class="block shrink-0 size-2 rounded-full"></span></span>
                                                                                <span class="truncate" x-text="slot.segment.event.identity.label"></span>
                                                                            </div>
                                                                        </template>
                                                                        <template x-if="slot.segment.event.schedule_type_ui">
                                                                            <div class="cal-event-type-base cal-event-type--month">
                                                                                <span class="inline-flex shrink-0 items-center justify-center size-4 rounded-sm [&_svg]:size-3 [&_svg]:stroke-2" :class="slot.segment.event.schedule_type_ui.bg_class" x-html="slot.segment.event.schedule_type_ui.icon"></span>
                                                                            </div>
                                                                        </template>
                                                                        <span class="flex-1 truncate" x-text="slot.segment.event.title"></span>
                                                                        <template x-if="slot.type === 'start' && slot.segment.span === 1 && slot.segment.event.start_time">
                                                                            <time class="text-xs shrink-0" x-text="slot.segment.event.start_time"></time>
                                                                        </template>
                                                                    </div>
                                                                </a>
                                                            </template>

                                                            <!-- Indicators -->
                                                            <template x-if="slot.segment.event.continues_before && weekIndex === 0">
                                                                <button type="button" class="cal-week-continuation size-4! [&_svg]:size-3 [&_svg]:stroke-2 left-1" @click.stop.prevent="shift(-1)" x-html="svgs.chevronLeft"></button>
                                                            </template>
                                                            <template x-if="slot.segment.event.continues_before && weekIndex > 0">
                                                                <div class="cal-week-indicator left-1 [&_svg]:size-3 [&_svg]:stroke-2" x-html="svgs.chevronRight"></div>
                                                            </template>
                                                            <template x-if="slot.segment.event.continues_after && weekIndex === payload.data.weeks.length - 1">
                                                                <button type="button" class="cal-week-continuation size-4! [&_svg]:size-3 [&_svg]:stroke-2 right-1" @click.stop.prevent="shift(1)" x-html="svgs.chevronRight"></button>
                                                            </template>
                                                            <template x-if="slot.segment.event.continues_after && weekIndex < payload.data.weeks.length - 1">
                                                                <div class="cal-week-indicator right-1 [&_svg]:size-3 [&_svg]:stroke-2" x-html="svgs.chevronRight"></div>
                                                            </template>
                                                        </div>
                                                    </template>
                                                </div>
                                            </template>
                                            <template x-if="day.in_month && filter !== 'automations' && !day.is_past">
                                                <template x-if="payload.create_action.mode === 'htmx'">
                                                    <button x-bind="resolveAction(payload.create_action, {date: day.iso, url: createUrl(day.iso)})" class="cal-event-add">
                                                        <span x-html="svgs.plus"  class="[&_svg]:size-4"></span>
                                                        <span class="ml-1 truncate" x-text="labels.add_newsletter"></span>
                                                    </button>
                                                </template>
                                                <template x-if="payload.create_action.mode !== 'htmx'">
                                                    <a x-bind="resolveAction(payload.create_action, {date: day.iso, url: createUrl(day.iso)})" class="cal-event-add">
                                                        <span x-html="svgs.plus" class="[&_svg]:size-4"></span>
                                                        <span class="ml-1 truncate" x-text="labels.add_newsletter"></span>
                                                    </a>
                                                </template>
                                            </template>
                                            <template x-if="day.in_month && filter !== 'automations' && day.is_past">
                                                <div class="cal-event-past">
                                                    <span class="truncate" x-text="labels.past_date"></span>
                                                </div>
                                            </template>
                                        </div>
                                    </div>
                                </template>
                            </div>
                        </div>
                    </template>
                </div>
            </div>
        </template>

        <!-- Alpine Week View -->
        <template x-if="payload && view === 'week'">
            <div class="cal-week-view flex-1 min-h-0 overflow-x-auto" :style="'min-height: ' + payload.data.min_height_rem + 'rem'">
                <div class="cal-grid flex h-full min-h-full flex-col" style="min-width: 56rem;">
                    <div class="grid grid-cols-7 border-b border-divider shrink-0">
                        <template x-for="(day, dayIndex) in payload.data.days" :key="day.iso">
                            <div class="cal-week-header flex min-h-16 flex-col gap-1 bg-canvas px-3 py-2" :class="dayIndex === 6 ? '' : 'border-r border-divider'">
                                <span class="text-xs font-medium uppercase tracking-wide text-secondary/70" x-text="day.weekday"></span>
                                <div class="flex items-center justify-between gap-2">
                                    <button type="button"
                                            class="cal-day-number"
                                            :class="day.is_today ? 'bg-brand text-white' : 'text-secondary'"
                                            @click="openMonth(day.iso)"
                                            :aria-current="day.is_today ? 'date' : false"
                                            x-text="day.day">
                                    </button>
                                    <span class="text-xs text-secondary/70">
                                        <span x-text="day.events_count"></span>
                                        <span x-text="labels.events"></span>
                                    </span>
                                </div>
                            </div>
                        </template>
                    </div>

                    <div class="grid flex-1 grid-cols-7">
                        <template x-for="(day, dayIndex) in payload.data.days" :key="day.iso">
                            <div class="cal-week-day cal-cell group text-secondary" 
                                 :class="[
                                    dayIndex === 6 ? '' : 'border-r border-divider',
                                    day.is_past ? 'cal-cell-past' : ''
                                 ]"
                                 :style="'z-index: ' + (7 - dayIndex)">
                                <div class="flex flex-col gap-1.5">
                                    <template x-for="slot in day.lane_slots">
                                        <div :class="[
                                            'h-12',
                                            slot.type === 'placeholder' ? 'pointer-events-none invisible' : '',
                                            (slot.type === 'start' && (slot.segment.span > 1 || slot.segment.event.continues_before || slot.segment.event.continues_after)) ? 'relative z-[1]' : ''
                                        ]" :style="(slot.type === 'start' && slot.segment.span > 1) ? 'width: calc(' + (slot.segment.span * 100) + '% + (' + ((slot.segment.span - 1) * 4) + ' * var(--spacing)) + ' + (slot.segment.span - 1) + 'px)' : ''">
                                            <div class="cal-event-wrapper relative h-full">
                                                <template x-if="payload.event_action.mode === 'htmx'">
                                                    <button x-bind="resolveAction(payload.event_action, slot.segment.event)"
                                                            @click.stop.prevent="locate(slot.segment.event)"
                                                            class="cal-event h-full min-h-12 !items-start py-1.5"
                                                            :class="[
                                                                slot.segment.event.status_bar_class,
                                                                slot.segment.event.continues_before ? 'pl-7' : '',
                                                                slot.segment.event.continues_after ? 'pr-7' : ''
                                                            ]">
                                                        <div class="flex flex-col gap-1 min-w-0">
                                                            <div class="flex w-full items-center justify-start gap-1">
                                                                <template x-if="slot.segment.event.identity">
                                                                    <div class="cal-event-identity-base max-w-36 px-1 pr-2 py-0 h-4 rounded-full border border-divider/60 bg-muted/20 text-[10px] leading-tight text-secondary">
                                                                        <span class="inline-flex shrink-0 items-center justify-center size-4"><span :class="slot.segment.event.identity.dot_class" class="block shrink-0 size-2 rounded-full"></span></span>
                                                                        <span class="truncate" x-text="slot.segment.event.identity.label"></span>
                                                                    </div>
                                                                </template>
                                                                <template x-if="slot.segment.event.schedule_type_ui">
                                                                    <div class="cal-event-type-base cal-event-type--month">
                                                                        <span class="inline-flex shrink-0 items-center justify-center size-4 rounded-sm [&_svg]:size-3 [&_svg]:stroke-2" :class="slot.segment.event.schedule_type_ui.bg_class" x-html="slot.segment.event.schedule_type_ui.icon"></span>
                                                                    </div>
                                                                </template>
                                                                <div class="flex-1"></div>
                                                                <template x-if="slot.type === 'start' && slot.segment.event.start_time">
                                                                    <time class="text-xs shrink-0" x-text="slot.segment.event.start_time"></time>
                                                                </template>
                                                            </div>
                                                            <span class="truncate text-left" x-text="slot.segment.event.title"></span>
                                                        </div>
                                                    </button>
                                                </template>
                                                <template x-if="payload.event_action.mode !== 'htmx'">
                                                    <a x-bind="resolveAction(payload.event_action, slot.segment.event)"
                                                       @click.stop.prevent="locate(slot.segment.event)"
                                                       class="cal-event h-full min-h-12 !items-start py-1.5"
                                                       :class="[
                                                            slot.segment.event.status_bar_class,
                                                            slot.segment.event.continues_before ? 'pl-7' : '',
                                                            slot.segment.event.continues_after ? 'pr-7' : ''
                                                       ]">
                                                        <div class="flex flex-col gap-1 min-w-0">
                                                            <div class="flex w-full items-center justify-start gap-1">
                                                                <template x-if="slot.segment.event.identity">
                                                                    <div class="cal-event-identity-base max-w-36 px-1 pr-2 py-0 h-4 rounded-full border border-divider/60 bg-muted/20 text-[10px] leading-tight text-secondary">
                                                                        <span class="inline-flex shrink-0 items-center justify-center size-4"><span :class="slot.segment.event.identity.dot_class" class="block shrink-0 size-2 rounded-full"></span></span>
                                                                        <span class="truncate" x-text="slot.segment.event.identity.label"></span>
                                                                    </div>
                                                                </template>
                                                                <template x-if="slot.segment.event.schedule_type_ui">
                                                                    <div class="cal-event-type-base cal-event-type--month">
                                                                        <span class="inline-flex shrink-0 items-center justify-center size-4 rounded-sm [&_svg]:size-3 [&_svg]:stroke-2" :class="slot.segment.event.schedule_type_ui.bg_class" x-html="slot.segment.event.schedule_type_ui.icon"></span>
                                                                    </div>
                                                                </template>
                                                                <div class="flex-1"></div>
                                                                <template x-if="slot.type === 'start' && slot.segment.event.start_time">
                                                                    <time class="text-xs shrink-0" x-text="slot.segment.event.start_time"></time>
                                                                </template>
                                                            </div>
                                                            <span class="truncate text-left" x-text="slot.segment.event.title"></span>
                                                        </div>
                                                    </a>
                                                </template>

                                                <template x-if="slot.type === 'start' && slot.segment.event.continues_before">
                                                    <button type="button" class="cal-week-continuation left-1" @click.stop.prevent="shift(-1)" x-html="svgs.chevronLeft"></button>
                                                </template>
                                                <template x-if="slot.type === 'start' && slot.segment.event.continues_after">
                                                    <button type="button" class="cal-week-continuation right-1" @click.stop.prevent="shift(1)" x-html="svgs.chevronRight"></button>
                                                </template>
                                            </div>
                                        </div>
                                    </template>
                                    <template x-if="filter !== 'automations' && !day.is_past">
                                        <template x-if="payload.create_action.mode === 'htmx'">
                                            <button x-bind="resolveAction(payload.create_action, {date: day.iso, url: createUrl(day.iso)})" class="cal-event-add">
                                                <span x-html="svgs.plus" class="[&_svg]:size-4"></span>
                                                <span class="ml-1 truncate" x-text="labels.add_newsletter"></span>
                                            </button>
                                        </template>
                                        <template x-if="payload.create_action.mode !== 'htmx'">
                                            <a x-bind="resolveAction(payload.create_action, {date: day.iso, url: createUrl(day.iso)})" class="cal-event-add">
                                                <span x-html="svgs.plus" class="[&_svg]:size-4"></span>
                                                <span class="ml-1 truncate" x-text="labels.add_newsletter"></span>
                                            </a>
                                        </template>
                                    </template>
                                    <template x-if="filter !== 'automations' && day.is_past">
                                        <div class="cal-event-past">
                                            <span class="truncate" x-text="labels.past_date"></span>
                                        </div>
                                    </template>
                                </div>
                            </div>
                        </template>
                    </div>
                </div>
            </div>
        </template>

        <!-- Alpine Quarter/Year View -->
        <template x-if="payload && (view === 'quarter' || view === 'year')">
            <div :class="view === 'quarter' ? 'cal-quarter-view h-full overflow-y-auto ' : 'cal-year-view h-full overflow-y-auto '">
                <div :class="view === 'quarter' ? 'mx-auto grid grid-cols-1 gap-4 md:grid-cols-3' : 'mx-auto grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4'">
                    <template x-for="month in payload.data.months" :key="month.iso">
                        <article class="cal-mini-card">
                            <div class="cal-mini-titlebar">
                                <button type="button" class="cal-mini-title" @click="openMonth(month.iso)" x-text="month.name"></button>
                                <span class="cal-mini-count">
                                    <span x-text="month.events_count"></span>
                                    <span x-text="labels.events"></span>
                                </span>
                            </div>
                            <div class="cal-mini-header">
                                <template x-for="wd in month.day_letters">
                                    <div class="text-center" x-text="wd"></div>
                                </template>
                            </div>
                            <div class="cal-mini-grid">
                                <template x-for="day in month.days" :key="day.iso">
                                    <div class="cal-mini-cell" :class="[
                                        day.in_month ? 'cal-mini-cell--current' : 'cal-mini-cell--muted',
                                        day.is_past ? 'cal-mini-cell--past' : ''
                                    ]">
                                        <div class="cal-mini-cell-inner">
                                            <button type="button"
                                                    class="cal-mini-day"
                                                    :class="day.is_today ? 'bg-brand text-white font-semibold' : ''"
                                                    @click="openWeek(day.iso)"
                                                    :aria-current="day.is_today ? 'date' : false"
                                                    x-text="day.day"></button>

                                            <div class="flex gap-0.5 mt-0.5 justify-center">
                                                <template x-for="status in day.statuses">
                                                    <div class="cal-mini-dot" :class="status === 'scheduled' ? 'bg-blue-600/70' : 'cal-mini-dot--' + status"></div>
                                                </template>
                                            </div>
                                        </div>
                                    </div>
                                </template>
                            </div>
                            <template x-if="month.show_stats">
                                <div class="cal-mini-stats">
                                    <template x-for="status in ['completed', 'active', 'scheduled']">
                                        <div class="cal-mini-stat" :class="'cal-mini-stat--' + status" x-text="month.counts[status]"></div>
                                    </template>
                                </div>
                            </template>
                        </article>
                    </template>
                </div>
            </div>
        </template>
        <?php
        return (string)ob_get_clean();
    }

    public static function renderBody(array $state, ?array $events = null, array $options = []): string
    {
        $events = $events ?? self::sampleData($state);
        $options = self::resolveRenderOptions($state, $options);

        // Get shared smart collection definitions
        $defs = self::smartCollectionDefs();

        // Get campaign identity map for smart collections (name + dot color)
        $campaignIdentityMap = [];
        if (self::canLoadNewsletterItemModel()) {
            try {
                $raw = \Components\Models\NewsletterItem::campaignOptions();
                foreach ($raw as $cid => $opt) {
                    if (!(is_int($cid) || (is_string($cid) && ctype_digit($cid)))) {
                        continue;
                    }
                    if (!is_array($opt)) {
                        continue;
                    }
                    $cidInt = (int)$cid;
                    if ($cidInt <= 0) {
                        continue;
                    }
                    $campaignIdentityMap[$cidInt] = [
                        'label'     => (string)($opt['label'] ?? ('Campaign ' . $cidInt)),
                        'dot_class' => (string)($opt['dot_class'] ?? 'bg-gray-400'),
                    ];
                }
            } catch (\Throwable $e) {
                $campaignIdentityMap = [];
            }
        }

        // For demo purposes, ensure we have some campaign identities if map is empty
        if (empty($campaignIdentityMap)) {
            $campaignIdentityMap[1] = ['label' => 'Spring 26', 'dot_class' => 'bg-green-500'];
            $campaignIdentityMap[2] = ['label' => 'VIP Booster', 'dot_class' => 'bg-purple-500'];
        }

        $options['campaign_identity_map'] = $campaignIdentityMap;

        ob_start();
        ?>
            <?php
            $view = (string)$state['view'];
            if ($view === self::VIEW_WEEK) {
                echo self::renderWeekView($state, $events, $options);
            } elseif ($view === self::VIEW_QUARTER) {
                echo self::renderQuarterView($state, $events, $options);
            } elseif ($view === self::VIEW_YEAR) {
                echo self::renderYearView($state, $events, $options);
            } else {
                echo self::renderMonthView($state, $events, $options);
            }
            ?>

        <?= self::renderLegend($options['footer_right'] ?? ''); ?>
        <?php
        return (string)ob_get_clean();
    }

    /**
     * @param array<string,mixed> $state
     * @param array<int,array<string,mixed>> $events
     * @param array<string,mixed> $options
     */
    private static function renderWeekView(array $state, array $events, array $options): string
    {
        $weekStart = self::startOfWeek($state['anchor']);
        $weekEnd = self::endOfWeek($weekStart);
        $eventsInWeek = self::eventsInRange($events, $weekStart, $weekEnd);
        $weekLanes = self::buildWeekLanes($eventsInWeek, $weekStart, $weekEnd);
        $laneSlots = self::buildWeekLaneSlots($weekLanes);
        $weekDays = self::weekDayNamesShort();
        $laneCount = max(1, count($weekLanes));
        $minHeightRem = 6 + ($laneCount * 3.5);
        $days = [];

        for ($i = 0; $i < 7; $i++) {
            $day = $weekStart->add(new DateInterval('P' . $i . 'D'));
            $days[] = [
                'date' => $day,
                'iso' => self::isoDate($day),
                'day' => (int)$day->format('j'),
                'weekday' => $weekDays[$i] ?? '',
                'is_today' => self::isoDate($day) === self::isoDate($state['now']),
                'is_past' => self::isoDate($day) < self::isoDate($state['now']),
                'events' => self::eventsForDay($eventsInWeek, $day),
                'lane_slots' => $laneSlots[$i + 1] ?? [],
            ];
        }

        ob_start();
        ?>
        <div class="cal-week-view flex-1 min-h-0 overflow-x-auto" style="min-height: <?= Base::h((string)$minHeightRem); ?>rem;">
            <div class="cal-grid flex h-full min-h-full flex-col" style="min-width: 56rem;">
                <div class="grid grid-cols-7 border-b border-divider shrink-0">
                    <?php foreach ($days as $dayIndex => $day): ?>
                        <?php $headerBorderClass = $dayIndex === 6 ? '' : 'border-r border-divider'; ?>
                        <div class="cal-week-header <?= Base::h($headerBorderClass); ?> flex min-h-16 flex-col gap-1 bg-canvas px-3 py-2">
                            <span class="text-xs font-medium uppercase tracking-wide text-secondary/70">
                                <?= Base::h((string)$day['weekday']); ?>
                            </span>
                            <div class="flex items-center justify-between gap-2">
                                <button type="button"
                                        class="cal-day-number <?= $day['is_today'] ? 'bg-brand text-white' : 'text-secondary'; ?>"
                                        @click="openMonth('<?= Base::h((string)$day['iso']); ?>')"
                                        <?= $day['is_today'] ? 'aria-current="date"' : ''; ?>>
                                    <?= Base::h((string)$day['day']); ?>
                                </button>
                                <span class="text-xs text-secondary/70">
                                    <?= Base::h((string)count((array)$day['events'])); ?>
                                    <?= Base::h(mh_lng('UI_CALENDAR_EVENTS', 'events')); ?>
                                </span>
                            </div>
                        </div>
                    <?php endforeach; ?>
                </div>

                <div class="grid flex-1 grid-cols-7">
                    <?php foreach ($days as $dayIndex => $day): ?>
                        <?php
                        $isLastCol = $dayIndex === 6;
                        $borderClass = $isLastCol ? '' : 'border-r border-divider';
                        $createAction = self::resolveAction((array)$options['create_action'], [
                            'date' => (string)$day['iso'],
                            'url' => self::createUrlForDay((string)$day['iso']),
                        ]);
                        $createTag = (string)$createAction['tag'];
                        $createAttrs = self::withClass((array)$createAction['attrs'], 'cal-add-button group');
                        $createAttrs['aria-label'] = 'Add newsletter on ' . (string)$day['iso'];
                        ?>
                        <div class="cal-week-day cal-cell group <?= Base::h($borderClass); ?> text-secondary <?= $day['is_past'] ? 'cal-cell-past' : ''; ?>" style="z-index: <?= 7 - $dayIndex; ?>;">
                            <div class="flex flex-col gap-1.5">
                                <?php foreach ((array)$day['lane_slots'] as $slot): ?>
                                    <?php
                                    $segment = $slot['segment'];
                                    $evt = $segment['event'];
                                    $status = (string)$evt['status'];
                                    $title = (string)$evt['title'];

                                    $campaignId = (int)($evt['newsletter_campaign_id'] ?? 0);
                                    $campaignMap = $options['campaign_identity_map'] ?? [];
                                    $identityHtml = '';
                                    $identityLabel = '';
                                    if ($campaignId > 0 && isset($campaignMap[$campaignId])) {
                                        $info = $campaignMap[$campaignId];
                                        $identityLabel = (string)$info['label'];
                                        $identityHtml =  \Components\Ui\CollectionIdentity::render(Base::h($identityLabel), $info['dot_class'], [
                                            'base_class' => 'cal-event-identity-base',
                                            'dot_size_class' => 'size-2',
                                            'size'       => '2xs',
                                            'class'      => 'cal-event-identity--month !max-w-none',
                                        ]);
                                    }

                                    $stUiHtml = self::renderScheduleTypeIcon(self::resolveScheduleTypeUi($evt));

                                    $dotClass = self::statusDotClass($status);
                                    $barClass = self::statusBarClass($status);
                                    $isStart = $slot['type'] === 'start';
                                    $isPlaceholder = $slot['type'] === 'placeholder';
                                    $span = max(1, (int)$segment['span']);
                                    $continuesBefore = $isStart && $evt['start'] instanceof DateTimeImmutable && $evt['start'] < $weekStart;
                                    $continuesAfter = $isStart && $evt['end'] instanceof DateTimeImmutable && $evt['end'] > $weekEnd;

                                    $eventAction = self::resolveAction((array)$options['event_action'], [
                                        'date' => self::isoDate($evt['start']),
                                        'url' => (string)($evt['edit_url'] ?? ''),
                                        'id' => (string)($evt['id'] ?? ''),
                                        'title' => $title,
                                    ]);
                                    $eventTag = (string)$eventAction['tag'];
                                    $eventClass = 'cal-event ' . $barClass . ' min-h-12 flex-col !items-stretch py-1.5';
                                    $eventClass .= $continuesBefore ? ' pl-7' : '';
                                    $eventClass .= $continuesAfter ? ' pr-7' : '';
                                    $eventAttrs = self::withClass((array)$eventAction['attrs'], $eventClass);
                                    $eventAttrs['@click.stop.prevent'] = 'locate(' . json_encode($evt) . ')';
                                    $hoverTitle = $identityLabel !== '' ? $identityLabel . ': ' . $title : $title;
                                    $eventAttrs['title'] = $hoverTitle;

                                    $entryClass = '';
                                    if ($isStart && ($span > 1 || $continuesBefore || $continuesAfter)) {
                                        $entryClass .= ' relative z-[1]';
                                    }
                                    if ($isPlaceholder) {
                                        $entryClass .= ' pointer-events-none invisible';
                                    }
                                    $entryAttrs = ['class' => trim($entryClass)];
                                    if ($isStart && $span > 1) {
                                        $entryAttrs['style'] = 'width: calc(' . $span . '00% + (' . (($span - 1) * 4) . ' * var(--spacing)) + ' . ($span - 1) . 'px);';
                                    }
                                    $previousWeekAttrs = [
                                        'type' => 'button',
                                        'class' => 'cal-week-continuation left-1',
                                        'aria-label' => mh_lng('UI_CALENDAR_PREVIOUS_WEEK', 'Previous week'),
                                        'title' => mh_lng('UI_CALENDAR_PREVIOUS_WEEK', 'Previous week'),
                                        '@click.stop.prevent' => 'shift(-1)',
                                        '@keydown.enter.stop.prevent' => 'shift(-1)',
                                        '@keydown.space.stop.prevent' => 'shift(-1)',
                                    ];
                                    $nextWeekAttrs = [
                                        'type' => 'button',
                                        'class' => 'cal-week-continuation right-1',
                                        'aria-label' => mh_lng('UI_CALENDAR_NEXT_WEEK', 'Next week'),
                                        'title' => mh_lng('UI_CALENDAR_NEXT_WEEK', 'Next week'),
                                        '@click.stop.prevent' => 'shift(1)',
                                        '@keydown.enter.stop.prevent' => 'shift(1)',
                                        '@keydown.space.stop.prevent' => 'shift(1)',
                                    ];
                                    ?>
                                    <div<?= Base::attrs($entryAttrs); ?>>
                                        <div class="cal-event-wrapper relative h-full">
                                            <div class="inline-flex size-2 items-center justify-center md:hidden">
                                                <span class="cal-dot <?= Base::h($dotClass); ?>"></span>
                                            </div>

                                            <?php if ($eventTag === 'button'): ?>
                                            <button <?= Base::attrs($eventAttrs); ?>>
                                            <?php else: ?>
                                            <a <?= Base::attrs($eventAttrs); ?>>
                                            <?php endif; ?>
                                                <div class="cal-event-inner">
                                                    <?php if ($identityHtml !== ''): ?>
                                                        <?= $identityHtml; ?>
                                                    <?php endif; ?>
                                                    <?php if ($stUiHtml !== ''): ?>
                                                        <?= $stUiHtml; ?>
                                                    <?php endif; ?>
                                                    <?php if ($isStart && ($timeLabel = self::eventTimeLabel($evt['start'])) !== ''): ?>
                                                        <time class="text-xs shrink-0"><?= Base::h($timeLabel); ?></time>
                                                    <?php endif; ?>
                                                </div>
                                                <div class="truncate text-left"><?= Base::h($title); ?></div>
                                            <?php if ($eventTag === 'button'): ?>
                                            </button>
                                            <?php else: ?>
                                            </a>
                                            <?php endif; ?>

                                            <?php if ($continuesBefore): ?>
                                                <button <?= Base::attrs($previousWeekAttrs); ?>>
                                                    <?= Base::SVG_CHEVRON_LEFT; ?>
                                                </button>
                                            <?php endif; ?>
                                            <?php if ($continuesAfter): ?>
                                                <button <?= Base::attrs($nextWeekAttrs); ?>>
                                                    <?= Base::SVG_CHEVRON_RIGHT; ?>
                                                </button>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                <?php endforeach; ?>

                                <?php if (($state['filter'] ?? 'all') !== 'automations' && !$day['is_past']): ?>
                                    <?php $createEventAttrs = self::withClass((array)$createAction['attrs'], 'cal-event-add'); ?>
                                    <?php if ($createTag === 'button'): ?>
                                        <button <?= Base::attrs($createEventAttrs); ?>><?= Base::SVG_PLUS; ?> <span class="ml-1 truncate"><?= Base::h(mh_lng('UI_CALENDAR_ADD_NEWSLETTER', 'Add Newsletter')); ?></span></button>
                                    <?php else: ?>
                                        <a <?= Base::attrs($createEventAttrs); ?>><?= Base::SVG_PLUS; ?> <span class="ml-1 truncate"><?= Base::h(mh_lng('UI_CALENDAR_ADD_NEWSLETTER', 'Add Newsletter')); ?></span></a>
                                    <?php endif; ?>
                                <?php elseif ($day['is_past'] && ($state['filter'] ?? 'all') !== 'automations'): ?>
                                    <div class="cal-event-past">
                                        <span class="truncate"><?= Base::h(mh_lng('UI_CALENDAR_PAST_DATE', 'Past Date')); ?></span>
                                    </div>
                                <?php endif; ?>
                            </div>
                        </div>
                    <?php endforeach; ?>
                </div>
            </div>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    /**
     * @param array<string,mixed> $state
     * @param array<int,array<string,mixed>> $events
     * @param array<string,mixed> $options
     */
    private static function renderMonthView(array $state, array $events, array $options): string
    {
        $monthStart = $state['anchor'];
        $monthEnd = $monthStart->modify('last day of this month')->setTime(23, 59, 59);

        $gridStart = self::startOfWeek($monthStart);
        $gridEnd = self::endOfWeek($monthEnd);

        $eventsInGrid = self::eventsInRange($events, $gridStart, $gridEnd);
        $weeks = [];
        $cursor = $gridStart;
        while ($cursor <= $gridEnd) {
            $weekStart = $cursor;
            $weekEnd = $weekStart->add(new DateInterval('P6D'))->setTime(23, 59, 59);
            $weekLanes = self::buildWeekLanes($eventsInGrid, $weekStart, $weekEnd);
            $laneSlots = self::buildWeekLaneSlots($weekLanes);
            $days = [];

            for ($i = 0; $i < 7; $i++) {
                $day = $weekStart->add(new DateInterval('P' . $i . 'D'));
                $dayEvents = self::eventsForDay($eventsInGrid, $day);
                $days[] = [
                    'date' => $day,
                    'iso' => self::isoDate($day),
                    'day' => (int)$day->format('j'),
                    'in_month' => $day->format('m') === $monthStart->format('m'),
                    'is_today' => self::isoDate($day) === self::isoDate($state['now']),
                    'is_past' => self::isoDate($day) < self::isoDate($state['now']),
                    'events' => $dayEvents,
                    'lane_slots' => $laneSlots[$i + 1] ?? [],
                ];
            }

            $weeks[] = [
                'start' => $weekStart,
                'end' => $weekEnd,
                'days' => $days,
            ];

            $cursor = $cursor->add(new DateInterval('P7D'));
        }

        $html = '<div class="cal-grid h-full flex flex-col"><div class="cal-month-header-row" style="grid-template-rows: 2rem;">';
        foreach (self::weekDayNamesShort() as $headerIndex => $wd) {
            $headerClass = 'cal-header-cell border-b-0!' . ($headerIndex === 6 ? '' : ' border-r border-divider');
            $html .= '<div class="' . Base::h($headerClass, false) . '">' . Base::h($wd, false) . '</div>';
        }
        $html .= '</div>';

        foreach ($weeks as $weekIdx => $week) {
            $html .= '<div class="cal-month-week"><div class="cal-month-week-grid">';
            foreach ($week['days'] as $dayIndex => $day) {
                $isLastCol = $dayIndex === 6;
                $isWeekStart = $dayIndex === 0;
                $cellClass = 'cal-cell group ' . ($day['in_month'] ? 'text-secondary' : 'cal-cell-muted');
                if ($day['is_past']) {
                    $cellClass .= ' cal-cell-past';
                }
                $cellClass .= $isLastCol ? '' : ' border-r border-divider';

                $createAction = self::resolveAction((array)$options['create_action'], [
                    'date' => (string)$day['iso'],
                    'url' => self::createUrlForDay((string)$day['iso']),
                ]);
                $createTag = (string)$createAction['tag'];
                $createAttrs = self::withClass((array)$createAction['attrs'], 'cal-add-button group');
                $createAttrs['aria-label'] = 'Add newsletter on ' . (string)$day['iso'];

                $html .= '<div class="' . Base::h(trim($cellClass), false) . '" style="z-index: ' . (7 - $dayIndex) . ';">';
                $html .= '<div class="cal-month-cell-head">';
                if ($isWeekStart) {
                    $html .= '<button' . Base::attrs([
                        'type' => 'button',
                        'class' => 'cal-week-badge',
                        '@click' => "openWeek('" . (string)$week['start']->format('Y-m-d') . "')",
                    ]) . '>' . Base::h((string)$day['date']->format('W'), false) . '</button>';
                }
                $dayNumberClass = 'cal-day-number ' . ($day['is_today'] ? 'bg-brand text-white' : 'text-secondary');
                $dayNumberClass .= $day['in_month'] ? '' : ' opacity-50';
                $html .= '<div class="flex-1"></div><button' . Base::attrs([
                    'type' => 'button',
                    'class' => $dayNumberClass,
                    '@click' => "openWeek('" . (string)$day['iso'] . "')",
                ]) . '>' . Base::h((string)$day['day'], false) . '</button></div>';

                $limit = (int)($options['events_limit'] ?? 10);
                $html .= '<div class="cal-month-events">';
                foreach (array_slice((array)$day['lane_slots'], 0, $limit) as $slot) {
                    $segment = $slot['segment'];
                    $evt = $segment['event'];
                    $status = (string)$evt['status'];
                    $title = (string)$evt['title'];
                    $campaignId = (int)($evt['newsletter_campaign_id'] ?? 0);
                    $campaignMap = $options['campaign_identity_map'] ?? [];
                    $identityHtml = '';
                    $identityLabel = '';

                    if ($campaignId > 0 && isset($campaignMap[$campaignId])) {
                        $info = $campaignMap[$campaignId];
                        $identityLabel = (string)$info['label'];
                        $identityHtml = \Components\Ui\CollectionIdentity::render(Base::h($identityLabel), $info['dot_class'], [
                            'base_class' => 'cal-event-identity-base',
                            'dot_size_class' => 'size-2',
                            'size' => '2xs',
                            'class' => 'cal-event-identity--month !max-w-none',
                        ]);
                    }

                    $stUiHtml = self::renderScheduleTypeIcon(self::resolveScheduleTypeUi($evt));

                    $dotClass = self::statusDotClass($status);
                    $barClass = self::statusBarClass($status);
                    $isStart = $slot['type'] === 'start';
                    $isPlaceholder = $slot['type'] === 'placeholder';
                    $span = max(1, (int)$segment['span']);

                    $continuesBefore = $isStart && $evt['start'] instanceof DateTimeImmutable && $evt['start'] < $week['start'];
                    $continuesAfter = $isStart && $evt['end'] instanceof DateTimeImmutable && $evt['end'] > $week['end'];
                    $isFirstWeek = $weekIdx === 0;
                    $isLastWeek = $weekIdx === count($weeks) - 1;

                    $eventAction = self::resolveAction((array)$options['event_action'], [
                        'date' => self::isoDate($evt['start']),
                        'url' => (string)($evt['edit_url'] ?? ''),
                        'id' => (string)($evt['id'] ?? ''),
                        'title' => $title,
                    ]);
                    $eventTag = (string)$eventAction['tag'];

                    $eventClass = 'cal-event ' . $barClass . ' h-full';
                    $eventClass .= $continuesBefore ? ' pl-7' : '';
                    $eventClass .= $continuesAfter ? ' pr-7' : '';

                    $eventAttrs = self::withClass((array)$eventAction['attrs'], $eventClass);
                    $eventAttrs['@click.stop.prevent'] = 'locate(' . json_encode($evt) . ')';
                    if ($isStart && $span > 1) {
                        $eventAttrs['style'] = 'width: calc(' . $span . '00% + (' . (($span - 1) * 4) . ' * var(--spacing)) + ' . ($span - 1) . 'px);';
                    }
                    $eventAttrs['title'] = $identityLabel !== '' ? $identityLabel . ': ' . $title : $title;

                    $entryClass = '';
                    $entryClass .= ($isStart && ($span > 1 || $continuesBefore || $continuesAfter)) ? ' relative z-[1]' : '';
                    $entryClass .= $isPlaceholder ? ' pointer-events-none invisible' : '';
                    $entryClass .= (!$day['in_month'] && !$isPlaceholder) ? ' opacity-50' : '';

                    $html .= '<div' . ($entryClass !== '' ? ' class="' . Base::h(trim($entryClass), false) . '"' : '') . '>';
                    if ($isStart) {
                        $html .= '<div class="cal-event-wrapper relative h-full">';
                        $html .= '<div class="cal-event-mobile-dot"><span class="cal-dot ' . Base::h($dotClass, false) . '"></span></div>';
                        $html .= $eventTag === 'button'
                            ? '<button' . Base::attrs($eventAttrs) . '>'
                            : '<a' . Base::attrs($eventAttrs) . '>';
                        $html .= '<div class="cal-event-inner !justify-start">' . $identityHtml . $stUiHtml . '<span class="flex-1 truncate">' . Base::h($title, false) . '</span>';
                        if ($span === 1 && ($timeLabel = self::eventTimeLabel($evt['start'])) !== '') {
                            $html .= '<time class="text-xs shrink-0">' . Base::h($timeLabel, false) . '</time>';
                        }
                        $html .= '</div>' . ($eventTag === 'button' ? '</button>' : '</a>');

                        if ($continuesBefore) {
                            if ($isFirstWeek) {
                                $html .= '<button type="button" class="cal-week-continuation left-1" @click.stop.prevent="shift(-1)">' . Base::SVG_CHEVRON_LEFT . '</button>';
                            } else {
                                $html .= '<div class="cal-week-indicator left-1">' . Base::SVG_CHEVRON_LEFT . '</div>';
                            }
                        }
                        if ($continuesAfter) {
                            if ($isLastWeek) {
                                $html .= '<button type="button" class="cal-week-continuation right-1" @click.stop.prevent="shift(1)">' . Base::SVG_CHEVRON_RIGHT . '</button>';
                            } else {
                                $html .= '<div class="cal-week-indicator right-1">' . Base::SVG_CHEVRON_RIGHT . '</div>';
                            }
                        }
                        $html .= '</div>';
                    }
                    $html .= '</div>';
                }

                $hiddenCount = count((array)$day['lane_slots']) - $limit;
                if ($hiddenCount > 0) {
                    $html .= '<div class="cal-more">+' . Base::h((string)$hiddenCount, false) . ' ' . Base::h(mh_lng('UI_CALENDAR_MORE', 'more'), false) . '</div>';
                }

                if ($day['in_month'] && ($state['filter'] ?? 'all') !== 'automations' && !$day['is_past']) {
                    $createEventAttrs = self::withClass((array)$createAction['attrs'], 'cal-event-add');
                    $html .= $createTag === 'button'
                        ? '<button' . Base::attrs($createEventAttrs) . '>' . Base::SVG_PLUS . ' <span class="ml-1 truncate">' . Base::h(mh_lng('UI_CALENDAR_ADD_NEWSLETTER', 'Add Newsletter')) . '</span></button>'
                        : '<a' . Base::attrs($createEventAttrs) . '>' . Base::SVG_PLUS . ' <span class="ml-1 truncate">' . Base::h(mh_lng('UI_CALENDAR_ADD_NEWSLETTER', 'Add Newsletter')) . '</span></a>';
                } elseif ($day['in_month'] && $day['is_past'] && ($state['filter'] ?? 'all') !== 'automations') {
                    $html .= '<div class="cal-event-past"><span class="truncate">' . Base::h(mh_lng('UI_CALENDAR_PAST_DATE', 'Past Date')) . '</span></div>';
                }

                $html .= '</div></div>';
            }
            $html .= '</div></div>';
        }

        return $html . '</div>';
    }

    /**
     * @param array<string,mixed> $state
     * @param array<int,array<string,mixed>> $events
     * @param array<string,mixed> $options
     */
    private static function renderQuarterView(array $state, array $events, array $options): string
    {
        $start = $state['anchor']->modify('first day of this month');
        $miniMonthOptions = $options;
        $miniMonthOptions['today_iso'] = self::isoDate($state['now']);
        $months = [];
        for ($i = 0; $i < 3; $i++) {
            $months[] = $start->add(new DateInterval('P' . $i . 'M'));
        }

        ob_start();
        ?>
        <div class="grid grid-cols-1 gap-3 lg:grid-cols-3">
            <?php foreach ($months as $month): ?>
                <?= self::renderMiniMonthCard($month, $events, false, $miniMonthOptions); ?>
            <?php endforeach; ?>
        </div>
        <?php
        return (string)ob_get_clean();
    }

    /**
     * @param array<string,mixed> $state
     * @param array<int,array<string,mixed>> $events
     * @param array<string,mixed> $options
     */
    private static function renderYearView(array $state, array $events, array $options): string
    {
        $start = $state['anchor']->setDate((int)$state['anchor']->format('Y'), 1, 1);
        $miniMonthOptions = $options;
        $miniMonthOptions['today_iso'] = self::isoDate($state['now']);

        ob_start();
        ?>
            <div class=" @container">

        <div class="grid grid-cols-1 gap-3 @sm:grid-cols-1 @md:grid-cols-2 @xl:grid-cols-3 @4xl:grid-cols-4 @7xl:grid-cols-6">
            <?php for ($i = 0; $i < 12; $i++): ?>
                <?php $month = $start->add(new DateInterval('P' . $i . 'M')); ?>
                <?= self::renderMiniMonthCard($month, $events, true, $miniMonthOptions); ?>
            <?php endfor; ?>
        </div>
            </div>
        <?php
        return (string)ob_get_clean();
    }

    /**
     * @param array<int,array<string,mixed>> $events
     * @param array<string,mixed> $options
     */
    private static function renderMiniMonthCard(DateTimeImmutable $month, array $events, bool $showStats = false, array $options = []): string
    {
        $monthStart = $month->modify('first day of this month')->setTime(0, 0, 0);
        $monthEnd = $monthStart->modify('last day of this month')->setTime(23, 59, 59);
        $todayIso = (string)($options['today_iso'] ?? '');
        $eventsInMonth = self::eventsInRange($events, $monthStart, $monthEnd);

        $counts = [
            'completed' => 0,
            'active' => 0,
            'scheduled' => 0,
        ];

        foreach ($eventsInMonth as $event) {
            $status = self::normalizeStatus((string)$event['status']);
            if (isset($counts[$status])) {
                $counts[$status]++;
            }
        }

        $gridStart = self::startOfWeek($monthStart);
        $gridEnd = self::endOfWeek($monthEnd);

        $html = '<article class="cal-mini-card">';
        $html .= '<div class="cal-mini-titlebar">';
        $html .= '<button' . Base::attrs([
            'type' => 'button',
            'class' => 'cal-mini-title',
            '@click' => "openMonth('" . self::isoDate($monthStart) . "')",
        ]) . '>' . Base::h(self::monthName($monthStart), false) . '</button>';
        $html .= '<span class="cal-mini-count">' . Base::h((string)count($eventsInMonth), false) . ' ' . Base::h(mh_lng('UI_CALENDAR_EVENTS', 'events'), false) . '</span>';
        $html .= '</div><div class="cal-mini-header">';

        foreach (self::weekDayLettersVeryShort() as $wd) {
            $html .= '<div class="text-center">' . Base::h($wd, false) . '</div>';
        }

        $html .= '</div><div class="cal-mini-grid">';
        $cursor = $gridStart;
        while ($cursor <= $gridEnd) {
            $dayEvents = self::eventsForDay($eventsInMonth, $cursor);
            $inMonth = $cursor->format('m') === $monthStart->format('m');
            $isToday = $inMonth && self::isoDate($cursor) === $todayIso;
            $isPast = $inMonth && self::isoDate($cursor) < $todayIso;
            $dayIso = self::isoDate($cursor);
            $cellClass = 'cal-mini-cell ' . ($inMonth ? 'cal-mini-cell--current' : 'cal-mini-cell--muted');
            if ($isPast) {
                $cellClass .= ' cal-mini-cell--past';
            }
            $dayClass = 'cal-mini-day' . ($isToday ? ' bg-brand text-white font-semibold' : '');
            $dayAttrs = [
                'type' => 'button',
                'class' => $dayClass,
                '@click' => "openWeek('" . $dayIso . "')",
                'aria-current' => $isToday ? 'date' : null,
            ];

            $html .= '<div class="' . $cellClass . '"><div class="cal-mini-cell-inner">';
            $html .= '<button' . Base::attrs($dayAttrs) . '>' . Base::h((string)$cursor->format('j'), false) . '</button>';

            if (!empty($dayEvents)) {
                $dayStatuses = [];
                foreach ($dayEvents as $evt) {
                    $s = self::normalizeStatus((string)$evt['status']);
                    if (!in_array($s, $dayStatuses, true)) {
                        $dayStatuses[] = $s;
                    }
                }
                usort($dayStatuses, static function($a, $b) {
                    $prio = ['active' => 1, 'scheduled' => 2, 'completed' => 3];
                    return ($prio[$a] ?? 99) <=> ($prio[$b] ?? 99);
                });
                foreach ($dayStatuses as $status) {
                    $dotClass = $status === 'scheduled' ? 'bg-blue-600/70' : 'cal-mini-dot--' . $status;
                    $html .= '<div class="cal-mini-dot ' . Base::h($dotClass, false) . '"></div>';
                }
            }

            $html .= '</div></div>';
            $cursor = $cursor->add(new DateInterval('P1D'));
        }
        $html .= '</div>';

        if ($showStats) {
            $html .= '<div class="cal-mini-stats">';
            foreach (['completed', 'active', 'scheduled'] as $status) {
                $html .= '<div class="cal-mini-stat cal-mini-stat--' . $status . '">' . Base::h((string)$counts[$status], false) . '</div>';
            }
            $html .= '</div>';
        }

        return $html . '</article>';
    }

    /**
     * @param array<int,array<string,mixed>> $events
     * @return array<int,array<string,mixed>>
     */
    private static function eventsInRange(array $events, DateTimeImmutable $rangeStart, DateTimeImmutable $rangeEnd): array
    {
        return array_values(array_filter($events, static function (array $event) use ($rangeStart, $rangeEnd): bool {
            /** @var DateTimeImmutable $eventStart */
            $eventStart = $event['start'];
            /** @var DateTimeImmutable $eventEnd */
            $eventEnd = $event['end'];

            return !($eventEnd < $rangeStart || $eventStart > $rangeEnd);
        }));
    }

    /**
     * @param array<int,array<string,mixed>> $events
     * @return array<int,array<string,mixed>>
     */
    private static function eventsForDay(array $events, DateTimeImmutable $day): array
    {
        $start = $day->setTime(0, 0, 0);
        $end = $day->setTime(23, 59, 59);

        return self::eventsInRange($events, $start, $end);
    }

    /**
     * @param array<int,array<string,mixed>> $events
     * @return array<int,array<int,array<string,mixed>>>
     */
    private static function buildWeekLanes(array $events, DateTimeImmutable $weekStart, DateTimeImmutable $weekEnd): array
    {
        $segments = [];
        foreach ($events as $event) {
            /** @var DateTimeImmutable $eventStart */
            $eventStart = $event['start'];
            /** @var DateTimeImmutable $eventEnd */
            $eventEnd = $event['end'];

            if ($eventEnd < $weekStart || $eventStart > $weekEnd) {
                continue;
            }

            $segmentStart = $eventStart > $weekStart ? $eventStart : $weekStart;
            $segmentEnd = $eventEnd < $weekEnd ? $eventEnd : $weekEnd;

            $startCol = ((int)$weekStart->diff($segmentStart->setTime(0, 0, 0))->format('%a')) + 1;
            $endCol = ((int)$weekStart->diff($segmentEnd->setTime(0, 0, 0))->format('%a')) + 1;
            $span = max(1, $endCol - $startCol + 1);

            $segments[] = [
                'event' => $event,
                'start_col' => $startCol,
                'span' => $span,
                'end_col' => $startCol + $span - 1,
            ];
        }

        usort($segments, static function (array $a, array $b): int {
            if ($a['start_col'] === $b['start_col']) {
                return $b['span'] <=> $a['span'];
            }
            return $a['start_col'] <=> $b['start_col'];
        });

        $lanes = [];
        foreach ($segments as $segment) {
            $placed = false;
            foreach ($lanes as &$lane) {
                $overlap = false;
                foreach ($lane as $existing) {
                    if (!($segment['end_col'] < $existing['start_col'] || $segment['start_col'] > $existing['end_col'])) {
                        $overlap = true;
                        break;
                    }
                }
                if (!$overlap) {
                    $lane[] = $segment;
                    $placed = true;
                    break;
                }
            }
            unset($lane);

            if (!$placed) {
                $lanes[] = [$segment];
            }
        }

        return $lanes;
    }

    /**
     * @param array<int,array<int,array<string,mixed>>> $lanes
     * @return array<int,array<int,array{type:string,segment:array<string,mixed>}>>
     */
    private static function buildWeekLaneSlots(array $lanes): array
    {
        $slots = [];
        for ($day = 1; $day <= 7; $day++) {
            $slots[$day] = [];
        }

        foreach ($lanes as $lane) {
            for ($day = 1; $day <= 7; $day++) {
                foreach ($lane as $segment) {
                    $startCol = (int)$segment['start_col'];
                    $endCol = (int)$segment['end_col'];
                    if ($day === $startCol) {
                        $slots[$day][] = [
                            'type' => 'start',
                            'segment' => $segment,
                        ];
                        continue 2;
                    }
                    if ($day > $startCol && $day <= $endCol) {
                        $slots[$day][] = [
                            'type' => 'placeholder',
                            'segment' => $segment,
                        ];
                        continue 2;
                    }
                }
            }
        }

        return $slots;
    }

    private static function eventTimeLabel(DateTimeImmutable $dateTime): string
    {
        if ($dateTime->format('H:i:s') === '00:00:00') {
            return '';
        }
        return $dateTime->format('g:i A');
    }

    /**
     * @param array<string,mixed> $state
     * @return array<int,array<string,mixed>>
     */
    private static function sampleEvents(array $state): array
    {
        /** @var DateTimeImmutable $now */
        $now = $state['now'];
        $tz = $state['tz'];
        $monthStart = $now->modify('first day of this month')->setTime(0, 0, 0);
        $filter = $state['filter'] ?? 'all';

        $seed = [
            ['id' => 101, 'newsletter_campaign_id' => 1, 'schedule_type' => 'bulk_mail', 'title' => 'Spring Reactivation Sequence', 'start' => $monthStart->modify('-12 days 09:00'), 'end' => $monthStart->modify('-9 days 18:00')],
            ['id' => 102, 'newsletter_campaign_id' => 2, 'schedule_type' => 'scheduled_window', 'title' => 'VIP Loyalty Booster', 'start' => $now->modify('-1 day 08:00'), 'end' => $now->modify('+2 days 19:00')],
            ['id' => 1021, 'schedule_type' => 'automation', 'title' => 'VIP Loyalty Booster', 'start' => $now->modify('-1 day 08:00'), 'end' => $now->modify('+2 days 19:00')],
            ['id' => 1022, 'schedule_type' => 'automation', 'title' => 'VIP Loyalty Booster', 'start' => $now->modify('-1 day 08:00'), 'end' => $now->modify('+2 days 19:00')],
            ['id' => 1023, 'schedule_type' => 'automation', 'title' => 'VIP Loyalty Booster', 'start' => $now->modify('-1 day 08:00'), 'end' => $now->modify('+2 days 19:00')],
            ['id' => 1031, 'schedule_type' => 'automation', 'title' => 'Flash Sale Follow-up', 'start' => $now->modify('-2 days 10:00'), 'end' => $now->modify('+6 days 16:00')],
            ['id' => 103, 'schedule_type' => 'automation', 'title' => 'Flash Sale Follow-up', 'start' => $now->modify('+3 days 10:00'), 'end' => $now->modify('+6 days 16:00')],
            ['id' => 1032, 'schedule_type' => 'automation', 'title' => 'Flash Sale Follow-up', 'start' => $now->modify('+3 days 10:00'), 'end' => $now->modify('+6 days 16:00')],
            ['id' => 104, 'schedule_type' => 'automation', 'title' => 'Abandoned Cart Reminder Wave', 'start' => $monthStart->modify('+11 days 08:00'), 'end' => $monthStart->modify('+13 days 12:00')],
            ['id' => 105, 'schedule_type' => 'automation', 'title' => 'Monthly Product Digest', 'start' => $monthStart->modify('+17 days 09:00'), 'end' => $monthStart->modify('+17 days 17:00')],
            ['id' => 106, 'schedule_type' => 'scheduled_window', 'title' => 'Summer Warm-up Teaser', 'start' => $monthStart->modify('+34 days 08:00'), 'end' => $monthStart->modify('+36 days 18:00')],
            ['id' => 107, 'schedule_type' => 'automation', 'title' => 'Quarterly Segment Refresh', 'start' => $monthStart->modify('+52 days 09:00'), 'end' => $monthStart->modify('+55 days 16:00')],
            ['id' => 108, 'schedule_type' => 'automation', 'title' => 'Holiday VIP Countdown', 'start' => $monthStart->modify('+112 days 09:00'), 'end' => $monthStart->modify('+116 days 23:00')],
            ['id' => 109, 'schedule_type' => 'automation', 'title' => 'Black Friday Warm-up', 'start' => $monthStart->modify('+156 days 10:00'), 'end' => $monthStart->modify('+160 days 20:00')],
            ['id' => 110, 'schedule_type' => 'automation', 'title' => 'Re-engagement Mini Campaign', 'start' => $monthStart->modify('-34 days 09:00'), 'end' => $monthStart->modify('-31 days 19:00')],
        ];

        $events = [];
        foreach ($seed as $row) {
            $isNewsletter = ($row['newsletter_campaign_id'] ?? 0) > 0;
            if ($filter === 'newsletter' && !$isNewsletter) continue;
            if ($filter === 'automations' && $isNewsletter) continue;

            $type = $isNewsletter ? \Components\Newsletter\PageConfig::MODE_NEWSLETTER : \Components\Newsletter\PageConfig::MODE_AUTOMATION;
            $id = (int)$row['id'];
            /** @var DateTimeImmutable $start */
            $start = $row['start'] instanceof DateTimeImmutable ? $row['start'] : new DateTimeImmutable((string)$row['start'], $tz);
            /** @var DateTimeImmutable $end */
            $end = $row['end'] instanceof DateTimeImmutable ? $row['end'] : new DateTimeImmutable((string)$row['end'], $tz);

            $events[] = [
                'id' => $id,
                'newsletter_campaign_id' => (int)($row['newsletter_campaign_id'] ?? 0),
                'title' => (string)$row['title'],
                'start' => $start,
                'end' => $end,
                'status' => self::eventStatus($start, $end, $now),
                'type' => $type,
                'schedule_type_ui' => self::sampleScheduleTypeUi($row),
                'edit_url' => Router::url('newsletter', ['group' => 'campaign-' . $id]),
            ];
        }

        return $events;
    }

    private static function sampleScheduleTypeUi(array $row): array
    {
        $code = $row['schedule_type'] ?? 'automation';
        $map = [
            'bulk_mail' => [
                'code' => 'bulk_mail',
                'token' => 'orange',
                'icon' => \Components\Base::SVG_MEGAPHONE,
                'label' => 'Bulk Mail',
            ],
            'scheduled_window' => [
                'code' => 'scheduled_window',
                'token' => 'green',
                'icon' => \Components\Base::SVG_CALENDAR_RANGE,
                'label' => 'Scheduled Window',
            ],
            'automation' => [
                'code' => 'automation',
                'token' => 'brand',
                'icon' => \Components\Base::SVG_FLOWER,
                'label' => 'Automation',
            ],
        ];
        return $map[$code] ?? $map['automation'];
    }

    private static function resolveScheduleTypeUi(array $evt): ?array
    {
        $ui = $evt['schedule_type_ui'] ?? null;
        if (!$ui && isset($evt['schedule_type'])) {
            if (self::canLoadNewsletterItemModel()) {
                try {
                    $code = $evt['schedule_type'];
                    $map = \Components\Models\NewsletterItem::scheduleTypeUiMap();
                    $cfg = $map[$code] ?? $map['automation'];
                    $ui = [
                        'code'  => $code,
                        'token' => (string)$cfg['token'],
                        'icon'  => (string)$cfg['icon'],
                        'label' => (string)\mh_lng($cfg['label_key'] ?? '', $cfg['label_fallback'] ?? ucfirst((string)$code)),
                    ];
                } catch (\Throwable $e) {}
            }
            if (!$ui) {
                $ui = self::sampleScheduleTypeUi($evt);
            }
        }

        if (!$ui) return null;

        $token = $ui['token'] ?? 'gray';
        $bg = \Components\Support\Palette::dotBg($token, 'gray');
        $ui['bg_class'] = trim($bg . ' text-white');
        return $ui;
    }

    /**
     * @param array<string,mixed>|null $stUi
     */
    public static function renderScheduleTypeIcon(?array $stUi, string $context = 'month'): string
    {
        if (!$stUi) {
            return '';
        }

        if (self::canLoadNewsletterItemModel()) {
            return '<div class="cal-event-type-base cal-event-type--' . $context . '">' .
                \Components\Models\NewsletterItem::renderScheduleTypeIconHtml($stUi['token'] ?? null, $stUi['icon'] ?? null) .
                '</div>';
        }

        $bgClass = Base::h($stUi['bg_class'] ?? '', false);
        $icon = $stUi['icon'] ?? '';

        return '<div class="cal-event-type-base cal-event-type--' . $context . '">
            <span class="inline-flex shrink-0 items-center justify-center size-3 rounded ' . $bgClass . ' [&_svg]:size-1.5">' . $icon . '</span>
        </div>';
    }

    private static function eventStatus(DateTimeImmutable $start, DateTimeImmutable $end, DateTimeImmutable $now): string
    {
        if ($end < $now) {
            return 'previous';
        }
        if ($start > $now) {
            return 'scheduled';
        }
        return 'active';
    }

    private static function statusDotClass(string $status): string
    {
        return self::statusPalette($status)['dot'];
    }

    private static function normalizeStatus(string $status): string
    {
        $status = strtolower($status);
        $map = [
            'active'    => 'active',
            'scheduled' => 'scheduled',
            'pending'   => 'scheduled',
            'previous'  => 'completed',
            'completed' => 'completed',
            'past'      => 'completed',
        ];
        return $map[$status] ?? 'active';
    }

    private static function statusBarClass(string $status): string
    {
        return self::statusPalette($status)['bar'];
    }

    private static function statusBadgeClass(string $status): string
    {
        return self::statusPalette($status)['badge'];
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    private static function smartCollectionDefs(): array
    {
        if (self::canLoadNewsletterItemModel()) {
            try {
                return \Components\Models\NewsletterItem::smartCollectionDefs();
            } catch (\Throwable $e) {
                // Fall through to the component-local palette below.
            }
        }

        return [
            'active' => [
                'color_token' => 'green',
            ],
            'scheduled' => [
                'color_token' => 'blue',
            ],
            'completed' => [
                'color_token' => 'gray',
            ],
        ];
    }

    private static function canLoadNewsletterItemModel(): bool
    {
        return class_exists('\Components\Models\NewsletterItem');
    }

    /**
     * @return array{dot:string,bar:string,badge:string}
     */
    private static function statusPalette(string $status): array
    {
        $defKey = self::normalizeStatus($status);
        $defs = self::smartCollectionDefs();
        $def = $defs[$defKey] ?? $defs['active'];
        $token = $def['color_token'];

        $palette = \Components\Support\Palette::map();
        $p = $palette[$token] ?? $palette['gray'];

        // Align bar class with the definition key for consistent CSS mapping
        $barClass = 'cal-event--' . $defKey;

        return [
            'dot'   => $p['dot'],
            'bar'   => $barClass,
            'badge' => $p['bg'] . ' ' . $p['text'] . ' ' . $p['ring'],
        ];
    }

    /**
     * @param array<string,mixed> $state
     * @param array<string,mixed> $options
     * @return array<string,mixed>
     */
    private static function resolveRenderOptions(array $state, array $options): array
    {
        $defaults = [
            'events_limit' => 4,
            'create_action' => [
                'mode' => 'link',
                'href_template' => (string)$state['create_base_url'] . '&demo_create_date={date}',
                'attrs' => [],
            ],
            'event_action' => [
                'mode' => 'link',
                'href_template' => '{url}',
                'attrs' => [],
            ],
        ];

        return array_replace_recursive($defaults, $options);
    }

    /**
     * @param array<string,mixed> $action
     * @param array<string,mixed> $context
     * @return array{tag:string,attrs:array<string,mixed>}
     */
    private static function resolveAction(array $action, array $context): array
    {
        $modeRaw = strtolower((string)($action['mode'] ?? 'link'));
        $mode = in_array($modeRaw, ['link', 'htmx'], true) ? $modeRaw : 'link';

        $attrs = [];
        $rawAttrs = $action['attrs'] ?? [];
        if (is_array($rawAttrs)) {
            foreach ($rawAttrs as $key => $value) {
                if (!is_scalar($value)) {
                    continue;
                }
                $attrs[(string)$key] = self::replaceTokens((string)$value, $context);
            }
        }

        if ($mode === 'htmx') {
            if (!isset($attrs['type'])) {
                $attrs['type'] = 'button';
            }
            return [
                'tag' => 'button',
                'attrs' => $attrs,
            ];
        }

        $hrefTemplate = isset($action['href_template']) ? (string)$action['href_template'] : '';
        if ($hrefTemplate === '') {
            $hrefTemplate = (string)($context['url'] ?? '');
        }

        $attrs = array_merge([
            'href' => self::replaceTokens($hrefTemplate, $context),
        ], $attrs);

        return [
            'tag' => 'a',
            'attrs' => $attrs,
        ];
    }

    /**
     * @param array<string,mixed> $context
     */
    private static function replaceTokens(string $template, array $context): string
    {
        $value = $template;
        foreach ($context as $key => $replacement) {
            if (!is_scalar($replacement)) {
                continue;
            }
            $value = str_replace('{' . $key . '}', (string)$replacement, $value);
        }

        return $value;
    }

    /**
     * @param array<string,mixed> $attrs
     * @return array<string,mixed>
     */
    private static function withClass(array $attrs, string $class): array
    {
        $existingClass = isset($attrs['class']) ? trim((string)$attrs['class']) : '';
        $attrs['class'] = trim($class . ($existingClass !== '' ? ' ' . $existingClass : ''));
        return $attrs;
    }

    private static function createUrlForDay(string $isoDate): string
    {
        return Router::url('newsletter', ['demo_create_date' => $isoDate]);
    }

    /**
     * @return array{0:DateTimeImmutable,1:DateTimeImmutable}
     */
    private static function resolveRange(string $view, DateTimeImmutable $anchor): array
    {
        if ($view === self::VIEW_WEEK) {
            $start = self::startOfWeek($anchor);
            $end = self::endOfWeek($anchor);
            return [$start, $end];
        }

        if ($view === self::VIEW_YEAR) {
            $start = $anchor->setDate((int)$anchor->format('Y'), 1, 1)->setTime(0, 0, 0);
            $end = $anchor->setDate((int)$anchor->format('Y'), 12, 31)->setTime(23, 59, 59);
            return [$start, $end];
        }

        if ($view === self::VIEW_QUARTER) {
            $start = $anchor->modify('first day of this month')->setTime(0, 0, 0);
            $end = $start->add(new DateInterval('P2M'))->modify('last day of this month')->setTime(23, 59, 59);
            return [$start, $end];
        }

        $start = $anchor->modify('first day of this month')->setTime(0, 0, 0);
        $end = $anchor->modify('last day of this month')->setTime(23, 59, 59);
        return [$start, $end];
    }

    private static function buildTitle(string $view, DateTimeImmutable $anchor): string
    {
        if ($view === self::VIEW_WEEK) {
            $start = self::startOfWeek($anchor);
            $end = self::endOfWeek($anchor);
            return mh_lng('UI_CALENDAR_WEEK', 'Week') . ' ' . $start->format('W') . ' -  ' . self::monthName($anchor) . ' ' . $anchor->format('Y'); //. ': ' . self::rangeLabel($start, $end);
        }
        if ($view === self::VIEW_YEAR) {
            return $anchor->format('Y');
        }
        if ($view === self::VIEW_QUARTER) {
            $end = $anchor->add(new DateInterval('P2M'));
            return self::monthName($anchor, true) . ' ' . $anchor->format('Y') . ' - ' . self::monthName($end, true) . ' ' . $end->format('Y');
        }
        return self::monthName($anchor) . ' ' . $anchor->format('Y');
    }

    private static function rangeLabel(DateTimeImmutable $start, DateTimeImmutable $end): string
    {
        return self::formatRangeDate($start) . ' - ' . self::formatRangeDate($end);
    }

    private static function resolveLocaleCode(): string
    {
        $rawLocale = function_exists('mh_session') ? (string)mh_session('language_code', 'en') : 'en';
        if ($rawLocale === '') {
            $rawLocale = 'en';
        }
        return strtolower(str_replace('_', '-', $rawLocale));
    }

    private static function isGermanLocale(): bool
    {
        return strpos(self::resolveLocaleCode(), 'de') === 0;
    }

    private static function formatRangeDate(DateTimeImmutable $date): string
    {
        $monthShort = self::monthName($date, true);
        if (self::isGermanLocale()) {
            return $date->format('j') . '. ' . $monthShort . ' ' . $date->format('Y');
        }

        return $monthShort . ' ' . $date->format('j') . ', ' . $date->format('Y');
    }

    private static function monthName(DateTimeImmutable $date, bool $short = false): string
    {
        $month = (int)$date->format('n');
        $fallback = $short
            ? [1 => 'Jan', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'May', 6 => 'Jun', 7 => 'Jul', 8 => 'Aug', 9 => 'Sep', 10 => 'Oct', 11 => 'Nov', 12 => 'Dec']
            : [1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August', 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'];

        $keyPrefix = $short ? 'UI_CALENDAR_MONTH_SHORT_' : 'UI_CALENDAR_MONTH_';
        return mh_lng($keyPrefix . $month, $fallback[$month] ?? '');
    }

    /**
     * @return array<int,string>
     */
    private static function weekDayNamesShort(): array
    {
        return [
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_MON', 'Mon'),
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_TUE', 'Tue'),
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_WED', 'Wed'),
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_THU', 'Thu'),
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_FRI', 'Fri'),
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_SAT', 'Sat'),
            mh_lng('UI_CALENDAR_WEEKDAY_SHORT_SUN', 'Sun'),
        ];
    }

    /**
     * @return array<int,string>
     */
    private static function weekDayLettersVeryShort(): array
    {
        return [
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_MON', 'M'),
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_TUE', 'T'),
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_WED', 'W'),
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_THU', 'T'),
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_FRI', 'F'),
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_SAT', 'S'),
            mh_lng('UI_CALENDAR_WEEKDAY_LETTER_SUN', 'S'),
        ];
    }

    private static function startOfWeek(DateTimeImmutable $date): DateTimeImmutable
    {
        return $date->modify('monday this week')->setTime(0, 0, 0);
    }

    private static function endOfWeek(DateTimeImmutable $date): DateTimeImmutable
    {
        return self::startOfWeek($date)->add(new DateInterval('P6D'))->setTime(23, 59, 59);
    }

    private static function isoDate(DateTimeImmutable $date): string
    {
        return $date->format('Y-m-d');
    }

    private static function parseYmd(string $value, DateTimeZone $tz): ?DateTimeImmutable
    {
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return null;
        }

        $parsed = DateTimeImmutable::createFromFormat('!Y-m-d', $value, $tz);
        return $parsed instanceof DateTimeImmutable ? $parsed : null;
    }
}
