<?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\Breadcrumb;

use Psr\Log\LoggerInterface;
use TYPO3\CMS\Backend\Dto\Breadcrumb\BreadcrumbNode;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Domain\RecordFactory;
use TYPO3\CMS\Core\Domain\RecordInterface;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Resource\ResourceInterface;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;

/**
 * Factory for creating breadcrumb contexts from controller actions.
 *
 * This factory centralizes the logic for determining what context to show
 * in breadcrumbs based on different controller actions (edit, new, list, etc.).
 *
 * It handles:
 * - Record lookups and validation
 * - Creation of "new record" breadcrumb nodes
 * - Multi-record edit scenarios
 * - Parent record resolution
 *
 * @internal Subject to change until v15 LTS
 */
final readonly class BreadcrumbFactory
{
    public function __construct(
        private LoggerInterface $logger,
        private RecordFactory $recordFactory,
        private IconFactory $iconFactory,
        private TcaSchemaFactory $tcaSchemaFactory,
    ) {}

    /**
     * Creates breadcrumb context for editing an existing record.
     *
     * @param string $table The table name
     * @param int $uid The record UID
     * @return BreadcrumbContext Context containing the record or null on failure
     */
    public function forEditAction(string $table, int $uid): BreadcrumbContext
    {
        $rawRecord = BackendUtility::getRecord($table, $uid);

        if ($rawRecord === null) {
            $this->logger->warning(
                'Failed to load record for breadcrumb',
                ['table' => $table, 'uid' => $uid]
            );
            return new BreadcrumbContext(null, []);
        }

        try {
            $record = $this->recordFactory->createResolvedRecordFromDatabaseRow($table, $rawRecord);
            return new BreadcrumbContext($record, []);
        } catch (\Exception $e) {
            // @todo: Catching \Exception here is a code smell, this shouldn't be so generic and can hide away too many issues.
            $this->logger->error(
                'Failed to create record instance for breadcrumb',
                ['table' => $table, 'uid' => $uid, 'exception' => $e->getMessage()]
            );
            return new BreadcrumbContext(null, []);
        }
    }

    /**
     * Creates breadcrumb context for editing multiple records.
     *
     * Shows a generic "Edit Multiple [RecordType]" node instead of individual records.
     *
     * @param string $table The table name
     * @param int $pid The parent page ID
     * @return BreadcrumbContext Context with parent page and "edit multiple" suffix node
     */
    public function forEditMultipleAction(string $table, int $pid): BreadcrumbContext
    {
        $parentRecord = $this->getParentPageRecord($pid);
        $schema = $this->tcaSchemaFactory->has($table) ? $this->tcaSchemaFactory->get($table) : null;

        $recordTypeLabel = $schema?->getTitle($this->getLanguageService()->sL(...))
            ?? $schema?->getTitle()
            ?? $table;

        $suffixNode = new BreadcrumbNode(
            identifier: 'edit-multiple-' . $table,
            label: sprintf(
                $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editMultiple'),
                $recordTypeLabel
            ),
            icon: $this->iconFactory->getIconForRecord($table, [])->getIdentifier(),
        );

        return new BreadcrumbContext($parentRecord, [$suffixNode]);
    }

    /**
     * Creates breadcrumb context for creating a new record.
     *
     * @param string $table The table name
     * @param int $pid The parent page ID
     * @param array $defaults Default values for the new record (used for icon overlay)
     * @return BreadcrumbContext Context with parent page and "create new" suffix node
     */
    public function forNewAction(string $table, int $pid, array $defaults = []): BreadcrumbContext
    {
        $parentRecord = $this->getParentPageRecord($pid);
        $schema = $this->tcaSchemaFactory->has($table) ? $this->tcaSchemaFactory->get($table) : null;

        $recordTypeLabel = $schema?->getTitle($this->getLanguageService()->sL(...))
            ?? $schema?->getTitle()
            ?? $table;

        try {
            $icon = $this->iconFactory->getIconForRecord($table, $defaults);
            $suffixNode = new BreadcrumbNode(
                identifier: 'new-' . $table,
                label: sprintf(
                    $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNew'),
                    $recordTypeLabel
                ),
                icon: $icon->getIdentifier(),
                iconOverlay: 'overlay-new',
            );
        } catch (\Exception $e) {
            $this->logger->warning(
                'Failed to create icon for new record breadcrumb',
                ['table' => $table, 'exception' => $e->getMessage()]
            );
            $suffixNode = new BreadcrumbNode(
                identifier: 'new-' . $table,
                label: sprintf(
                    $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNew'),
                    $recordTypeLabel
                ),
            );
        }

        return new BreadcrumbContext($parentRecord, [$suffixNode]);
    }

    /**
     * Creates breadcrumb context from a page record array.
     *
     * Example:
     * `$view->getDocHeaderComponent()->setBreadcrumbContext($this->breadcrumbFactory->forPageArray($pageInfo));`
     *
     * @param array $pageRecord The page record array (must contain 'uid')
     * @return BreadcrumbContext Context with the page record or null on failure
     */
    public function forPageArray(array $pageRecord): BreadcrumbContext
    {
        if (!isset($pageRecord['uid'])) {
            $this->logger->warning('Page record array must contain uid for breadcrumb');
            return new BreadcrumbContext(null, []);
        }

        try {
            $record = $this->recordFactory->createResolvedRecordFromDatabaseRow('pages', $pageRecord);
            return new BreadcrumbContext($record, []);
        } catch (\Exception $e) {
            $this->logger->error(
                'Failed to create page record instance for breadcrumb',
                ['uid' => $pageRecord['uid'], 'exception' => $e->getMessage()]
            );
            return new BreadcrumbContext(null, []);
        }
    }

    /**
     * Creates breadcrumb context for any resource (file or folder).
     *
     * @param ResourceInterface $resource The resource (file or folder)
     * @return BreadcrumbContext Context with the resource
     */
    public function forResource(ResourceInterface $resource): BreadcrumbContext
    {
        return new BreadcrumbContext($resource, []);
    }

    /**
     * Gets the parent page record for a given PID.
     *
     * @param int $pid The page ID
     * @return RecordInterface|null The page record or null if not found/accessible
     */
    private function getParentPageRecord(int $pid): ?RecordInterface
    {
        if ($pid <= 0) {
            return null;
        }

        $rawRecord = BackendUtility::getRecord('pages', $pid);
        if ($rawRecord === null) {
            $this->logger->warning(
                'Failed to load parent page for breadcrumb',
                ['pid' => $pid]
            );
            return null;
        }

        try {
            return $this->recordFactory->createResolvedRecordFromDatabaseRow('pages', $rawRecord);
        } catch (\Exception $e) {
            // @todo: Catching \Exception here is a code smell, this shouldn't be so generic and can hide away too many issues.
            $this->logger->error(
                'Failed to create page record instance for breadcrumb',
                ['pid' => $pid, 'exception' => $e->getMessage()]
            );
            return null;
        }
    }

    private function getLanguageService(): LanguageService
    {
        return $GLOBALS['LANG'];
    }
}
