<?php

declare(strict_types=1);

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace TYPO3\CMS\Backend\Template\Components;

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
use TYPO3\CMS\Backend\Template\Components\Buttons\PositionInterface;

/**
 * Container for managing buttons in the backend module document header.
 * The ButtonBar organizes buttons into two positions (left and right) with multiple groups,
 * allowing precise control over button placement and visual grouping.
 *
 * Buttons are organized in a three-level structure:
 * 1. Position: LEFT or RIGHT side of the header
 * 2. Group: Numerical groups within each position (1, 2, 3, etc.)
 * 3. Individual buttons within each group
 *
 * Groups are rendered with visual spacing between them, allowing logical grouping
 * of related actions. Lower group numbers appear first (left-to-right, or top-to-bottom on mobile).
 *
 * Example - Using pre-configured buttons from ComponentFactory:
 *
 * ```
 * public function __construct(
 *     protected readonly ComponentFactory $componentFactory,
 * ) {}
 *
 * public function myAction(): ResponseInterface
 * {
 *     $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
 *
 *     // Use pre-configured save button
 *     $saveButton = $this->componentFactory->createSaveButton('editform');
 *     $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
 *
 *     // Use pre-configured back button
 *     $backButton = $this->componentFactory->createBackButton($returnUrl);
 *     $buttonBar->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
 * }
 * ```
 *
 * Example - Creating custom buttons with ComponentFactory:
 *
 * ```
 * public function __construct(
 *     protected readonly ComponentFactory $componentFactory,
 * ) {}
 *
 * public function myAction(): ResponseInterface
 * {
 *     $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
 *
 *     // Create custom link button
 *     $customButton = $this->componentFactory->createLinkButton()
 *         ->setHref('/custom-action')
 *         ->setTitle('Custom Action')
 *         ->setIcon($iconFactory->getIcon('actions-custom'));
 *     $buttonBar->addButton($customButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
 * }
 * ```
 *
 * Example - Buttons with automatic positioning:
 *
 * ```
 * // ShortcutButton implements PositionInterface and positions itself automatically
 * $shortcutButton = $this->componentFactory->createShortcutButton()
 *     ->setRouteIdentifier('my_module')
 *     ->setDisplayName('My Module');
 * $buttonBar->addButton($shortcutButton); // Position and group are automatic
 * ```
 *
 * Example - Dropdown button:
 *
 * ```
 * $dropdownButton = $this->componentFactory->createDropDownButton()
 *     ->setLabel('Actions')
 *     ->setIcon($iconFactory->getIcon('actions-menu'));
 *
 * $item1 = $this->componentFactory->createDropDownItem()
 *     ->setLabel('Edit')
 *     ->setHref('/edit');
 * $dropdownButton->addItem($item1);
 *
 * $item2 = $this->componentFactory->createDropDownItem()
 *     ->setLabel('Delete')
 *     ->setHref('/delete');
 * $dropdownButton->addItem($item2);
 *
 * $buttonBar->addButton($dropdownButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
 * ```
 *
 * @phpstan-type Buttons array<self::BUTTON_POSITION_*, array<int, list<ButtonInterface>>>
 */
#[Autoconfigure(public: true)]
class ButtonBar
{
    /**
     * Position constant for left side of the button bar
     */
    public const BUTTON_POSITION_LEFT = 'left';

    /**
     * Position constant for right side of the button bar
     */
    public const BUTTON_POSITION_RIGHT = 'right';

    /**
     * Internal array of all registered buttons
     *
     * @var Buttons
     */
    protected array $buttons = [];

    public function __construct(
        protected readonly EventDispatcherInterface $eventDispatcher,
    ) {}

    /**
     * Add a new button
     *
     * Buttons implementing PositionInterface will automatically use their own
     * predefined position and group, ignoring the $buttonPosition and $buttonGroup
     * parameters. This ensures buttons like ShortcutButton always appear in their
     * designated location.
     *
     * @param ButtonInterface $button The Button Object to add
     * @param self::BUTTON_POSITION_* $buttonPosition Position of the button (left/right). Ignored if button implements PositionInterface.
     * @param int $buttonGroup Buttongroup of the button. Ignored if button implements PositionInterface.
     *
     * @throws \InvalidArgumentException In case a button is not valid
     */
    public function addButton(
        ButtonInterface $button,
        string $buttonPosition = self::BUTTON_POSITION_LEFT,
        int $buttonGroup = 1
    ): static {
        if (!$button->isValid()) {
            throw new \InvalidArgumentException('Button "' . $button->getType() . '" is not valid', 1441706370);
        }
        // Buttons implementing PositionInterface define their own position and group
        if ($button instanceof PositionInterface) {
            $buttonPosition = $button->getPosition();
            $buttonGroup = $button->getGroup();
        }
        // We make the button immutable here
        $this->buttons[$buttonPosition][$buttonGroup][] = clone $button;
        return $this;
    }

    /**
     * Returns an associative array of all buttons in the form of
     * ButtonPosition > ButtonGroup > Button
     */
    public function getButtons(ServerRequestInterface $request): array
    {
        // here we need to call the sorting methods and stuff.
        foreach ($this->buttons as $position => $_) {
            ksort($this->buttons[$position]);
        }

        // Dispatch event for manipulating the docHeaderButtons
        $this->buttons = $this->eventDispatcher->dispatch(new ModifyButtonBarEvent($this->buttons, $this, $request))->getButtons();

        return $this->buttons;
    }

    /**
     * Checks whether a button of the specified type has been added to the button bar.
     *
     * This is primarily used for backwards compatibility detection to prevent
     * duplicate automatic buttons when controllers manually add them.
     *
     * @param class-string<ButtonInterface> $buttonClassName Fully qualified button class name
     * @return bool True if a button of this type exists, false otherwise
     */
    public function hasButtonOfType(string $buttonClassName): bool
    {
        foreach ($this->buttons as $groups) {
            foreach ($groups as $groupButtons) {
                foreach ($groupButtons as $button) {
                    if ($button instanceof $buttonClassName) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}
