<?php

namespace Filament\Forms\Components;

use Closure;
use Filament\Actions\Action;
use Filament\Forms\Components\Builder\Block;
use Filament\Forms\View\FormsIconAlias;
use Filament\Schemas\Components\Concerns\CanBeCollapsed;
use Filament\Schemas\Components\Contracts\CanConcealComponents;
use Filament\Schemas\Components\Contracts\HasExtraItemActions;
use Filament\Schemas\Schema;
use Filament\Support\Components\Contracts\HasEmbeddedView;
use Filament\Support\Components\ViewComponent;
use Filament\Support\Concerns\HasReorderAnimationDuration;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\GridDirection;
use Filament\Support\Enums\Size;
use Filament\Support\Enums\Width;
use Filament\Support\Facades\FilamentIcon;
use Filament\Support\Icons\Heroicon;
use Filament\Support\View\ComponentAttributeBag as FilamentComponentAttributeBag;
use Filament\Support\View\Components\DropdownComponent\ItemComponent;
use Filament\Support\View\Components\DropdownComponent\ItemComponent\IconComponent;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Js;
use Illuminate\Support\Str;

use function Filament\Forms\array_move_after;
use function Filament\Forms\array_move_before;
use function Filament\Support\generate_icon_html;
use function Filament\Support\generate_loading_indicator_html;

class Builder extends Field implements CanConcealComponents, HasEmbeddedView, HasExtraItemActions
{
    use CanBeCollapsed;
    use Concerns\CanBeCloned;
    use Concerns\CanGenerateUuids;
    use Concerns\CanLimitItemsLength;
    use Concerns\HasExtraItemActions;
    use HasReorderAnimationDuration;

    protected ?string $publishedViewOverrideCheckPath = 'filament-forms::components.builder';

    protected string | Closure | null $addBetweenActionLabel = null;

    protected string | Closure | null $addActionLabel = null;

    protected bool | Closure $isReorderable = true;

    protected bool | Closure $isReorderableWithDragAndDrop = true;

    protected bool | Closure $isReorderableWithButtons = false;

    protected bool | Closure $isAddable = true;

    protected bool | Closure $isDeletable = true;

    protected bool | Closure $hasBlockLabels = true;

    protected bool | Closure $hasBlockNumbers = true;

    protected bool | Closure $hasBlockIcons = false;

    protected bool | Closure $hasBlockHeaders = true;

    protected bool | Closure $hasBlockPreviews = false;

    protected bool | Closure $hasInteractiveBlockPreviews = false;

    protected Alignment | string | Closure | null $addActionAlignment = null;

    protected ?Closure $modifyAddActionUsing = null;

    protected ?Closure $modifyAddBetweenActionUsing = null;

    protected ?Closure $modifyCloneActionUsing = null;

    protected ?Closure $modifyDeleteActionUsing = null;

    protected ?Closure $modifyMoveDownActionUsing = null;

    protected ?Closure $modifyMoveUpActionUsing = null;

    protected ?Closure $modifyReorderActionUsing = null;

    protected ?Closure $modifyCollapseActionUsing = null;

    protected ?Closure $modifyExpandActionUsing = null;

    protected ?Closure $modifyCollapseAllActionUsing = null;

    protected ?Closure $modifyExpandAllActionUsing = null;

    protected ?Closure $modifyEditActionUsing = null;

    protected string | Closure | null $labelBetweenItems = null;

    protected bool | Closure $isBlockLabelTruncated = true;

    /**
     * @var array<string | int, int | Closure | null> | null
     */
    protected ?array $blockPickerColumns = [];

    protected Width | string | Closure | null $blockPickerWidth = null;

    protected bool | Closure | null $shouldPartiallyRenderAfterActionsCalled = null;

    /**
     * @var array<?string> | null
     */
    protected ?array $cachedItemsRawStateStructure = null;

    protected function setUp(): void
    {
        parent::setUp();

        $this->default([]);

        $this->afterStateHydrated(static function (Builder $component): void {
            $component->hydrateItems();
        });

        $this->registerActions([
            fn (Builder $component): Action => $component->getAddAction(),
            fn (Builder $component): Action => $component->getAddBetweenAction(),
            fn (Builder $component): Action => $component->getCloneAction(),
            fn (Builder $component): Action => $component->getCollapseAction(),
            fn (Builder $component): Action => $component->getCollapseAllAction(),
            fn (Builder $component): Action => $component->getDeleteAction(),
            fn (Builder $component): Action => $component->getEditAction(),
            fn (Builder $component): Action => $component->getExpandAction(),
            fn (Builder $component): Action => $component->getExpandAllAction(),
            fn (Builder $component): Action => $component->getMoveDownAction(),
            fn (Builder $component): Action => $component->getMoveUpAction(),
            fn (Builder $component): Action => $component->getReorderAction(),
        ]);

        $this->mutateDehydratedStateUsing(static function (?array $state): array {
            return array_values($state ?? []);
        });
    }

    public function hydrateItems(): void
    {
        $items = [];

        foreach ($this->getRawState() ?? [] as $itemData) {
            if ($uuid = $this->generateUuid()) {
                $items[$uuid] = $itemData;
            } else {
                $items[] = $itemData;
            }
        }

        $this->rawState($items);
    }

    /**
     * @param  array<Block> | Closure  $blocks
     */
    public function blocks(array | Closure $blocks): static
    {
        $this->components($blocks);

        return $this;
    }

    public function getAddAction(): Action
    {
        $action = Action::make($this->getAddActionName())
            ->label(fn (Builder $component) => $component->getAddActionLabel())
            ->color('gray')
            ->action(function (array $arguments, Builder $component, array $data = []): void {
                $newUuid = $component->generateUuid();

                $items = $component->getRawState();

                if ($newUuid) {
                    $items[$newUuid] = [
                        'type' => $arguments['block'],
                        'data' => $data,
                    ];
                } else {
                    $items[] = [
                        'type' => $arguments['block'],
                        'data' => $data,
                    ];
                }

                $component->rawState($items);

                $component->getChildSchema($newUuid ?? array_key_last($items))->fill(filled($data) ? $data : null);

                $component->collapsed(false, shouldMakeComponentCollapsible: false);

                $component->callAfterStateUpdated();
            })
            ->livewireClickHandlerEnabled(false)
            ->button()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isAddable());

        if ($this->hasBlockPreviews()) {
            $action
                ->modalHeading(fn (Builder $component) => __('filament-forms::components.builder.actions.add.modal.heading', [
                    'label' => $component->getLabel(),
                ]))
                ->modalSubmitActionLabel(__('filament-forms::components.builder.actions.add.modal.actions.add.label'))
                ->schema(function (array $arguments, Builder $component): array {
                    return $component->getBlock($arguments['block'])->getClone()->getDefaultChildComponents();
                });
        }

        if ($this->modifyAddActionUsing) {
            $action = $this->evaluate($this->modifyAddActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function addActionAlignment(Alignment | string | Closure | null $addActionAlignment): static
    {
        $this->addActionAlignment = $addActionAlignment;

        return $this;
    }

    public function getAddActionAlignment(): Alignment | string | null
    {
        $alignment = $this->evaluate($this->addActionAlignment);

        if (is_string($alignment)) {
            $alignment = Alignment::tryFrom($alignment) ?? $alignment;
        }

        return $alignment;
    }

    public function addAction(?Closure $callback): static
    {
        $this->modifyAddActionUsing = $callback;

        return $this;
    }

    public function getAddActionName(): string
    {
        return 'add';
    }

    public function getAddBetweenAction(): Action
    {
        $action = Action::make($this->getAddBetweenActionName())
            ->label(fn (Builder $component) => $component->getAddBetweenActionLabel())
            ->color('gray')
            ->action(function (array $arguments, Builder $component, array $data = []): void {
                $newKey = $component->generateUuid();

                $items = [];

                foreach ($component->getRawState() ?? [] as $key => $item) {
                    $items[$key] = $item;

                    if ($key === $arguments['afterItem']) {
                        if ($newKey) {
                            $items[$newKey] = [
                                'type' => $arguments['block'],
                                'data' => $data,
                            ];
                        } else {
                            $items[] = [
                                'type' => $arguments['block'],
                                'data' => $data,
                            ];

                            $newKey = array_key_last($items);
                        }
                    }
                }

                $component->rawState($items);

                $component->getChildSchema($newKey)->fill(filled($data) ? $data : null);

                $component->collapsed(false, shouldMakeComponentCollapsible: false);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->livewireClickHandlerEnabled(false)
            ->button()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isAddable());

        if ($this->hasBlockPreviews()) {
            $action
                ->modalHeading(fn (Builder $component) => __('filament-forms::components.builder.actions.add_between.modal.heading', [
                    'label' => $component->getLabel(),
                ]))
                ->modalSubmitActionLabel(__('filament-forms::components.builder.actions.add_between.modal.actions.add.label'))
                ->schema(function (array $arguments, Builder $component): array {
                    return $component->getBlock($arguments['block'])->getClone()->getDefaultChildComponents();
                });
        }

        if ($this->modifyAddBetweenActionUsing) {
            $action = $this->evaluate($this->modifyAddBetweenActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function addBetweenAction(?Closure $callback): static
    {
        $this->modifyAddBetweenActionUsing = $callback;

        return $this;
    }

    public function getAddBetweenActionName(): string
    {
        return 'addBetween';
    }

    public function getCloneAction(): Action
    {
        $action = Action::make($this->getCloneActionName())
            ->label(__('filament-forms::components.builder.actions.clone.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_CLONE) ?? Heroicon::Square2Stack)
            ->color('gray')
            ->action(function (array $arguments, Builder $component): void {
                $newUuid = $component->generateUuid();

                $items = $component->getRawState();

                if ($newUuid) {
                    $items[$newUuid] = $items[$arguments['item']];
                } else {
                    $items[] = $items[$arguments['item']];
                }

                $component->rawState($items);

                $component->collapsed(false, shouldMakeComponentCollapsible: false);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->iconButton()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isCloneable());

        if ($this->modifyCloneActionUsing) {
            $action = $this->evaluate($this->modifyCloneActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function cloneAction(?Closure $callback): static
    {
        $this->modifyCloneActionUsing = $callback;

        return $this;
    }

    public function getCloneActionName(): string
    {
        return 'clone';
    }

    public function getDeleteAction(): Action
    {
        $action = Action::make($this->getDeleteActionName())
            ->label(__('filament-forms::components.builder.actions.delete.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_DELETE) ?? Heroicon::Trash)
            ->color('danger')
            ->action(function (array $arguments, Builder $component): void {
                $items = $component->getRawState();
                unset($items[$arguments['item']]);

                $component->rawState($items);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->iconButton()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isDeletable());

        if ($this->modifyDeleteActionUsing) {
            $action = $this->evaluate($this->modifyDeleteActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function deleteAction(?Closure $callback): static
    {
        $this->modifyDeleteActionUsing = $callback;

        return $this;
    }

    public function getDeleteActionName(): string
    {
        return 'delete';
    }

    public function getMoveDownAction(): Action
    {
        $action = Action::make($this->getMoveDownActionName())
            ->label(__('filament-forms::components.builder.actions.move_down.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_MOVE_DOWN) ?? Heroicon::ArrowDown)
            ->color('gray')
            ->action(function (array $arguments, Builder $component): void {
                $items = array_move_after($component->getRawState(), $arguments['item']);

                $component->rawState($items);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->iconButton()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isReorderable());

        if ($this->modifyMoveDownActionUsing) {
            $action = $this->evaluate($this->modifyMoveDownActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function moveDownAction(?Closure $callback): static
    {
        $this->modifyMoveDownActionUsing = $callback;

        return $this;
    }

    public function getMoveDownActionName(): string
    {
        return 'moveDown';
    }

    public function getMoveUpAction(): Action
    {
        $action = Action::make($this->getMoveUpActionName())
            ->label(__('filament-forms::components.builder.actions.move_up.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_MOVE_UP) ?? Heroicon::ArrowUp)
            ->color('gray')
            ->action(function (array $arguments, Builder $component): void {
                $items = array_move_before($component->getRawState(), $arguments['item']);

                $component->rawState($items);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->iconButton()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isReorderable());

        if ($this->modifyMoveUpActionUsing) {
            $action = $this->evaluate($this->modifyMoveUpActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function moveUpAction(?Closure $callback): static
    {
        $this->modifyMoveUpActionUsing = $callback;

        return $this;
    }

    public function labelBetweenItems(string | Closure | null $label): static
    {
        $this->labelBetweenItems = $label;

        return $this;
    }

    public function getMoveUpActionName(): string
    {
        return 'moveUp';
    }

    public function getReorderAction(): Action
    {
        $action = Action::make($this->getReorderActionName())
            ->label(__('filament-forms::components.builder.actions.reorder.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_REORDER) ?? Heroicon::ArrowsUpDown)
            ->color('gray')
            ->action(function (array $arguments, Builder $component): void {
                $items = [
                    ...array_flip($arguments['items']),
                    ...$component->getRawState(),
                ];

                $component->rawState($items);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->livewireClickHandlerEnabled(false)
            ->iconButton()
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => $component->isReorderableWithDragAndDrop());

        if ($this->modifyReorderActionUsing) {
            $action = $this->evaluate($this->modifyReorderActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function reorderAction(?Closure $callback): static
    {
        $this->modifyReorderActionUsing = $callback;

        return $this;
    }

    public function getReorderActionName(): string
    {
        return 'reorder';
    }

    public function getCollapseAction(): Action
    {
        $action = Action::make($this->getCollapseActionName())
            ->label(__('filament-forms::components.builder.actions.collapse.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_COLLAPSE) ?? Heroicon::ChevronUp)
            ->color('gray')
            ->livewireClickHandlerEnabled(false)
            ->iconButton()
            ->size(Size::Small);

        if ($this->modifyCollapseActionUsing) {
            $action = $this->evaluate($this->modifyCollapseActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function collapseAction(?Closure $callback): static
    {
        $this->modifyCollapseActionUsing = $callback;

        return $this;
    }

    public function getCollapseActionName(): string
    {
        return 'collapse';
    }

    public function getExpandAction(): Action
    {
        $action = Action::make($this->getExpandActionName())
            ->label(__('filament-forms::components.builder.actions.expand.label'))
            ->icon(FilamentIcon::resolve(FormsIconAlias::COMPONENTS_BUILDER_ACTIONS_EXPAND) ?? Heroicon::ChevronDown)
            ->color('gray')
            ->livewireClickHandlerEnabled(false)
            ->iconButton()
            ->size(Size::Small);

        if ($this->modifyExpandActionUsing) {
            $action = $this->evaluate($this->modifyExpandActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function expandAction(?Closure $callback): static
    {
        $this->modifyExpandActionUsing = $callback;

        return $this;
    }

    public function getExpandActionName(): string
    {
        return 'expand';
    }

    public function getCollapseAllAction(): Action
    {
        $action = Action::make($this->getCollapseAllActionName())
            ->label(__('filament-forms::components.builder.actions.collapse_all.label'))
            ->color('gray')
            ->livewireClickHandlerEnabled(false)
            ->link()
            ->size(Size::Small);

        if ($this->modifyCollapseAllActionUsing) {
            $action = $this->evaluate($this->modifyCollapseAllActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function collapseAllAction(?Closure $callback): static
    {
        $this->modifyCollapseAllActionUsing = $callback;

        return $this;
    }

    public function getCollapseAllActionName(): string
    {
        return 'collapseAll';
    }

    public function getExpandAllAction(): Action
    {
        $action = Action::make($this->getExpandAllActionName())
            ->label(__('filament-forms::components.builder.actions.expand_all.label'))
            ->color('gray')
            ->livewireClickHandlerEnabled(false)
            ->link()
            ->size(Size::Small);

        if ($this->modifyExpandAllActionUsing) {
            $action = $this->evaluate($this->modifyExpandAllActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function expandAllAction(?Closure $callback): static
    {
        $this->modifyExpandAllActionUsing = $callback;

        return $this;
    }

    public function getExpandAllActionName(): string
    {
        return 'expandAll';
    }

    public function getEditAction(): Action
    {
        $action = Action::make($this->getEditActionName())
            ->label(__('filament-forms::components.builder.actions.edit.label'))
            ->modalHeading(__('filament-forms::components.builder.actions.edit.modal.heading'))
            ->modalSubmitActionLabel(__('filament-forms::components.builder.actions.edit.modal.actions.save.label'))
            ->color('gray')
            ->fillForm(function (array $arguments, Builder $component) {
                $state = $component->getState();

                return $state[$arguments['item']]['data'];
            })
            ->schema(function (array $arguments, Builder $component) {
                return $component->getChildSchema($arguments['item'])
                    ->getClone()
                    ->getComponents(withHidden: true);
            })
            ->action(function (array $arguments, Builder $component, $data): void {
                $state = $component->getRawState();

                $state[$arguments['item']]['data'] = $data;

                $component->rawState($state);

                $component->getChildSchema($arguments['item'])->fill($data);

                $component->callAfterStateUpdated();

                $component->shouldPartiallyRenderAfterActionsCalled() ? $component->partiallyRender() : null;
            })
            ->iconButton()
            ->icon(Heroicon::Cog6Tooth)
            ->size(Size::Small)
            ->visible(fn (Builder $component): bool => (! $component->isDisabled()) && $component->hasBlockPreviews());

        if ($this->modifyEditActionUsing) {
            $action = $this->evaluate($this->modifyEditActionUsing, [
                'action' => $action,
            ]) ?? $action;
        }

        return $action;
    }

    public function editAction(?Closure $callback): static
    {
        $this->modifyEditActionUsing = $callback;

        return $this;
    }

    public function getEditActionName(): string
    {
        return 'edit';
    }

    public function truncateBlockLabel(bool | Closure $condition = true): static
    {
        $this->isBlockLabelTruncated = $condition;

        return $this;
    }

    public function addBetweenActionLabel(string | Closure | null $label): static
    {
        $this->addBetweenActionLabel = $label;

        return $this;
    }

    /**
     * @deprecated Use `addBetweenActionLabel()` instead.
     */
    public function createItemBetweenButtonLabel(string | Closure | null $label): static
    {
        $this->addBetweenActionLabel($label);

        return $this;
    }

    public function addActionLabel(string | Closure | null $label): static
    {
        $this->addActionLabel = $label;

        return $this;
    }

    /**
     * @deprecated Use `addActionLabel()` instead.
     */
    public function createItemButtonLabel(string | Closure | null $label): static
    {
        $this->addActionLabel($label);

        return $this;
    }

    public function addable(bool | Closure $condition = true): static
    {
        $this->isAddable = $condition;

        return $this;
    }

    public function deletable(bool | Closure $condition = true): static
    {
        $this->isDeletable = $condition;

        return $this;
    }

    public function reorderable(bool | Closure $condition = true): static
    {
        $this->isReorderable = $condition;

        return $this;
    }

    /**
     * @deprecated Use `addable()` instead.
     */
    public function disableItemCreation(bool | Closure $condition = true): static
    {
        $this->addable(fn (Builder $component): bool => ! $this->evaluate($condition));

        return $this;
    }

    /**
     * @deprecated Use `deletable()` instead.
     */
    public function disableItemDeletion(bool | Closure $condition = true): static
    {
        $this->deletable(fn (Builder $component): bool => ! $this->evaluate($condition));

        return $this;
    }

    /**
     * @deprecated Use `reorderable()` instead.
     */
    public function disableItemMovement(bool | Closure $condition = true): static
    {
        $this->reorderable(fn (Builder $component): bool => ! $this->evaluate($condition));

        return $this;
    }

    /**
     * @deprecated No longer part of the design system.
     */
    public function inset(bool | Closure $condition = true): static
    {
        return $this;
    }

    public function reorderableWithDragAndDrop(bool | Closure $condition = true): static
    {
        $this->isReorderableWithDragAndDrop = $condition;

        return $this;
    }

    public function reorderableWithButtons(bool | Closure $condition = true): static
    {
        $this->isReorderableWithButtons = $condition;

        return $this;
    }

    /**
     * @deprecated Use `blockLabels()` instead.
     */
    public function showBlockLabels(bool | Closure $condition = true): static
    {
        $this->withBlockLabels($condition);

        return $this;
    }

    /**
     * @deprecated Use `blockLabels()` instead.
     */
    public function withBlockLabels(bool | Closure $condition = true): static
    {
        $this->blockLabels($condition);

        return $this;
    }

    /**
     * @deprecated Use `blockNumbers()` instead.
     */
    public function withBlockNumbers(bool | Closure $condition = true): static
    {
        $this->blockNumbers($condition);

        return $this;
    }

    public function blockLabels(bool | Closure $condition = true): static
    {
        $this->hasBlockLabels = $condition;

        return $this;
    }

    public function blockNumbers(bool | Closure $condition = true): static
    {
        $this->hasBlockNumbers = $condition;

        return $this;
    }

    public function blockIcons(bool | Closure $condition = true): static
    {
        $this->hasBlockIcons = $condition;

        return $this;
    }

    public function blockPreviews(bool | Closure $condition = true, bool | Closure $areInteractive = false): static
    {
        $this->hasBlockPreviews = $condition;
        $this->hasInteractiveBlockPreviews = $areInteractive;

        return $this;
    }

    public function getBlock(string $name): ?Block
    {
        return Arr::first(
            $this->getBlocks(),
            fn (Block $block): bool => $block->getName() === $name,
        );
    }

    /**
     * @return array<Block>
     */
    public function getBlocks(): array
    {
        /** @var array<Block> $blocks */
        $blocks = $this->getChildSchema()->getComponents();

        return $blocks;
    }

    /**
     * @return array<Schema>
     */
    public function getItems(): array
    {
        return $this->getCachedDefaultChildSchemas();
    }

    /**
     * @return array<Schema>
     */
    public function getDefaultChildSchemas(): array
    {
        $rawState = $this->getRawState();

        $this->cachedItemsRawStateStructure = $this->getRawStateStructure($rawState);

        $blocks = [];

        foreach ($this->getBlocks() as $block) {
            $blocks[$block->getName()] = $block;
        }

        return collect($rawState)
            ->filter(fn (array $itemData): bool => filled($itemData['type'] ?? null) && array_key_exists($itemData['type'], $blocks))
            ->map(
                fn (array $itemData, $itemIndex): Schema => $blocks[$itemData['type']]
                    ->getChildSchema()
                    ->statePath("{$itemIndex}.data")
                    ->constantState($itemData['data'] ?? [])
                    ->inlineLabel(false)
                    ->getClone(),
            )
            ->all();
    }

    /**
     * Item schemas only depend on the raw state's structure - the item keys, their
     * order, and each item's block type - since fields inside the items read their
     * values from the live raw state. Comparing a structural fingerprint instead of
     * the item values keeps this check cheap when it runs often, and avoids
     * rebuilding the item schemas every time a value inside an item changes.
     */
    protected function areCachedDefaultChildSchemasFresh(): bool
    {
        return $this->cachedItemsRawStateStructure === $this->getRawStateStructure($this->getRawState());
    }

    /**
     * @return array<?string>
     */
    protected function getRawStateStructure(mixed $rawState): array
    {
        return array_map(
            static fn (mixed $itemData): ?string => is_array($itemData) ? ($itemData['type'] ?? null) : null,
            is_array($rawState) ? $rawState : [],
        );
    }

    public function getAddBetweenActionLabel(): string
    {
        return $this->evaluate($this->addBetweenActionLabel) ?? __('filament-forms::components.builder.actions.add_between.label');
    }

    public function getAddActionLabel(): string
    {
        return $this->evaluate($this->addActionLabel) ?? __('filament-forms::components.builder.actions.add.label', [
            'label' => Str::lcfirst($this->getLabel()),
        ]);
    }

    public function hasBlock(string $name): bool
    {
        return (bool) $this->getBlock($name);
    }

    public function isReorderable(): bool
    {
        if ($this->isDisabled()) {
            return false;
        }

        return (bool) $this->evaluate($this->isReorderable);
    }

    public function isReorderableWithDragAndDrop(): bool
    {
        return $this->evaluate($this->isReorderableWithDragAndDrop) && $this->isReorderable();
    }

    public function isReorderableWithButtons(): bool
    {
        return $this->evaluate($this->isReorderableWithButtons) && $this->isReorderable();
    }

    public function isAddable(): bool
    {
        if ($this->isDisabled()) {
            return false;
        }

        if (filled($this->getMaxItems()) && ($this->getMaxItems() <= $this->getItemsCount())) {
            return false;
        }

        return (bool) $this->evaluate($this->isAddable);
    }

    public function isDeletable(): bool
    {
        if ($this->isDisabled()) {
            return false;
        }

        return (bool) $this->evaluate($this->isDeletable);
    }

    public function hasBlockLabels(): bool
    {
        return (bool) $this->evaluate($this->hasBlockLabels);
    }

    public function hasBlockNumbers(): bool
    {
        return (bool) $this->evaluate($this->hasBlockNumbers);
    }

    public function hasBlockIcons(): bool
    {
        return (bool) $this->evaluate($this->hasBlockIcons);
    }

    public function hasBlockPreviews(): bool
    {
        return (bool) $this->evaluate($this->hasBlockPreviews);
    }

    public function hasInteractiveBlockPreviews(): bool
    {
        return (bool) $this->evaluate($this->hasInteractiveBlockPreviews);
    }

    public function canConcealComponents(): bool
    {
        return $this->isCollapsible();
    }

    public function getLabelBetweenItems(): ?string
    {
        return $this->evaluate($this->labelBetweenItems);
    }

    public function isBlockLabelTruncated(): bool
    {
        return (bool) $this->evaluate($this->isBlockLabelTruncated);
    }

    /**
     * @return array<Block>
     */
    public function getBlockPickerBlocks(): array
    {
        $state = $this->getRawState();

        /** @var array<Block> $blocks */
        $blocks = array_filter($this->getBlocks(), function (Block $block) use ($state): bool {
            /** @var Block $block */
            $maxItems = $block->getMaxItems();

            if ($maxItems === null) {
                return true;
            }

            $count = count(array_filter($state, function (array $item) use ($block): bool {
                return $item['type'] === $block->getName();
            }));

            return $count < $maxItems;
        });

        return $blocks;
    }

    /**
     * @param  array<string, int | Closure | null> | int | Closure | null  $columns
     */
    public function blockPickerColumns(array | int | Closure | null $columns = 2): static
    {
        if ($columns instanceof Closure) {
            $this->blockPickerColumns[] = $columns;

            return $this;
        }

        if (! is_array($columns)) {
            $columns = [
                'lg' => $columns,
            ];
        }

        $this->blockPickerColumns = [
            ...($this->blockPickerColumns ?? []),
            ...$columns,
        ];

        return $this;
    }

    /**
     * @return array<string, ?int> | int | null
     */
    public function getBlockPickerColumns(?string $breakpoint = null): array | int | null
    {
        $columns = $this->blockPickerColumns ?? [
            'default' => 1,
            'sm' => null,
            'md' => null,
            'lg' => null,
            'xl' => null,
            '2xl' => null,
        ];

        foreach ($this->blockPickerColumns ?? [] as $columnBreakpoint => $column) {
            $column = $this->evaluate($column);

            if (is_array($column)) {
                $columns = [
                    ...$columns,
                    ...$column,
                ];

                unset($columns[$columnBreakpoint]);

                continue;
            }

            if (blank($columnBreakpoint)) {
                unset($columns[$columnBreakpoint]);

                continue;
            }

            if (! is_string($columnBreakpoint)) {
                $columns['lg'] = $column;

                unset($columns[$columnBreakpoint]);

                continue;
            }

            $columns[$columnBreakpoint] = $column;
        }

        if ($breakpoint !== null) {
            return $columns[$breakpoint] ?? null;
        }

        return $columns;
    }

    public function blockPickerWidth(Width | string | Closure | null $width): static
    {
        $this->blockPickerWidth = $width;

        return $this;
    }

    public function getBlockPickerWidth(): Width | string | null
    {
        $width = $this->evaluate($this->blockPickerWidth);

        if (filled($width)) {
            return $width;
        }

        $columns = $this->getBlockPickerColumns();

        if (empty($columns)) {
            return null;
        }

        return match (max($columns)) {
            2 => 'md',
            3 => '2xl',
            4 => '4xl',
            5 => '6xl',
            6 => '7xl',
            default => null,
        };
    }

    /**
     * @return array<string, mixed>
     */
    public function getItemState(string $key): array
    {
        return $this->getChildSchema($key)->getState(shouldCallHooksBefore: false);
    }

    /**
     * @return array<string, mixed>
     */
    public function getRawItemState(string $key): array
    {
        return $this->getChildSchema($key)->getStateSnapshot();
    }

    public function getHeadingsCount(): int
    {
        if (! $this->hasBlockLabels()) {
            return 0;
        }

        return 1;
    }

    /**
     * @param  array<string, array<mixed>>  $rules
     */
    public function dehydrateValidationRules(array &$rules): void
    {
        parent::dehydrateValidationRules($rules);

        $rules["{$this->getStatePath()}.*.type"] = ['required'];
    }

    public function partiallyRenderAfterActionsCalled(bool | Closure | null $condition = true): static
    {
        $this->shouldPartiallyRenderAfterActionsCalled = $condition;

        return $this;
    }

    public function shouldPartiallyRenderAfterActionsCalled(): bool
    {
        $condition = $this->evaluate($this->shouldPartiallyRenderAfterActionsCalled);

        if ($condition !== null) {
            return (bool) $condition;
        }

        return ! $this->isLive();
    }

    public function blockHeaders(bool | Closure $condition = true): static
    {
        $this->hasBlockHeaders = $condition;

        return $this;
    }

    public function hasBlockHeaders(): bool
    {
        return (bool) $this->evaluate($this->hasBlockHeaders);
    }

    /**
     * @param  array<Block>  $blocks
     * @param  array<string, ?int> | int | null  $columns
     */
    protected function generateBlockPickerHtml(
        Action $action,
        array $blocks,
        string $key,
        string $triggerHtml,
        Alignment | string | null $actionAlignment = null,
        ?string $afterItem = null,
        array | int | null $columns = null,
        Width | string | null $width = null,
    ): string {
        /** @var view-string $publishedView */
        $publishedView = 'filament-forms::components.builder.block-picker';

        if (ViewComponent::hasPublishedEmbeddedViewOverride($publishedView)) {
            return view($publishedView, [
                'action' => $action,
                'actionAlignment' => $actionAlignment,
                'afterItem' => $afterItem,
                'blocks' => $blocks,
                'columns' => $columns,
                'key' => $key,
                'trigger' => new HtmlString($triggerHtml),
                'width' => $width,
            ])->render();
        }

        $placement = match ($actionAlignment) {
            Alignment::Start, Alignment::Left => 'bottom-start',
            Alignment::End, Alignment::Right => 'bottom-end',
            default => null,
        };

        $alignmentClass = ($actionAlignment instanceof Alignment)
            ? ('fi-align-' . $actionAlignment->value)
            : (is_string($actionAlignment) ? $actionAlignment : null);

        if (is_string($width)) {
            $width = Width::tryFrom($width) ?? $width;
        }

        $widthClass = ($width instanceof Width)
            ? "fi-width-{$width->value}"
            : (is_string($width) ? $width : null);

        $xFloatDirective = 'x-float' . ($placement ? ".placement.{$placement}" : '') . '.flip.shift.offset';

        $dropdownAttributes = (new FilamentComponentAttributeBag)
            ->merge(['x-data' => 'filamentDropdown'], escape: false)
            ->class([
                'fi-dropdown',
                'fi-fo-builder-block-picker',
                $alignmentClass,
            ]);

        $panelAttributes = (new FilamentComponentAttributeBag)
            ->merge([
                'x-cloak' => true,
                'x-ref' => 'panel',
                'x-transition:enter-start' => 'fi-opacity-0',
                'x-transition:leave-end' => 'fi-opacity-0',
                $xFloatDirective => '{ offset: 8 }',
            ], escape: false)
            ->class([
                'fi-dropdown-panel',
                $widthClass,
            ]);

        $loadingDelay = config('filament.livewire_loading_delay', 'default');

        ob_start(); ?>

        <div <?= $dropdownAttributes->toHtml() ?>>
            <div
                x-on:keyup.enter="toggle($event)"
                x-on:keyup.space="toggle($event)"
                x-on:mousedown="if ($event.button === 0) toggle($event)"
                class="fi-dropdown-trigger"
            >
                <?= $triggerHtml ?>
            </div>

            <?php if (filled($blocks)) { ?>
                <div <?= $panelAttributes->toHtml() ?>>
                    <div class="fi-dropdown-list">
                        <div <?= (new FilamentComponentAttributeBag)->grid($columns, GridDirection::Column)->toHtml() ?>>
                            <?php foreach ($blocks as $block) {
                                $blockIcon = $block->getIcon();

                                $wireClickArguments = ['block' => $block->getName()];

                                if (filled($afterItem)) {
                                    $wireClickArguments['afterItem'] = $afterItem;
                                }

                                $wireClick = "mountAction('{$action->getName()}', " . Js::from($wireClickArguments) . ", { schemaComponent: '{$key}' })";

                                $itemAttributes = (new FilamentComponentAttributeBag)
                                    ->merge([
                                        'x-on:click' => 'close',
                                        'wire:click' => $wireClick,
                                        'type' => 'button',
                                        'wire:loading.attr' => 'disabled',
                                        'wire:target' => $wireClick,
                                    ], escape: false)
                                    ->class(['fi-dropdown-list-item'])
                                    ->color(ItemComponent::class, 'gray');
                                ?>

                            <button <?= $itemAttributes->toHtml() ?>>
                                <?php if (filled($blockIcon)) { ?>
                                    <?= generate_icon_html($blockIcon, attributes: (new FilamentComponentAttributeBag([
                                        'wire:loading.remove.delay.' . $loadingDelay => true,
                                        'wire:target' => $wireClick,
                                    ]))->color(IconComponent::class, 'gray'))?->toHtml() ?>
                                <?php } ?>

                                <?= generate_loading_indicator_html((new FilamentComponentAttributeBag([
                                    'wire:loading.delay.' . $loadingDelay => '',
                                    'wire:target' => $wireClick,
                                ]))->color(IconComponent::class, 'gray'))->toHtml() ?>

                                <span class="fi-dropdown-list-item-label">
                                    <?= e($block->getLabel()) ?>
                                </span>
                            </button>
                            <?php } ?>
                        </div>
                    </div>
                </div>
            <?php } ?>
        </div>

        <?php return ob_get_clean();
    }

    public function toEmbeddedHtml(): string
    {
        $items = $this->getItems();

        // Filter before counting so `$itemCount` agrees with the loop's
        // `$isFirst` / `$isLast` calculations.
        $items = array_filter(
            $items,
            static fn ($item): bool => $item->getParentComponent() instanceof Block,
        );

        $blockPickerBlocks = $this->getBlockPickerBlocks();
        $blockPickerColumns = $this->getBlockPickerColumns();
        $blockPickerWidth = $this->getBlockPickerWidth();
        $hasBlockPreviews = $this->hasBlockPreviews();
        $hasInteractiveBlockPreviews = $this->hasInteractiveBlockPreviews();

        $addAction = $this->getAction($this->getAddActionName());
        $addActionAlignment = $this->getAddActionAlignment();
        $addBetweenAction = $this->getAction($this->getAddBetweenActionName());
        $cloneAction = $this->getAction($this->getCloneActionName());
        $collapseAllAction = $this->getAction($this->getCollapseAllActionName());
        $editAction = $this->getAction($this->getEditActionName());
        $expandAllAction = $this->getAction($this->getExpandAllActionName());
        $deleteAction = $this->getAction($this->getDeleteActionName());
        $moveDownAction = $this->getAction($this->getMoveDownActionName());
        $moveUpAction = $this->getAction($this->getMoveUpActionName());
        $reorderAction = $this->getAction($this->getReorderActionName());
        $extraItemActions = $this->getExtraItemActions();

        $isAddable = $this->isAddable();
        $isCloneable = $this->isCloneable();
        $isCollapsible = $this->isCollapsible();
        $isDeletable = $this->isDeletable();
        $isReorderableWithButtons = $this->isReorderableWithButtons();
        $isReorderableWithDragAndDrop = $this->isReorderableWithDragAndDrop();

        $collapseAllActionIsVisible = $isCollapsible && $collapseAllAction->isVisible();
        $expandAllActionIsVisible = $isCollapsible && $expandAllAction->isVisible();
        $persistCollapsed = $this->shouldPersistCollapsed();

        $key = $this->getKey();
        $statePath = $this->getStatePath();

        $blockLabelHeadingTag = $this->getHeadingTag();
        $isBlockLabelTruncated = $this->isBlockLabelTruncated();
        $labelBetweenItems = $this->getLabelBetweenItems();

        $id = $this->getId();

        $outerAttributes = (new FilamentComponentAttributeBag)
            ->merge($this->getExtraAttributes(), escape: false)
            ->merge([
                'aria-labelledby' => "{$id}-label",
                'id' => $id,
                'role' => 'group',
            ], escape: false)
            ->class([
                'fi-fo-builder',
                'fi-collapsible' => $isCollapsible,
            ]);

        $itemCount = count($items);
        $itemIndex = 0;

        $hasBlockLabels = $this->hasBlockLabels();
        $hasBlockIcons = $this->hasBlockIcons();
        $hasBlockNumbers = $this->hasBlockNumbers();
        $hasBlockHeaders = $this->hasBlockHeaders();

        ob_start(); ?>

        <div <?= $outerAttributes->toHtml() ?>>
            <?php if ($collapseAllActionIsVisible || $expandAllActionIsVisible) { ?>
                <div
                    <?= (new FilamentComponentAttributeBag)->class([
                        'fi-fo-builder-actions',
                        'fi-hidden' => $itemCount < 2,
                    ])->toHtml() ?>
                >
                    <?php if ($collapseAllActionIsVisible) { ?>
                        <span x-on:click="$dispatch('builder-collapse', '<?= e($statePath) ?>')">
                            <?= $collapseAllAction->toHtml() ?>
                        </span>
                    <?php } ?>

                    <?php if ($expandAllActionIsVisible) { ?>
                        <span x-on:click="$dispatch('builder-expand', '<?= e($statePath) ?>')">
                            <?= $expandAllAction->toHtml() ?>
                        </span>
                    <?php } ?>
                </div>
            <?php } ?>

            <?php if ($itemCount) { ?>
                <ul
                    x-sortable
                    data-sortable-animation-duration="<?= e($this->getReorderAnimationDuration()) ?>"
                    x-on:end.stop="$wire.mountAction('reorder', { items: $event.target.sortable.toArray() }, { schemaComponent: '<?= e($key) ?>' })"
                    class="fi-fo-builder-items"
                >
                    <?php foreach ($items as $itemKey => $item) {
                        /** @var Block $block */
                        $block = $item->getParentComponent();

                        $itemIndex++;
                        $isFirst = $itemIndex === 1;
                        $isLast = $itemIndex === $itemCount;

                        $visibleExtraItemActions = array_filter(
                            $extraItemActions,
                            fn (Action $action): bool => $action(['item' => $itemKey])->isVisible(),
                        );
                        $itemCloneAction = $cloneAction(['item' => $itemKey]);
                        $cloneActionIsVisible = $isCloneable && $itemCloneAction->isVisible();
                        $itemDeleteAction = $deleteAction(['item' => $itemKey]);
                        $deleteActionIsVisible = $isDeletable && $itemDeleteAction->isVisible();
                        $itemEditAction = $editAction(['item' => $itemKey]);
                        $editActionIsVisible = $hasBlockPreviews && $itemEditAction->isVisible();
                        $itemMoveDownAction = $moveDownAction(['item' => $itemKey])->disabled($isLast);
                        $moveDownActionIsVisible = $isReorderableWithButtons && $itemMoveDownAction->isVisible();
                        $itemMoveUpAction = $moveUpAction(['item' => $itemKey])->disabled($isFirst);
                        $moveUpActionIsVisible = $isReorderableWithButtons && $itemMoveUpAction->isVisible();
                        $reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
                        $hasItemHeader = $hasBlockHeaders && ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible || $hasBlockIcons || $hasBlockLabels || $editActionIsVisible || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions);
                        ?>

                        <li
                            wire:ignore.self
                            wire:key="<?= e($item->getLivewireKey()) ?>.item"
                            x-data="{
                                isCollapsed: <?php if ($persistCollapsed) { ?>$persist(<?= Js::from($this->isCollapsed($item)) ?>).as(`builder-${<?= Js::from($key) ?>}-${<?= Js::from($itemKey) ?>}-isCollapsed`)<?php } else { ?><?= Js::from($this->isCollapsed($item)) ?><?php } ?>,
                            }"
                            x-on:builder-expand.window="$event.detail === '<?= e($statePath) ?>' && (isCollapsed = false)"
                            x-on:builder-collapse.window="$event.detail === '<?= e($statePath) ?>' && (isCollapsed = true)"
                            x-on:expand="isCollapsed = false"
                            x-sortable-item="<?= e($itemKey) ?>"
                            <?= $block->getExtraAttributeBag()
                                ->class([
                                    'fi-fo-builder-item',
                                    'fi-fo-builder-item-has-header' => $hasItemHeader,
                                ])->toHtml() ?>
                            x-bind:class="{ 'fi-collapsed': isCollapsed }"
                        >
                            <?php if ($hasItemHeader) { ?>
                                <div
                                    <?php if ($isCollapsible) { ?>
                                        x-on:click.stop="isCollapsed = !isCollapsed"
                                    <?php } ?>
                                    class="fi-fo-builder-item-header"
                                >
                                    <?php if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible) { ?>
                                        <ul class="fi-fo-builder-item-header-start-actions">
                                            <?php if ($reorderActionIsVisible) { ?>
                                                <li x-on:click.stop>
                                                    <?= $reorderAction->extraAttributes(['x-sortable-handle' => true], merge: true)->toHtml() ?>
                                                </li>
                                            <?php } ?>

                                            <?php if ($moveUpActionIsVisible || $moveDownActionIsVisible) { ?>
                                                <li x-on:click.stop><?= $itemMoveUpAction->toHtml() ?></li>
                                                <li x-on:click.stop><?= $itemMoveDownAction->toHtml() ?></li>
                                            <?php } ?>
                                        </ul>
                                    <?php } ?>

                                    <?php
                                        $blockIcon = $block->getIcon();
                                ?>

                                    <?php if ($hasBlockIcons && filled($blockIcon)) { ?>
                                        <?= generate_icon_html($blockIcon, attributes: (new FilamentComponentAttributeBag)->class(['fi-fo-builder-item-header-icon']))?->toHtml() ?>
                                    <?php } ?>

                                    <?php if ($hasBlockLabels) { ?>
                                        <<?= e($blockLabelHeadingTag) ?>
                                            <?= (new FilamentComponentAttributeBag)->class([
                                                'fi-fo-builder-item-header-label',
                                                'fi-truncated' => $isBlockLabelTruncated,
                                            ])->toHtml() ?>
                                        >
                                            <?= e($block->getLabel($item->getRawState(), $itemKey, $itemIndex - 1)) ?>
                                            <?php if ($hasBlockNumbers) { ?>
                                                <?= e($itemIndex) ?>
                                            <?php } ?>
                                        </<?= e($blockLabelHeadingTag) ?>>
                                    <?php } ?>

                                    <?php if ($editActionIsVisible || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions) { ?>
                                        <ul class="fi-fo-builder-item-header-end-actions">
                                            <?php foreach ($visibleExtraItemActions as $extraItemAction) { ?>
                                                <li x-on:click.stop><?= $extraItemAction(['item' => $itemKey])->toHtml() ?></li>
                                            <?php } ?>

                                            <?php if ($editActionIsVisible) { ?>
                                                <li x-on:click.stop><?= $itemEditAction->toHtml() ?></li>
                                            <?php } ?>

                                            <?php if ($cloneActionIsVisible) { ?>
                                                <li x-on:click.stop><?= $itemCloneAction->toHtml() ?></li>
                                            <?php } ?>

                                            <?php if ($deleteActionIsVisible) { ?>
                                                <li x-on:click.stop><?= $itemDeleteAction->toHtml() ?></li>
                                            <?php } ?>

                                            <?php if ($isCollapsible) { ?>
                                                <li class="fi-fo-builder-item-header-collapsible-actions" x-on:click.stop="isCollapsed = !isCollapsed">
                                                    <div class="fi-fo-builder-item-header-collapse-action">
                                                        <?= $this->getAction('collapse')->toHtml() ?>
                                                    </div>
                                                    <div class="fi-fo-builder-item-header-expand-action">
                                                        <?= $this->getAction('expand')->toHtml() ?>
                                                    </div>
                                                </li>
                                            <?php } ?>
                                        </ul>
                                    <?php } ?>
                                </div>
                            <?php } ?>

                            <div
                                x-show="! isCollapsed"
                                <?= (new FilamentComponentAttributeBag)->class([
                                    'fi-fo-builder-item-content',
                                    'fi-fo-builder-item-content-has-preview' => $hasBlockPreviews && $block->hasPreview(),
                                ])->toHtml() ?>
                            >
                                <?php if ($hasBlockPreviews && $block->hasPreview()) { ?>
                                    <div
                                        <?= (new FilamentComponentAttributeBag)->class([
                                            'fi-fo-builder-item-preview',
                                            'fi-interactive' => $hasInteractiveBlockPreviews,
                                        ])->toHtml() ?>
                                    >
                                        <?= $block->renderPreview($item->getRawState())->render() ?>
                                    </div>

                                    <?php if ($editActionIsVisible && (! $hasInteractiveBlockPreviews)) { ?>
                                        <div
                                            class="fi-fo-builder-item-preview-edit-overlay"
                                            role="button"
                                            x-on:click.stop="<?= e('$wire.mountAction(\'edit\', { item: \'' . $itemKey . '\' }, { schemaComponent: \'' . $key . '\' })') ?>"
                                        ></div>
                                    <?php } ?>
                                <?php } else { ?>
                                    <?= $item->toHtml() ?>
                                <?php } ?>
                            </div>
                        </li>

                        <?php if (! $isLast) { ?>
                            <?php if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible()) { ?>
                                <li class="fi-fo-builder-add-between-items-ctn">
                                    <div class="fi-fo-builder-add-between-items">
                                        <div class="fi-fo-builder-block-picker-ctn">
                                            <?= $this->generateBlockPickerHtml(
                                                action: $addBetweenAction,
                                                blocks: $blockPickerBlocks,
                                                key: $key,
                                                triggerHtml: $addBetweenAction(['afterItem' => $itemKey])->toHtml(),
                                                afterItem: $itemKey,
                                                columns: $blockPickerColumns,
                                                width: $blockPickerWidth,
                                            ) ?>
                                        </div>
                                    </div>
                                </li>
                            <?php } elseif (filled($labelBetweenItems)) { ?>
                                <li class="fi-fo-builder-label-between-items-ctn">
                                    <div class="fi-fo-builder-label-between-items-divider-before"></div>
                                    <span class="fi-fo-builder-label-between-items"><?= e($labelBetweenItems) ?></span>
                                    <div class="fi-fo-builder-label-between-items-divider-after"></div>
                                </li>
                            <?php } ?>
                        <?php } ?>
                    <?php } ?>
                </ul>
            <?php } ?>

            <?php if ($isAddable && $addAction->isVisible()) { ?>
                <?= $this->generateBlockPickerHtml(
                    action: $addAction,
                    blocks: $blockPickerBlocks,
                    key: $key,
                    triggerHtml: $addAction->toHtml(),
                    actionAlignment: $addActionAlignment,
                    columns: $blockPickerColumns,
                    width: $blockPickerWidth,
                ) ?>
            <?php } ?>
        </div>

        <?php return $this->wrapEmbeddedHtml(ob_get_clean(), labelTag: 'div');
    }
}
