<?php

namespace Components\Models;

class NewsletterItem extends BaseModel
{
    protected static string $table = 'TABLE_MAILBEEZ_NEWSLETTER_ITEMS';
    protected static string $primaryKey = 'newsletter_item_id';

    protected static string $blueprint = 'newsletter_item.yaml';

    // Router is used to build URLs for UI resources (ItemList options)
    // Import here to allow static helpers to provide unified render options.
    // Layering note: this is intentional per issue requirement to centralize UI options in the model.

    /**
     * By default return only items of type mb_newsletter::ITEM_TYPE_DEFAULT.
     * Pass $onlyDefault = false to fetch all items (legacy BaseModel::all behavior).
     * @param bool $onlyDefault
     * @return array<static>
     */
    public static function all(bool $onlyDefault = true): array
    {
        if (!$onlyDefault) {
            return parent::all();
        }
        if (self::isDev()) return [];

        $types = static::itemTypeDefs();
        $defaultType = (int)($types['default'] ?? 0);

        $sql = "SELECT * FROM " . static::table() .
            " WHERE newsletter_item_type = {$defaultType} ORDER BY " . static::pk() . " DESC";
        $q = \mh_db_query($sql);

        $items = [];
        while ($row = \mh_db_fetch_array($q)) {
            $items[] = new static($row);
        }
        \mh_db_free_result($q);

        return $items;
    }

    /**
     * Fetch all default items and attach a nested followups tree to each item.
     * The tree is provided on the dynamic attribute $item->followups as an array of NewsletterItem,
     * each of which also contains its own ->followups array, recursively.
     *
     * @return array<static> Default items with ->followups tree attached
     */
    public static function allWithFollowup(): array
    {
        // Use existing model methods and relations instead of manual SQL
        $roots = static::all(true); // default items only
        if (!$roots) return [];

        foreach ($roots as $root) {
            if ($root instanceof self) {
                $root->loadFollowupsTree();
            }
        }

        return $roots;
    }

    /**
     * Provide unified default options for Components\Resources\ItemList::render when listing NewsletterItems.
     * Pages can pass overrides to customize title, items, outer_attrs, etc.
     *
     * @param array $overrides
     * @return array
     */
    public static function itemListOptions(array $overrides = []): array
    {
        // Build defaults identical to those used in newsletter_items_list.php and newsletter_item_row.php
        $defaults = [
            'title' => 'All Flows',
            'items' => [],
            'id_key' => 'id',
            'name_key' => 'name',
            'status_key' => 'status',
            'outer_attrs' => 'id="newsletter-items-list"',
            'empty_text' => 'No items found or database not available.',
            'panel_type' => 'drawer',
            'expandable' => true,
            'expand_lazy_url' => \Components\Router::url('newsletter_item_flow', ['partial' => 1, 'id' => '{id}']),
            // Ensure lazy loader replaces itself with the flow HTML so the DOM structure
            // matches subsequent refreshes (which swap the inner content container).
            'expand_lazy_swap' => 'outerHTML',
            'expand_lazy_attrs' => [
                'hx-trigger' => 'intersect once',
            ],
            'open_key_prefix' => 'newsletter-flow',
            // Pattern A wiring: resource-specific events (newsletter_item:updated, etc.)
            'event_prefix' => 'newsletter_item',
            'expand_refresh_url' => \Components\Router::url('newsletter_item_flow', ['partial' => 1, 'id' => '{id}']),
            // Append no_loader=1 so that ItemList's placeholder skeleton is suppressed on row refresh
            'row_refresh_url' => \Components\Router::url('newsletter_item_row', ['partial' => 1, 'id' => '{id}', 'no_loader' => 1]),
            // Expose a stable DOM id for the row title so we can OOB-update it after save
            'row_title_id_prefix' => 'newsletter-item-title-',
            'new_action' => [
                'label' => 'New',
                'url' => \Components\Router::url('newsletter_item_modal', ['partial' => 1, 'mode' => 'new']),
                'attrs' => [
                    'data-title' => 'New Newsletter Item',
                    'data-size' => 'xl',
                ],
                'variant' => 'primary',
            ],
            // Gear-style config action (replaces inline Edit button)
            'config_action' => [
                'url' => \Components\Router::url('newsletter_item_modal', ['partial' => 1, 'mode' => 'edit']) . '&id={id}',
                'attrs' => [
                    'data-title' => 'Configure Item',
                    'data-size' => 'xl',
                    // panel_type is drawer, defaults will target config-drawer/body
                    'command' => 'show-modal',
                    'commandfor' => 'config-drawer',
                    'hx-target' => '#config-drawer-body',
                    'hx-swap' => 'innerHTML',
                ],
                'variant' => 'ghost',
                'size' => 'sm',
            ],
            'row_actions' => [
                // Deep copy flow (opens modal to choose target campaign)
                /*
                 NOT WORKING CORRECTLY - smart list configurations are not copied.
review and improve \mb_newsletter_items::deepCopy - read smart list configurations directly from database Select the target collection to copy this flow into.

                [
                    'label' => 'Deep copy…',
                    'url' => \Components\Router::url('newsletter_item_deepcopy', ['partial' => 1, 'id' => '{id}']),
                    'attrs' => [
                        'data-title' => 'Deep copy flow',
                        'data-size' => 'md',
                        // Force modal target (not drawer)
                        'command' => 'show-modal',
                        'commandfor' => 'config-modal',
                        'hx-target' => '#config-modal-body',
                        'hx-swap' => 'innerHTML',
                    ],
                ],
                */
            ],
        ];

        // Allow nested overrides (e.g., changing attrs inside actions)
        return array_replace_recursive($defaults, $overrides);
    }

    /**
     * Dynamic select options for newsletter_campaign_id field.
     * Refactored to use NewsletterCampaign model instead of direct DB access.
     * @return array<int|string, string>
     */
    public static function campaignOptions(): array
    {
        $options = [];
        // Use the model layer which gracefully returns [] when legacy DB isn't available.

        foreach (NewsletterCampaign::all() as $c) {
            $id = (int)($c->newsletter_campaign_id ?? 0);
            if ($id <= 0) {
                continue;
            }
            $name = (string)($c->newsletter_campaign_name ?? ('Campaign ' . $id));
            $options[$id] = $name;
        }
        return $options;
    }

    /**
     * Dynamic select options for newsletter_list_id (Audience)
     * @return array<int|string, string>
     */
    public static function audienceOptions(): array
    {
        $options = [];

        // Determine base source type filter ('customers'|'prospects') based on the
        // current item's assigned audience (or its campaign's audience as fallback).
        // Only apply this restriction when editing an existing item (id present).
        $typeFilter = null; // one of 'customers'|'prospects' or null for no filtering

        try {
            $item = static::find(mh_get('id', -1));
            if ($item instanceof self) {
                // Prefer the item's own audience
                $listId = (int)($item->newsletter_list_id ?? 0);

                // If item has no audience, try the campaign's audience
                if ($listId <= 0) {
                    try {
                        $camp = $item->campaign();
                        $listId = (int)($camp->newsletter_list_id ?? 0);
                    } catch (\Throwable $e) {
                        $listId = 0;
                    }
                }

                if ($listId > 0) {
                    $tp = self::resolveAudienceType($listId);
                    if ($tp !== '') {
                        $typeFilter = $tp; // reuse central resolver
                    }
                }
            }
        } catch (\Throwable $e) {
            // best-effort only; leave $typeFilter as null
        }

        foreach (Audience::all() as $a) {
            $id = (int)($a->newsletter_list_id ?? 0);
            if ($id <= 0) {
                continue;
            }

            if ($typeFilter !== null) {
                $tp = self::resolveAudienceType($id);
                if ($tp !== $typeFilter) {
                    continue;
                }
            }

            $name = (string)($a->newsletter_list_name ?? ('List ' . $id));
            $options[$id] = $name;
        }

        return $options;
    }

    /**
     * Resolve the base segmentation type for a given audience list id.
     * Returns 'customers'|'prospects' or '' when undetermined.
     */
    protected static function resolveAudienceType(int $listId): string
    {
        if ($listId <= 0) {
            return '';
        }
        try {
            $aud = Audience::find($listId);
            if (!$aud) {
                return '';
            }
            $src = (string)($aud->newsletter_list_source ?? '');
            if ($src === '') {
                return '';
            }
            $match = Audience::resolveSourcebeezMatch($src, false);
            $obj = $match['object'] ?? null;
            if ($obj) {
                $tp = strtolower((string)($obj->list_segmentation_type ?? ''));
                return in_array($tp, ['customers', 'prospects'], true) ? $tp : '';
            }
        } catch (\Throwable $e) { /* ignore */
        }
        return '';
    }

    /**
     * Prevent changing the audience to a different base source type on update.
     */
    protected function filterAttributesForSave(array $data): array
    {
        try {
            // Only enforce on update when both old and new list ids are available
            $oldId = (int)($this->attributes[static::pk()] ?? 0);
            if ($oldId > 0) {
                $oldList = (int)($this->attributes['newsletter_list_id'] ?? 0);
                $newList = isset($data['newsletter_list_id']) ? (int)$data['newsletter_list_id'] : $oldList;

                if ($oldList > 0 && $newList > 0 && $oldList !== $newList) {
                    $oldType = self::resolveAudienceType($oldList);
                    $newType = self::resolveAudienceType($newList);
                    if ($oldType !== '' && $newType !== '' && $oldType !== $newType) {
                        // Mismatch detected: keep the old audience to avoid issues
                        $data['newsletter_list_id'] = $oldList;
                    }
                }
            }
        } catch (\Throwable $e) { /* best-effort only */
        }

        return $data;
    }

    /**
     * Relation: this item belongs to a single campaign (reverse of NewsletterCampaign::items)
     * @return ?NewsletterCampaign
     */
    public function campaign(): ?object
    {
        return $this->belongsTo(NewsletterCampaign::class, 'newsletter_campaign_id');
    }

    /**
     * Relation: this item targets a single audience/list
     * @return ?Audience
     */
    public function audience(): ?object
    {
        return $this->belongsTo(Audience::class, 'newsletter_list_id');
    }

    /**
     * Self-relation: parent item (for follow-ups)
     * @return ?NewsletterItem
     */
    public function parent(): ?object
    {
        return $this->belongsTo(self::class, 'newsletter_followup_id');
    }

    /**
     * Self-relation: children (follow-up items)
     * @return array<NewsletterItem>
     */
    public function children(): array
    {
        return $this->hasMany(self::class, 'newsletter_followup_id', static::pk());
    }

    /**
     * Build and attach the follow-ups tree for this item.
     * Populates $this->followups with an array of NewsletterItem instances, each with its own ->followups, recursively.
     * Returns the built followups array for convenience.
     *
     * Safety: protects against accidental cycles by tracking visited item IDs.
     *
     * @param bool $onlyValid When true, includes only items recognized as follow-ups via isFollowup().
     * @return array<self>
     */
    public function loadFollowupsTree(bool $onlyValid = true): array
    {
        // If legacy DB is unavailable, nothing to load
        if (self::isDev()) {
            $this->followups = [];
            return [];
        }

        $visited = [];
        $build = function (self $item) use (&$build, &$visited, $onlyValid): array {
            $children = [];

            $id = (int)($item->{static::pk()} ?? 0);
            if ($id > 0) {
                if (isset($visited[$id])) {
                    // cycle detected: stop here
                    return [];
                }
                $visited[$id] = true;
            }

            foreach ($item->children() as $child) {
                if (!$child instanceof self) {
                    continue;
                }
                if ($onlyValid && !$child->isFollowup()) {
                    continue;
                }
                $child->followups = $build($child);
                $children[] = $child;
            }

            $item->followups = $children;
            return $children;
        };

        return $build($this);
    }


    /**
     * Build a FlowViz items array representing this NewsletterItem and its followups.
     * Uses FlowBuilder to generate a simple linear flow when single-child chains are present,
     * and a branch block when there are multiple immediate children.
     *
     * @return array<int, array<string,mixed>> FlowViz items
     */
    public function flow(): array
    {
        try {
            // In dev mode with no DB, return an empty flow to avoid errors
            if (self::isDev()) {
                return [];
            }

            // Ensure followups tree exists
            if (!isset($this->followups) || !is_array($this->followups)) {
                try {
                    $this->loadFollowupsTree(true);
                } catch (\Throwable $e) {
                    $this->followups = [];
                }
            }


            $fb = \Components\CampaignFlow\FlowBuilder::make();

            // 1) Initial Audience step (represents the audience for this newsletter)
            // Resolve the current item id early so we can wire the audience card to open the edit modal
            $rootId = (int)($this->{static::pk()} ?? 0);
            $audListId = (int)($this->newsletter_list_id ?? 0);

            $campaignId = $this->campaign()->newsletter_campaign_id ?? -1;

            if ($audListId <= 0) {
                // Fallback: take from campaign if item does not have a list assigned
                try {
                    $c = $this->campaign();
                    $audListId = (int)($c->newsletter_list_id ?? 0);
                } catch (\Throwable $e) { /* ignore */
                }
            }

            $audName = '';
            if ($audListId > 0) {
                try {
                    $aud = Audience::find($audListId);
                    $audName = (string)($aud->newsletter_list_name ?? '');
                } catch (\Throwable $e) {
                    $audName = '';
                }
            }
            if ($audName === '') {
                $audName = (string)\mh_lng('MAILBEEZ_AUDIENCE_UNDEFINED', 'Audience');
            }
            // Build modal attrs to edit this item so users can change newsletter_list_id directly
            // Support targeted field editing similar to FlowBuilder::cfgModal by passing fields=...
            $editUrl = \Components\Router::url('newsletter_item_modal', [
                'partial' => 1,
                'mode' => 'edit',
                'id' => $rootId,
                // limit the form to the audience field for a focused edit experience
                'fields' => 'newsletter_list_id',
                // auto-close the modal on save for snappier UX
                'autoclose' => 'true',
                // Narrow the emitted HX-Trigger to refresh only the expandable flow area
                // instead of the full row (which listens to :updated)
                'event_suffix' => 'expandable:refresh',
            ]);

            $fb->audience($audName, [
                'id' => 'audience-' . max(0, $audListId),
                'subtitle' => (string)\mh_lng('MAILBEEZ_NEWSLETTER_AUDIENCE', 'Audience') . " #$audListId " ,
                // Make the audience clickable to edit the item (newsletter_list_id)
                'attrs' => [
                    'command' => 'show-modal',
                    'commandfor' => 'config-modal',
                    'data-title' => (string)\mh_lng('MAILBEEZ_EDIT_AUDIENCE', 'Edit Audience'),
                    'data-size' => 'xl',
                    'hx-get' => $editUrl,
                    'hx-target' => '#config-modal-body',
                    'hx-swap' => 'innerHTML',
                ],
            ]);

            // 1.5) Visualize schedule as a date_range right after Audience
            // Normalize dates (YYYY-MM-DD) and provide a quick-edit modal for start/end
            $toDate = function ($v) {
                if (!$v) {
                    return null;
                }
                $ts = @strtotime((string)$v);
                if ($ts === false || $ts <= 0) {
                    return null;
                }
                return date('Y-m-d', $ts);
            };
            /** @var string|null $startRaw */
            $startRaw = $this->newsletter_item_start;
            /** @var string|null $endRaw */
            $endRaw = $this->newsletter_item_end;
            $startDate = $toDate($startRaw);
            $endDate = $toDate($endRaw);

            $editScheduleUrl = \Components\Router::url('newsletter_item_modal', [
                'partial' => 1,
                'mode' => 'edit',
                'id' => $rootId,
                'fields' => 'newsletter_item_start,newsletter_item_end',
                'autoclose' => 'true',
                // Similarly constrain refresh scope to the flow expandable area
                'event_suffix' => 'expandable:refresh',
            ]);

            $fb->dateRange($startDate, $endDate, [
                'label' => null, // (string)\mh_lng('MAILBEEZ_NEWSLETTER_SCHEDULE', 'Schedule'),
                'attrs' => [
                    'command' => 'show-modal',
                    'commandfor' => 'config-modal',
                    'data-title' => (string)\mh_lng('MAILBEEZ_EDIT_SCHEDULE', 'Edit Schedule'),
                    'data-size' => 'lg',
                    'hx-get' => $editScheduleUrl,
                    'hx-target' => '#config-modal-body',
                    'hx-swap' => 'innerHTML',
                ],
                // When no dates are set, keep it interactive but visually indicate disabled state
                'disabled' => ($startDate === null && $endDate === null),
            ]);

            // 2) Root step: the item itself
            $rootTitle = (string)($this->newsletter_item_name ?? ('Item #' . $rootId));
            $subtitle = $this->isInitial() ? (string)\mh_lng('MAILBEEZ_NEWSLETTER_INITIAL', 'Initial') : (string)\mh_lng('MAILBEEZ_NEWSLETTER_FOLLOWUP', 'Follow-up');

            $fb->step('item-' . $rootId, $rootTitle, [
                'subtitle' => $subtitle,
                'icon' => \Components\Base::SVG_MAIL,
                'color' => 'indigo',
                // Attach dropdown actions (Editor, Preview, Send test, etc.) using legacy mb_newsletter routes
                // similar to how module flows attach actions in legacy modules.
                'dropdown' => (function () use ($rootId, $campaignId) {
                    try {
                        if (class_exists('\\mb_newsletter')) {
                            $mod = new \mb_newsletter();
                            // Use module-level routes � they open the newsletter editor/preview pages
                            $routes = $mod->getNewsletterAdminActionRoutes($rootId, $rootId, $campaignId);
                            return $mod->buildFlowStepDropdownFromAdminRoutes($routes);
                        }
                    } catch (\Throwable $e) {
                        dd($e);
                        // ignore, no dropdown
                    }
                    return ['items' => []];
                })(),
            ]);

            // If there are no children, return single-step flow
            $children = is_array($this->followups ?? null) ? $this->followups : [];
            if (empty($children)) {
                return $fb->toArray();
            }

            // Helper to create a lane from a child, recursively expanding linear chains
            // and inserting nested branches when a node has multiple children.
            // For each follow-up email, we insert a condition and a wait connector before the email step.
            $buildLane = function (self $node, $existingBuilder = null) use (&$buildLane, $rootId, $campaignId) {
                $b = ($existingBuilder instanceof \Components\CampaignFlow\FlowBuilder)
                    ? $existingBuilder
                    : \Components\CampaignFlow\FlowBuilder::make();

                $current = $node;
                $guard = 0; // protect from infinite loops
                while ($current instanceof self && $guard < 100) {
                    $guard++;
                    $cid = (int)($current->{static::pk()} ?? 0);
                    $title = (string)($current->newsletter_item_name ?? ('Item #' . $cid));

                    // Determine segmentation editor URL (legacy) for this follow-up's audience
                    // so both condition and wait connectors become clickable.
                    $segmentationAttrs = null;
                    try {
                        // Resolve list id: prefer item-specific, then campaign-level
                        $listId = 0;
                        try {
                            $listId = (int)($current->newsletter_list_id ?? 0);
                            if ($listId <= 0) {
                                $campaign = $current->campaign();
                                $listId = (int)($campaign->newsletter_list_id ?? 0);
                            }
                        } catch (\Throwable $e) {
                            $listId = 0;
                        }

                        if ($listId > 0 && class_exists('\\mb_newsletter')) {
                            $mod = new \mb_newsletter();
                            $routes = (array)$mod->getNewsletterSegmentationActionRoutes($listId, $rootId);
                            if (isset($routes['segmentation']['url'])) {
                                $url = (string)$routes['segmentation']['url'];
                                if ($url !== '') {
                                    $segmentationAttrs = [
                                        'command' => 'show-modal',
                                        'commandfor' => 'config-modal',
                                        // open legacy inside iframe wrapper
                                        'data-size' => '4xl',
                                        'data-url' => $url,
                                        'hx-target' => '#config-modal-body',
                                        'hx-swap' => 'innerHTML',
                                    ];
                                }
                            }
                        }
                    } catch (\Throwable $e) { /* ignore; keep non-clickable */ }

                    // Read follow-up segmentation to render condition (event/condition) and wait (timing)
                    // Be defensive in case audience or config is not available
                    try {
                        $followUpSegmentation = (array)($current->audience()->followUpConfiguration());
                    } catch (\Throwable $e) {
                        $followUpSegmentation = [];
                    }

                    // Insert condition and wait before each follow-up email (clickable when segmentation editor is available)
                    // Condition badge: top line = event (uses event-* colors), bottom line = standardized message
                    $condEvent = trim((string)($followUpSegmentation['event'] ?? ''));
                    $condCond = trim((string)($followUpSegmentation['condition'] ?? ''));

                    // normalize event: date_sent -> sent
                    $eventLower = strtolower($condEvent);
                    if ($eventLower === 'date_sent') {
                        $eventLower = 'sent';
                    }

                    $condLower = strtolower($condCond);
                    // Skip badge entirely when event=sent and condition=always (no gating)
                    $skipCondition = ($eventLower === 'sent' && $condLower === 'always');

                    if (!$skipCondition) {
                        $condOptions = $segmentationAttrs ? ['attrs' => $segmentationAttrs] : [];
                        // Enable special rendering mode in ConditionBadge via Connector
                        $condOptions['upper_event_lower_condition'] = true;

                        // Determine lower label ("condition" line). For event=ordered we show no lower label.
                        // Keep previous behavior (use the raw condition text) for all other events.
                        $condLabel = $condLower;
                        if (in_array($eventLower, ['ordered'])) {
                            $condLabel = '';
                        }
                        if (in_array($condLabel, ['always'])) {
                            $condLabel = '';
                        }
                        // Build a combined title/label for hover based on both parts
                        $conditionStepLabel = ($eventLower !== '' ? $eventLower : (string)\mh_lng('MAILBEEZ_FLOW_CONDITION_DEFAULT', 'Condition'))
                            . (($condLabel !== '') ? (' — ' . $condLabel) : '');


                        $eventLabel = $eventLower == 'sent' ? '' : $eventLower;

                        // Upper shows normalized event keyword; lower shows standardized message for common events
                        $b->condition(
                            $conditionStepLabel,
                            $eventLabel,
                            $condLabel,
                            $condOptions
                        );
                    }

                    // Wait label: use timing data if available (days/hours/minutes)
                    $waitLabel = (string)\mh_lng('MAILBEEZ_FLOW_WAIT_GENERIC', 'Wait');
                    $hasTiming = array_key_exists('timing', $followUpSegmentation ?? []);
                    $timing = (array)($followUpSegmentation['timing'] ?? []);
                    $tDays = isset($timing['days']) ? (int)$timing['days'] : 0;
                    $tHours = isset($timing['hours']) ? (int)$timing['hours'] : 0;
                    $tMinutes = isset($timing['minutes']) ? (int)$timing['minutes'] : 0;
                    if ($hasTiming) {
                        // Prefer FlowBuilder's localized day formatter when only days are set (including 0)
                        if ($tHours === 0 && $tMinutes === 0) {
                            $waitLabel = (string)$tDays; // FlowBuilder::wait will localize numeric day labels
                        } else {
                            $parts = [];
                            if ($tDays !== 0) { $parts[] = $tDays . 'd'; }
                            if ($tHours !== 0) { $parts[] = $tHours . 'h'; }
                            if ($tMinutes !== 0) { $parts[] = $tMinutes . 'm'; }
                            if (empty($parts)) { $parts[] = '0m'; }
                            $waitLabel = implode(' ', $parts);
                        }
                    }

                    $b->wait(
                        $waitLabel,
                        $segmentationAttrs ? ['attrs' => $segmentationAttrs] : []
                    );

                    // Then the follow-up email step
                    $b->step('item-' . $cid, $title, [
                        'subtitle' => (string)\mh_lng('MAILBEEZ_NEWSLETTER_FOLLOWUP', 'Follow-up'),
                        'icon' => \Components\Base::SVG_MAIL,
                        'color' => 'indigo',
                        'dropdown' => (function () use ($cid, $rootId, $campaignId) {
                            try {
                                if (class_exists('\\mb_newsletter')) {
                                    $mod = new \mb_newsletter();
                                    $routes = $mod->getNewsletterAdminActionRoutes($cid, $rootId, $campaignId);
                                    return $mod->buildFlowStepDropdownFromAdminRoutes($routes);
                                }
                            } catch (\Throwable $e) { /* ignore */ }
                            return ['items' => []];
                        })(),
                    ]);

                    $kids = is_array($current->followups ?? null) ? $current->followups : [];
                    $kidCount = count($kids);
                    if ($kidCount === 1) {
                        // Continue linear chain
                        $current = $kids[0];
                        continue;
                    }

                    if ($kidCount > 1) {
                        // Insert a nested branch right after the current step
                        $closures = [];
                        foreach ($kids as $child) {
                            if (!$child instanceof self) { continue; }
                            $closures[] = function ($laneBuilder) use ($child, &$buildLane) {
                                $buildLane($child, $laneBuilder);
                            };
                        }
                        if (!empty($closures)) {
                            $b->branch($closures, ['gap' => 'md']);
                        }
                    }

                    // Stop after inserting a branch; deeper levels handled inside closures
                    break;
                }

                // Return items array when no builder was provided, otherwise just leave items in builder
                return ($existingBuilder instanceof \Components\CampaignFlow\FlowBuilder)
                    ? $b
                    : $b->toArray();
            };

            if (count($children) === 1) {
                // Linear chain: append lane items directly after root
                $lane = $buildLane($children[0]);
                // Easiest is to merge arrays
                $items = $fb->toArray();
                return array_merge($items, $lane);
            }

            // Multiple children: create a branch with one lane per child (each lane may be a short linear chain)
            // Use FlowBuilder::branch so it can add helpful metadata like top/bottom ordering for 2-lane branches.
            $closures = [];
            foreach ($children as $ch) {
                if (!$ch instanceof self) {
                    continue;
                }
                $closures[] = function ($laneBuilder) use ($ch, &$buildLane) {
                    $buildLane($ch, $laneBuilder);
                };
            }

            if (!empty($closures)) {
                $fb->branch($closures, ['gap' => 'md']);
            }

            return $fb->toArray();
        } catch (\Throwable $e) {
            return [];
        }
    }

    /**
     * Resolve type constants for newsletter items, preferring mb_newsletter when available.
     * @return array{default:int, followup:int}
     */
    protected static function itemTypeDefs(): array
    {
        $default = 0;
        $followup = 1;

        // Use mb_newsletter constants when available
        if (class_exists('\\mb_newsletter')) {
            if (defined('\\mb_newsletter::ITEM_TYPE_DEFAULT')) {
                $default = (int)\mb_newsletter::ITEM_TYPE_DEFAULT;
            }
            if (defined('\\mb_newsletter::ITEM_TYPE_FOLLOWUP')) {
                $followup = (int)\mb_newsletter::ITEM_TYPE_FOLLOWUP;
            }
        }

        return ['default' => $default, 'followup' => $followup];
    }

    public function isInitial(): bool
    {
        $types = static::itemTypeDefs();
        return ((int)($this->newsletter_followup_id ?? 0)) === 0
            && ((int)($this->newsletter_item_type ?? 0)) === (int)($types['default'] ?? 0);
    }

    public function isFollowup(): bool
    {
        $types = static::itemTypeDefs();
        return ((int)($this->newsletter_followup_id ?? 0)) > 0
            && ((int)($this->newsletter_item_type ?? 0)) === (int)($types['followup'] ?? 1);
    }

    /**
     * Determine the schedule state of this newsletter item based on
     * newsletter_item_start and newsletter_item_end.
     * Three states are supported:
     *  - pending: start date is in the future
     *  - active: currently within the date range (or no restrictive dates)
     *  - past: end date is in the past
     *
     * Returns an array with:
     *  - state: one of 'pending'|'active'|'past'
     *  - label: short, user-friendly wording for the state
     */
    public function scheduleState(): array
    {
        $parse = function ($v, bool $isEnd = false): ?int {
            if ($v === null || $v === '') {
                return null;
            }
            // Integer timestamp
            if (is_int($v)) {
                return $v;
            }
            if (is_string($v)) {
                $s = trim($v);
                // If it looks like a pure date (YYYY-MM-DD), treat end dates as end-of-day
                if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $s)) {
                    $ts = @strtotime($s . ($isEnd ? ' 23:59:59' : ' 00:00:00'));
                    return $ts !== false ? (int)$ts : null;
                }
                $ts = @strtotime($s);
                return $ts !== false ? (int)$ts : null;
            }
            return null;
        };

        $now = time();
        $startTs = $parse($this->newsletter_item_start ?? null, false);
        $endTs = $parse($this->newsletter_item_end ?? null, true);

        // Determine state
        if ($startTs !== null && $startTs > $now) {
            // Pending: starts in the future
            $label = (string)\mh_lng('MAILBEEZ_SCHEDULE_PENDING', 'Starts on ') . date('j M Y', $startTs);
            return ['state' => 'pending', 'label' => $label];
        }

        if ($endTs !== null && $endTs < $now) {
            // Past: already ended
            $label = (string)\mh_lng('MAILBEEZ_SCHEDULE_PAST', 'Ended on ') . date('j M Y', $endTs);
            return ['state' => 'past', 'label' => $label];
        }

        // Active: within range or unconstrained
        if ($endTs !== null) {
            $label = (string)\mh_lng('MAILBEEZ_SCHEDULE_ACTIVE_UNTIL', 'Active until ') . date('j M Y', $endTs);
        } else {
            $label = (string)\mh_lng('MAILBEEZ_SCHEDULE_ACTIVE', 'Active');
        }
        return ['state' => 'active', 'label' => $label];
    }

    /**
     * Return only the schedule state key: pending|active|past
     */
    public function scheduleStateKey(): string
    {
        $st = $this->scheduleState();
        return isset($st['state']) ? (string)$st['state'] : 'active';
    }

    public function isSchedulePending(): bool
    {
        return $this->scheduleStateKey() === 'pending';
    }

    public function isScheduleActive(): bool
    {
        return $this->scheduleStateKey() === 'active';
    }

    public function isSchedulePast(): bool
    {
        return $this->scheduleStateKey() === 'past';
    }

}
