<?php

namespace Filament\Actions\Concerns;

use Closure;
use Filament\Actions\Contracts\HasRecord;
use Filament\Actions\MountableAction;
use Filament\Actions\StaticAction;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\MaxWidth;
use Filament\Support\View\Components\Modal;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;

trait CanOpenModal
{
    /**
     * @var array<string, StaticAction>
     */
    protected array $cachedExtraModalFooterActions;

    /**
     * @var array<StaticAction> | Closure
     */
    protected array | Closure $extraModalFooterActions = [];

    protected bool | Closure | null $isModalFooterSticky = null;

    protected bool | Closure | null $isModalHeaderSticky = null;

    /**
     * @var array<string, StaticAction>
     */
    protected array $cachedModalActions;

    /**
     * @var array<StaticAction | Closure>
     */
    protected array $modalActions = [];

    protected bool | Closure $isModalSlideOver = false;

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

    /**
     * @var array<string, StaticAction>
     */
    protected array $cachedModalFooterActions;

    /**
     * @var array<StaticAction> | Closure | null
     */
    protected array | Closure | null $modalFooterActions = null;

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

    protected StaticAction | bool | Closure | null $modalCancelAction = null;

    protected string | Closure | null $modalCancelActionLabel = null;

    protected StaticAction | bool | Closure | null $modalSubmitAction = null;

    protected string | Closure | null $modalSubmitActionLabel = null;

    protected View | Htmlable | Closure | null $modalContent = null;

    protected View | Htmlable | Closure | null $modalContentFooter = null;

    protected string | Htmlable | Closure | null $modalHeading = null;

    protected string | Htmlable | Closure | null $modalDescription = null;

    protected MaxWidth | string | Closure | null $modalWidth = null;

    protected bool | Closure | null $hasModal = null;

    protected bool | Closure | null $isModalHidden = null;

    protected bool | Closure | null $hasModalCloseButton = null;

    protected bool | Closure | null $isModalClosedByClickingAway = null;

    protected bool | Closure | null $isModalClosedByEscaping = null;

    protected bool | Closure | null $isModalAutofocused = null;

    protected string | Closure | null $modalIcon = null;

    /**
     * @var string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | Closure | null
     */
    protected string | array | Closure | null $modalIconColor = null;

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

        return $this;
    }

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

        return $this;
    }

    /**
     * @deprecated Use `modalAlignment(Alignment::Center)` instead.
     */
    public function centerModal(bool | Closure | null $condition = true): static
    {
        if ($this->evaluate($condition)) {
            $this->modalAlignment(Alignment::Center);
        }

        return $this;
    }

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

        return $this;
    }

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

        return $this;
    }

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

        return $this;
    }

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

        return $this;
    }

    /**
     * @param  string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | Closure | null  $color
     */
    public function modalIconColor(string | array | Closure | null $color = null): static
    {
        $this->modalIconColor = $color;

        return $this;
    }

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

        return $this;
    }

    /**
     * @deprecated Use `modalFooterActions()` instead.
     *
     * @param  array<StaticAction> | Closure | null  $actions
     */
    public function modalActions(array | Closure | null $actions = null): static
    {
        $this->modalFooterActions($actions);

        return $this;
    }

    /**
     * @param  array<StaticAction> | Closure | null  $actions
     */
    public function modalFooterActions(array | Closure | null $actions = null): static
    {
        $this->modalFooterActions = $actions;

        return $this;
    }

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

        return $this;
    }

    /**
     * @deprecated Use `extraModalFooterActions()` instead.
     *
     * @param  array<StaticAction> | Closure  $actions
     */
    public function extraModalActions(array | Closure $actions): static
    {
        $this->extraModalFooterActions($actions);

        return $this;
    }

    /**
     * @param  array<StaticAction> | Closure  $actions
     */
    public function extraModalFooterActions(array | Closure $actions): static
    {
        $this->extraModalFooterActions = $actions;

        return $this;
    }

    /**
     * @param  array<StaticAction | Closure>  $actions
     */
    public function registerModalActions(array $actions): static
    {
        $this->modalActions = [
            ...$this->modalActions,
            ...$actions,
        ];

        return $this;
    }

    public function modalSubmitAction(StaticAction | bool | Closure | null $action = null): static
    {
        $this->modalSubmitAction = $action;

        return $this;
    }

    public function modalCancelAction(StaticAction | bool | Closure | null $action = null): static
    {
        $this->modalCancelAction = $action;

        return $this;
    }

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

        return $this;
    }

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

        return $this;
    }

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

        return $this;
    }

    public function modalContent(View | Htmlable | Closure | null $content = null): static
    {
        $this->modalContent = $content;

        return $this;
    }

    /**
     * @deprecated Use `modalContentFooter()` instead.
     */
    public function modalFooter(View | Htmlable | Closure | null $footer = null): static
    {
        return $this->modalContentFooter($footer);
    }

    public function modalContentFooter(View | Htmlable | Closure | null $footer = null): static
    {
        $this->modalContentFooter = $footer;

        return $this;
    }

    public function modalHeading(string | Htmlable | Closure | null $heading = null): static
    {
        $this->modalHeading = $heading;

        return $this;
    }

    public function modalDescription(string | Htmlable | Closure | null $description = null): static
    {
        $this->modalDescription = $description;

        return $this;
    }

    /**
     * @deprecated Use `modalDescription()` instead.
     */
    public function modalSubheading(string | Htmlable | Closure | null $subheading = null): static
    {
        $this->modalDescription($subheading);

        return $this;
    }

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

        return $this;
    }

    public function getLivewireCallMountedActionName(): ?string
    {
        return null;
    }

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

        return $this;
    }

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

        return $this;
    }

    /**
     * @return array<string, StaticAction>
     */
    public function getModalFooterActions(): array
    {
        if ($this->isWizard()) {
            return [];
        }

        if (isset($this->cachedModalFooterActions)) {
            return $this->cachedModalFooterActions;
        }

        if ($this->modalFooterActions) {
            $actions = [];

            foreach ($this->evaluate($this->modalFooterActions) as $modalAction) {
                $actions[$modalAction->getName()] = $this->prepareModalAction($modalAction);
            }

            return $this->cachedModalFooterActions = $actions;
        }

        $actions = [];

        if ($submitAction = $this->getModalSubmitAction()) {
            $actions['submit'] = $submitAction;
        }

        $actions = [
            ...$actions,
            ...$this->getExtraModalFooterActions(),
        ];

        if ($cancelAction = $this->getModalCancelAction()) {
            $actions['cancel'] = $cancelAction;
        }

        if (in_array($this->getModalFooterActionsAlignment(), [Alignment::Center, 'center'])) {
            $actions = array_reverse($actions);
        }

        return $this->cachedModalFooterActions = $actions;
    }

    public function getModalFooterActionsAlignment(): string | Alignment | null
    {
        return $this->evaluate($this->modalFooterActionsAlignment);
    }

    /**
     * @return array<string, StaticAction>
     */
    public function getModalActions(): array
    {
        if (isset($this->cachedModalActions)) {
            return $this->cachedModalActions;
        }

        $actions = $this->getModalFooterActions();

        foreach ($this->modalActions as $action) {
            foreach (Arr::wrap($this->evaluate($action)) as $modalAction) {
                $actions[$modalAction->getName()] = $this->prepareModalAction($modalAction);
            }
        }

        return $this->cachedModalActions = $actions;
    }

    public function getModalAction(string $name): ?StaticAction
    {
        return $this->getModalActions()[$name] ?? null;
    }

    public function getMountableModalAction(string $name): ?MountableAction
    {
        $action = $this->getModalAction($name);

        if (! $action) {
            return null;
        }

        if (! $action instanceof MountableAction) {
            return null;
        }

        return $action;
    }

    public function prepareModalAction(StaticAction $action): StaticAction
    {
        if (! $action instanceof MountableAction) {
            return $action;
        }

        $action->livewire($this->getLivewire());

        if (
            ($this instanceof HasRecord) &&
            ($action instanceof HasRecord) &&
            (! $action->hasRecord())
        ) {
            $action->record($this->getRecord());
        }

        return $action;
    }

    /**
     * @return array<StaticAction>
     */
    public function getVisibleModalFooterActions(): array
    {
        return array_filter(
            $this->getModalFooterActions(),
            fn (StaticAction $action): bool => $action->isVisible(),
        );
    }

    public function getModalSubmitAction(): ?StaticAction
    {
        $action = static::makeModalAction('submit')
            ->label($this->getModalSubmitActionLabel())
            ->submit($this->getLivewireCallMountedActionName())
            ->color(match ($color = $this->getColor()) {
                'gray' => 'primary',
                default => $color,
            });

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

        if ($action === false) {
            return null;
        }

        return $action;
    }

    public function getModalCancelAction(): ?StaticAction
    {
        $action = static::makeModalAction('cancel')
            ->label($this->getModalCancelActionLabel())
            ->close()
            ->color('gray');

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

        if ($action === false) {
            return null;
        }

        return $action;
    }

    /**
     * @return array<StaticAction>
     */
    public function getExtraModalFooterActions(): array
    {
        if (isset($this->cachedExtraModalFooterActions)) {
            return $this->cachedExtraModalFooterActions;
        }

        $actions = [];

        foreach ($this->evaluate($this->extraModalFooterActions) as $action) {
            $actions[$action->getName()] = $this->prepareModalAction($action);
        }

        return $this->cachedExtraModalFooterActions = $actions;
    }

    public function getModalAlignment(): Alignment | string
    {
        return $this->evaluate($this->modalAlignment) ?? (in_array($this->getModalWidth(), [MaxWidth::ExtraSmall, MaxWidth::Small, 'xs', 'sm']) ? Alignment::Center : Alignment::Start);
    }

    public function getModalSubmitActionLabel(): string
    {
        return $this->evaluate($this->modalSubmitActionLabel) ?? __('filament-actions::modal.actions.submit.label');
    }

    public function getModalCancelActionLabel(): string
    {
        return $this->evaluate($this->modalCancelActionLabel) ?? __('filament-actions::modal.actions.cancel.label');
    }

    public function getModalContent(): View | Htmlable | null
    {
        return $this->evaluate($this->modalContent);
    }

    public function getModalContentFooter(): View | Htmlable | null
    {
        return $this->evaluate($this->modalContentFooter);
    }

    public function hasModalContent(): bool
    {
        return $this->modalContent !== null;
    }

    public function hasModalContentFooter(): bool
    {
        return $this->modalContentFooter !== null;
    }

    public function getCustomModalHeading(): string | Htmlable | null
    {
        return $this->evaluate($this->modalHeading);
    }

    public function getModalHeading(): string | Htmlable
    {
        return $this->getCustomModalHeading() ?? $this->getLabel();
    }

    public function hasCustomModalHeading(): bool
    {
        return filled($this->getCustomModalHeading());
    }

    public function getModalDescription(): string | Htmlable | null
    {
        return $this->evaluate($this->modalDescription);
    }

    public function hasModalDescription(): bool
    {
        return filled($this->getModalDescription());
    }

    public function getModalWidth(): MaxWidth | string
    {
        return $this->evaluate($this->modalWidth) ?? MaxWidth::FourExtraLarge;
    }

    public function isModalFooterSticky(): bool
    {
        return (bool) ($this->evaluate($this->isModalFooterSticky) ?? $this->isModalSlideOver());
    }

    public function isModalHeaderSticky(): bool
    {
        return (bool) ($this->evaluate($this->isModalHeaderSticky) ?? $this->isModalSlideOver());
    }

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

    public function shouldOpenModal(?Closure $checkForFormUsing = null): bool
    {
        if (is_bool($hasModal = $this->evaluate($this->hasModal))) {
            return $hasModal;
        }

        if ($this->evaluate($this->isModalHidden)) {
            return false;
        }

        return $this->hasCustomModalHeading() ||
            $this->hasModalDescription() ||
            $this->hasModalContent() ||
            $this->hasModalContentFooter() ||
            $this->getInfolist() ||
            (value($checkForFormUsing, $this) ?? false);
    }

    public function hasModalCloseButton(): bool
    {
        return $this->evaluate($this->hasModalCloseButton) ?? Modal::$hasCloseButton;
    }

    public function isModalClosedByClickingAway(): bool
    {
        return (bool) ($this->evaluate($this->isModalClosedByClickingAway) ?? Modal::$isClosedByClickingAway);
    }

    public function isModalClosedByEscaping(): bool
    {
        return (bool) ($this->evaluate($this->isModalClosedByEscaping) ?? Modal::$isClosedByEscaping);
    }

    public function isModalAutofocused(): bool
    {
        return $this->evaluate($this->isModalAutofocused) ?? Modal::$isAutofocused;
    }

    /**
     * @deprecated Use `makeModalSubmitAction()` instead.
     *
     * @param  array<string, mixed> | null  $arguments
     */
    public function makeExtraModalAction(string $name, ?array $arguments = null): StaticAction
    {
        return $this->makeModalSubmitAction($name, $arguments);
    }

    /**
     * @param  array<string, mixed> | null  $arguments
     */
    public function makeModalSubmitAction(string $name, ?array $arguments = null): StaticAction
    {
        return static::makeModalAction($name)
            ->callParent($this->getLivewireCallMountedActionName())
            ->arguments($arguments)
            ->color('gray');
    }

    public function makeModalAction(string $name): StaticAction
    {
        return StaticAction::make($name)
            ->button();
    }

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

    /**
     * @return string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null
     */
    public function getModalIconColor(): string | array | null
    {
        return $this->evaluate($this->modalIconColor) ?? $this->getColor() ?? 'primary';
    }

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

        return $this;
    }

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

        return $this;
    }
}
