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

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Context\PageContext;
use TYPO3\CMS\Backend\Domain\Model\Language\PageLanguageInformation;
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout;
use TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher;
use TYPO3\CMS\Backend\View\Drawing\DrawingConfiguration;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Domain\Persistence\RecordIdentityMap;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Page Module specific rendering context.
 *
 * Extends generic PageContext with module-specific rendering configuration
 * for the page module (web_layout).
 *
 * This context provides:
 * - Generic page data (via delegation to PageContext)
 * - Backend layout configuration
 * - Drawing configuration
 * - Content type labels
 * - Content fetcher
 *
 * @internal
 */
class PageLayoutContext
{
    protected ContentFetcher $contentFetcher;
    protected ?array $localizedPageRecord = null;

    /**
     * @var SiteLanguage[]
     */
    protected array $siteLanguages = [];
    protected SiteLanguage $siteLanguage;

    /**
     * Array of content type labels. Key is CType, value is either a plain text
     * label or an LLL:EXT:... reference to a specific label.
     */
    protected array $contentTypeLabels = [];

    /**
     * Labels for columns, in format of TCA select options. Numerically indexed
     * array of numerically indexed value arrays, with each sub-array containing
     * at least two values and one optional third value:
     *
     * - label (hardcoded or LLL:EXT:... reference. MANDATORY)
     * - value (colPos of column. MANDATORY)
     * - icon (icon name or file reference. OPTIONAL)
     */
    protected array $itemLabels = [];

    protected RecordIdentityMap $recordIdentityMap;

    public function __construct(
        protected readonly PageContext $pageContext,
        protected readonly BackendLayout $backendLayout,
        protected readonly DrawingConfiguration $drawingConfiguration,
        protected readonly ServerRequestInterface $request,
    ) {
        $this->contentFetcher = GeneralUtility::makeInstance(ContentFetcher::class);
        $this->siteLanguages = $this->pageContext->site->getAvailableLanguages($this->getBackendUser(), true, $this->pageContext->pageId);
        $this->siteLanguage = $this->pageContext->site->getDefaultLanguage();
        $this->recordIdentityMap = GeneralUtility::makeInstance(RecordIdentityMap::class);
    }

    public function cloneForLanguage(SiteLanguage $language): self
    {
        $copy = clone $this;
        $copy->setSiteLanguage($language);
        return $copy;
    }

    protected function setSiteLanguage(SiteLanguage $siteLanguage): void
    {
        $this->siteLanguage = $siteLanguage;
        $languageId = $siteLanguage->getLanguageId();
        if ($languageId > 0) {
            $pageLocalizationRecord = GeneralUtility::makeInstance(LocalizationRepository::class)
                ->getPageTranslations($this->getPageId(), [$languageId], $this->getBackendUser()->workspace);
            if ($pageLocalizationRecord !== []) {
                // @todo: lets move to Record API soon
                $pageLocalizationRecord = reset($pageLocalizationRecord);
                $this->localizedPageRecord = $pageLocalizationRecord->toArray(true) ?: null;
            }
        }
    }

    public function getPageContext(): PageContext
    {
        return $this->pageContext;
    }

    public function getPageId(): int
    {
        return $this->pageContext->pageId;
    }

    public function getPageRecord(): array
    {
        return $this->pageContext->pageRecord;
    }

    public function getSite(): SiteInterface
    {
        return $this->pageContext->site;
    }

    public function getSelectedLanguageIds(): array
    {
        return $this->pageContext->selectedLanguageIds;
    }

    public function getPrimaryLanguageId(): int
    {
        return $this->pageContext->getPrimaryLanguageId();
    }

    public function getLanguageInformation(): PageLanguageInformation
    {
        return $this->pageContext->languageInformation;
    }

    public function getBackendLayout(): BackendLayout
    {
        return $this->backendLayout;
    }

    public function getDrawingConfiguration(): DrawingConfiguration
    {
        return $this->drawingConfiguration;
    }

    /**
     * @return SiteLanguage[]
     */
    public function getSiteLanguages(): iterable
    {
        return $this->siteLanguages;
    }

    /**
     * @return SiteLanguage[]
     */
    public function getLanguagesToShow(): iterable
    {
        $site = $this->pageContext->site;
        $selectedLanguageIds = $this->drawingConfiguration->getSelectedLanguageIds();

        // If multiple languages are selected, show default language + all selected languages
        if (count($selectedLanguageIds) > 1 || (count($selectedLanguageIds) === 1 && $selectedLanguageIds[0] > 0)) {
            $languagesToShow = [];
            // Always include default language (0) first
            $languagesToShow[] = $site->getDefaultLanguage();
            // Add all selected languages, except default
            foreach ($selectedLanguageIds as $languageId) {
                try {
                    if ($languageId > 0) {
                        $languagesToShow[] = $site->getLanguageById($languageId);
                    }
                } catch (\InvalidArgumentException $e) {
                    // Skip invalid language IDs
                }
            }
            return $languagesToShow;
        }

        // Single language selected (default language only)
        return [$site->getDefaultLanguage()];
    }

    public function hasMultiLanguages(): bool
    {
        return count($this->getLanguagesToShow()) > 1;
    }

    public function getSiteLanguage(?int $languageId = null): SiteLanguage
    {
        if ($languageId === null) {
            return $this->siteLanguage;
        }
        if ($languageId === -1) {
            return $this->siteLanguages[-1];
        }

        return $this->pageContext->site->getLanguageById($languageId);
    }

    public function isPageEditable(): bool
    {
        // TODO: refactor to page permissions container
        if ($this->getBackendUser()->isAdmin()) {
            return true;
        }
        $pageRecord = $this->pageContext->pageRecord;
        return $this->getBackendUser()->doesUserHaveAccess($pageRecord, Permission::PAGE_EDIT)
            && (
                !($schema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('pages'))->hasCapability(TcaSchemaCapability::EditLock)
                || !($pageRecord[$schema->getCapability(TcaSchemaCapability::EditLock)->getFieldName()] ?? false)
            );
    }

    public function getAllowNewContent(): bool
    {
        $allowInconsistentLanguageHandling = $this->drawingConfiguration->getAllowInconsistentLanguageHandling();
        if (!$allowInconsistentLanguageHandling && $this->getLanguageModeIdentifier() === 'connected') {
            return false;
        }
        return true;
    }

    public function getContentTypeLabels(): array
    {
        if (empty($this->contentTypeLabels)) {
            $schemaFactory = GeneralUtility::makeInstance(TcaSchemaFactory::class);
            $schema = $schemaFactory->get('tt_content');
            if ($schema->supportsSubSchema()) {
                if (($schemaTypeInformation = $schema->getSubSchemaTypeInformation())->isPointerToForeignFieldInForeignSchema()) {
                    $typeField = $schemaFactory->get($schemaTypeInformation->getForeignSchemaName())->getField($schemaTypeInformation->getForeignFieldName());
                } else {
                    $typeField = $schema->getField($schemaTypeInformation->getFieldName());
                }
                foreach ($typeField->getConfiguration()['items'] ?? [] as $val) {
                    $this->contentTypeLabels[$val['value']] = $this->getLanguageService()->sL($val['label']);
                }
            }
        }
        return $this->contentTypeLabels;
    }

    public function getItemLabels(): array
    {
        if (empty($this->itemLabels)) {
            foreach (GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('tt_content')->getFields() as $field) {
                $this->itemLabels[$field->getName()] = $this->getLanguageService()->sL($field->getLabel());
            }
        }
        return $this->itemLabels;
    }

    public function getLanguageModeLabelClass(): string
    {
        $languageId = $this->siteLanguage->getLanguageId();
        $contentRecordsPerColumn = $this->contentFetcher->getFlatContentRecords($this, $languageId);
        $translationData = $this->contentFetcher->getTranslationData($this, $contentRecordsPerColumn, $languageId);
        return $translationData['mode'] === 'mixed' ? 'danger' : 'info';
    }

    public function getLanguageMode(): string
    {
        return match ($this->getLanguageModeIdentifier()) {
            'mixed' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:languageModeMixed'),
            'connected' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:languageModeConnected'),
            'free' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:languageModeFree'),
            default => '',
        };
    }

    public function getLanguageModeIdentifier(): string
    {
        $contentRecordsPerColumn = $this->contentFetcher->getContentRecordsPerColumn($this, null, $this->siteLanguage->getLanguageId());
        $contentRecords = empty($contentRecordsPerColumn) ? [] : array_merge(...$contentRecordsPerColumn);
        $translationData = $this->contentFetcher->getTranslationData($this, $contentRecords, $this->siteLanguage->getLanguageId());
        return $translationData['mode'] ?? '';
    }

    public function getCurrentRequest(): ServerRequestInterface
    {
        return $this->request;
    }

    public function getLocalizedPageTitle(): string
    {
        return $this->localizedPageRecord['title'] ?? $this->pageContext->pageRecord['title'] ?? '';
    }

    public function getLocalizedPageRecord(): ?array
    {
        return $this->localizedPageRecord;
    }

    public function getRecordIdentityMap(): RecordIdentityMap
    {
        return $this->recordIdentityMap;
    }

    public function getReturnUrl(): string
    {
        return $this->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri();
    }

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

    public function getBackendUser(): BackendUserAuthentication
    {
        return $GLOBALS['BE_USER'];
    }
}
