<?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\ContentBlocks\Generator;

use TYPO3\CMS\ContentBlocks\Backend\Preview\PreviewRenderer;
use TYPO3\CMS\ContentBlocks\Definition\Capability\NativeTableCapabilityProxy;
use TYPO3\CMS\ContentBlocks\Definition\Capability\RootLevelType;
use TYPO3\CMS\ContentBlocks\Definition\Capability\SystemFieldPalettesInterface;
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentElementDefinition;
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType;
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentTypeInterface;
use TYPO3\CMS\ContentBlocks\Definition\ContentType\PageTypeDefinition;
use TYPO3\CMS\ContentBlocks\Definition\PaletteDefinition;
use TYPO3\CMS\ContentBlocks\Definition\TableDefinition;
use TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection;
use TYPO3\CMS\ContentBlocks\Definition\TCA\LinebreakDefinition;
use TYPO3\CMS\ContentBlocks\Definition\TCA\TabDefinition;
use TYPO3\CMS\ContentBlocks\Definition\TcaFieldDefinition;
use TYPO3\CMS\ContentBlocks\FieldType\CollectionFieldType;
use TYPO3\CMS\ContentBlocks\FieldType\FieldTypeInterface;
use TYPO3\CMS\ContentBlocks\FieldType\FlexFormFieldType;
use TYPO3\CMS\ContentBlocks\Registry\LanguageFileRegistry;
use TYPO3\CMS\ContentBlocks\Schema\SimpleTcaSchemaFactory;
use TYPO3\CMS\ContentBlocks\Service\SystemExtensionAvailability;
use TYPO3\CMS\ContentBlocks\Validation\PageTypeNameValidator;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * TCA generation based on Content Block definition.
 *
 * TCA (Table Configuration Array) is the database table configuration language
 * of TYPO3. First and foremost it defines which tables are editable in the
 * TYPO3 backend. Over time, it gained more and more responsibilities and
 * inverted the control so that it does not only configure database tables, but
 * also creates them. TCA is therefore one of the most important aspects when
 * dealing with Content Types.
 *
 * TCA is loaded in two steps: First base TCA is loaded from an extensions'
 * Configuration/TCA/table_name.php folder. After this is finished, overrides
 * are applied from Configuration/TCA/Overrides/table_name.php. In TYPO3 v12
 * Content Blocks uses the second step to add its TCA. This is done, so that
 * extensions are able to override the generated TCA. In TYPO3 v13 a new PSR-14
 * Event `BeforeTcaOverridesEvent` was introduced, which is used in favor of the
 * TCA/Overrides method. This has the advantage that Content Blocks' TCA is
 * added as base TCA and can be overridden more easily without the need to
 * respect loading order. However, this does also mean that Content Blocks has
 * no knowledge about any TCA columns added by overrides.
 *
 * The main strategy is to reuse as many fields as possible. To achieve this,
 * every viable TCA config is added as type specific `columnsOverride`. This is
 * a very powerful tool, which allows us to configure the same field with
 * completely different options depending on the use case. This is done for
 * Content Types with a defined `typeField` aka `type` in TCA. Content Elements
 * and Page Types do have these inherently. User defined Record Types may have
 * multiple types, as well. Collections only have one type, so they will always
 * receive the full column TCA. Some options are non-overridable (see
 * $nonOverridableOptions property). These will never be added to
 * columnsOverrides.
 *
 * Every new type is added to the TCA type field select automatically. See
 * `fillTypeFieldSelectItems` method.
 *
 * Fields defined by Content Blocks are added after the type field, which
 * is always at the first position. Then any number of custom tabs may follow.
 * Now only system fields remain. These are added after the users' custom
 * fields, in case they are required. This can be controlled by the
 * Content Block itself with bool flags like `languageAware`. Per default, all
 * system fields are enabled. Experienced users may disable them if they see no
 * need for them.
 *
 * FlexForm is generated by FlexFormGenerator. It is always added as string to
 * TCA. It is taken care for existing FlexForm fields like pi_flexform, so that
 * alternative config is added with the correct `ds` key.
 *
 * Search fields are added automatically based on the type of the field.
 *
 * The `appearance.useSortable` option is enabled for Collections automatically
 * when sorting is enabled for the related table.
 *
 * @internal Not part of TYPO3's public API.
 */
readonly class TcaGenerator
{
    public function __construct(
        protected TableDefinitionCollection $tableDefinitionCollection,
        protected SimpleTcaSchemaFactory $simpleTcaSchemaFactory,
        protected LanguageFileRegistry $languageFileRegistry,
        protected SystemExtensionAvailability $systemExtensionAvailability,
        protected FlexFormGenerator $flexFormGenerator,
    ) {}

    #[AsEventListener(identifier: 'content-blocks-tca')]
    public function __invoke(BeforeTcaOverridesEvent $event): void
    {
        $event->setTca(array_replace_recursive($event->getTca(), $this->generate($event->getTca())));

        // Store backup of current TCA, as the helper methods in `fillTypeFieldSelectItems` operate on the global array.
        $tcaBackup = $GLOBALS['TCA'] ?? null;
        $GLOBALS['TCA'] = $event->getTca();
        $this->fillTypeFieldSelectItems();
        $event->setTca($GLOBALS['TCA']);
        if ($tcaBackup !== null) {
            $GLOBALS['TCA'] = $tcaBackup;
        }
    }

    public function generate(array $baseTca): array
    {
        $tca = [];
        foreach ($this->tableDefinitionCollection as $tableDefinition) {
            $tca[$tableDefinition->table] = $this->generateTableTca($tableDefinition, $baseTca);
        }
        return $tca;
    }

    protected function generateTableTca(TableDefinition $tableDefinition, array $baseTca): array
    {
        $tca = [];
        $isNewTable = !$this->simpleTcaSchemaFactory->has($tableDefinition->table);
        if ($isNewTable) {
            $tca = $this->generateBaseTableTca($tableDefinition);
        }
        $currentPalettesTca = $tca['palettes'] ?? [];
        $tca['palettes'] = $currentPalettesTca + $this->generatePalettesTca($tableDefinition);
        foreach ($tableDefinition->tcaFieldDefinitionCollection as $column) {
            $fieldType = $column->fieldType;
            if ($fieldType instanceof FlexFormFieldType) {
                $dataStructure = [];
                foreach ($fieldType->getFlexFormDefinitions() as $flexFormDefinition) {
                    $dataStructure[$flexFormDefinition->getTypeName()] = $this->flexFormGenerator->generate($flexFormDefinition);
                }
                $fieldType->setDataStructure($dataStructure);
            }
            if ($tableDefinition->hasTypeField() || $tableDefinition->contentType === ContentType::FILE_TYPE) {
                $tca['columns'][$column->uniqueIdentifier] = $this->getColumnTcaForTableWithTypeField($tableDefinition, $column, $baseTca);
                // Ensure label exists for the standard column definition. This is used e.g. in the List module.
                if (!$column->useExistingField) {
                    $tca['columns'][$column->uniqueIdentifier]['label'] ??= $column->identifier;
                }
            } else {
                $tca['columns'][$column->uniqueIdentifier] = $this->getColumnTcaForTableWithoutTypeField($tableDefinition, $column);
            }
        }
        foreach ($tableDefinition->contentTypeDefinitionCollection as $typeDefinition) {
            foreach ($typeDefinition->getOverrideColumns() as $column) {
                $columnTca = $tca['columns'][$column->uniqueIdentifier];
                $tca['columns'][$column->uniqueIdentifier] = $this->determineItemsLabel($typeDefinition, $column, $columnTca);
            }
            $tca['types'][$typeDefinition->getTypeName()] = $this->processTypeDefinition($typeDefinition, $tableDefinition);
            if ($tableDefinition->hasTypeField()) {
                if ($typeDefinition->getTypeIcon()->initialized) {
                    $tca['ctrl']['typeicon_classes'][$typeDefinition->getTypeName()] = $typeDefinition->getTypeIcon()->iconIdentifier;
                }
                if ($typeDefinition instanceof PageTypeDefinition) {
                    if ($typeDefinition->getPageIconSet()->iconHideInMenu->initialized) {
                        $tca['ctrl']['typeicon_classes'][$typeDefinition->getTypeName() . '-hideinmenu'] = $typeDefinition->getPageIconSet()->iconHideInMenu->iconIdentifier;
                    }
                    if ($typeDefinition->getPageIconSet()->iconRoot->initialized) {
                        $tca['ctrl']['typeicon_classes'][$typeDefinition->getTypeName() . '-root'] = $typeDefinition->getPageIconSet()->iconRoot->iconIdentifier;
                    }
                }
            }
            if ($tableDefinition->contentType === ContentType::RECORD_TYPE) {
                if ($typeDefinition->getTypeIcon()->initialized && ($isNewTable || !isset($baseTca[$tableDefinition->table]['ctrl']['typeicon_classes']['default']))) {
                    $tca['ctrl']['typeicon_classes']['default'] ??= $typeDefinition->getTypeIcon()->iconIdentifier;
                }
                if ($tableDefinition->hasTypeField()) {
                    // Ensure "type" is always set when a type field exists. This could be missing when an existing
                    // record is extended with a new record type.
                    if ($isNewTable || !isset($baseTca[$tableDefinition->table]['ctrl']['type'])) {
                        $tca['ctrl']['type'] ??= $tableDefinition->typeField;
                    }
                    if ($isNewTable || !isset($baseTca[$tableDefinition->table]['ctrl']['typeicon_column'])) {
                        $tca['ctrl']['typeicon_column'] ??= $tableDefinition->typeField;
                    }
                }
            }
        }
        $tca['ctrl']['searchFields'] = $this->generateSearchFields($tableDefinition, $baseTca);
        $tca = $this->cleanTableTca($tca);
        return $tca;
    }

    protected function generatePalettesTca(TableDefinition $tableDefinition): array
    {
        $palettes = [];
        foreach ($tableDefinition->paletteDefinitionCollection as $paletteDefinition) {
            $paletteTca = $this->generatePalettesTcaSingle($paletteDefinition);
            $palettes[$paletteDefinition->getIdentifier()] = $paletteTca;
        }
        if ($this->simpleTcaSchemaFactory->has($tableDefinition->table)
            && $tableDefinition->table !== 'pages'
        ) {
            $tcaSchema = $this->simpleTcaSchemaFactory->get($tableDefinition->table);
            $nativeCapability = new NativeTableCapabilityProxy($tcaSchema);
            $systemPalettes = $this->buildSystemPalettes($nativeCapability);
            if (isset($systemPalettes['hidden'])) {
                $palettes['hidden'] = $systemPalettes['hidden'];
            }
            if (isset($systemPalettes['access'])) {
                $palettes['access'] = $systemPalettes['access'];
            }
            if (isset($systemPalettes['language'])) {
                $palettes['language'] = $systemPalettes['language'];
            }
        }
        return $palettes;
    }

    protected function generatePalettesTcaSingle(PaletteDefinition $paletteDefinition): array
    {
        $paletteTca = [
            'showitem' => $this->generatePaletteShowItem($paletteDefinition),
        ];
        if ($this->languageFileRegistry->isset($paletteDefinition->getContentBlockName(), $paletteDefinition->getLanguagePathLabel())) {
            $paletteTca['label'] = $paletteDefinition->getLanguagePathLabel();
        } elseif ($paletteDefinition->hasLabel()) {
            $paletteTca['label'] = $paletteDefinition->getLabel();
        }
        if ($this->languageFileRegistry->isset($paletteDefinition->getContentBlockName(), $paletteDefinition->getLanguagePathDescription())) {
            $paletteTca['description'] = $paletteDefinition->getLanguagePathDescription();
        } elseif ($paletteDefinition->hasDescription()) {
            $paletteTca['description'] = $paletteDefinition->getDescription();
        }
        return $paletteTca;
    }

    protected function generatePaletteShowItem(PaletteDefinition $paletteDefinition): string
    {
        $showItem = [];
        foreach ($paletteDefinition->getItems() as $fieldIdentifier) {
            if ($fieldIdentifier instanceof LinebreakDefinition) {
                $showItem[] = '--linebreak--';
            } else {
                $showItem[] = $fieldIdentifier;
            }
        }
        $showItemString = implode(',', $showItem);
        return $showItemString;
    }

    protected function fillTypeFieldSelectItems(): void
    {
        foreach ($this->tableDefinitionCollection as $tableDefinition) {
            // This definition has only one type (the default type "1"). There is no type select to add it to.
            if (!$tableDefinition->hasTypeField()) {
                continue;
            }
            foreach ($tableDefinition->contentTypeDefinitionCollection as $typeDefinition) {
                $typeName = $typeDefinition->getTypeName();
                if ($tableDefinition->contentType === ContentType::PAGE_TYPE && PageTypeNameValidator::isExistingPageType($typeName)) {
                    continue;
                }
                $languagePathTitle = $typeDefinition->getLanguagePathTitle();
                if ($this->languageFileRegistry->isset($typeDefinition->getName(), $languagePathTitle)) {
                    $label = $languagePathTitle;
                } else {
                    $label = $typeDefinition->getTitle();
                }
                $languagePathDescription = $typeDefinition->getLanguagePathDescription();
                if ($this->languageFileRegistry->isset($typeDefinition->getName(), $languagePathDescription)) {
                    $description = $languagePathDescription;
                } else {
                    $description = $typeDefinition->getDescription();
                }
                $icon = $typeDefinition->getTypeIcon();
                ExtensionManagementUtility::addTcaSelectItem(
                    $typeDefinition->getTable(),
                    $tableDefinition->typeField,
                    [
                        'label' => $label,
                        'value' => $typeName,
                        'icon' => $icon->initialized ? $icon->iconIdentifier : null,
                        'group' => $typeDefinition->getGroup(),
                        'description' => $description,
                    ]
                );
            }
        }
    }

    protected function processTypeDefinition(ContentTypeInterface $typeDefinition, TableDefinition $tableDefinition): array
    {
        $columnsOverrides = $this->getColumnsOverrides($typeDefinition);
        foreach ($typeDefinition->getOverrideColumns() as $overrideColumn) {
            $overrideTca = $columnsOverrides[$overrideColumn->uniqueIdentifier];
            $columnsOverrides[$overrideColumn->uniqueIdentifier] = $this->addUseSortableIfEnabled($overrideColumn, $tableDefinition, $overrideTca);
        }
        $tca = match ($tableDefinition->contentType) {
            ContentType::CONTENT_ELEMENT => $this->processContentElement($typeDefinition, $columnsOverrides),
            ContentType::PAGE_TYPE => $this->processPageType($typeDefinition, $columnsOverrides),
            ContentType::FILE_TYPE => $this->processFileType($typeDefinition, $columnsOverrides),
            ContentType::RECORD_TYPE => $this->processRecordType($typeDefinition, $columnsOverrides, $tableDefinition),
        };
        return $tca;
    }

    protected function processContentElement(ContentTypeInterface $typeDefinition, array $columnsOverrides): array
    {
        if (!$typeDefinition instanceof ContentElementDefinition) {
            throw new \InvalidArgumentException(
                'Expected ContentElementDefinition, got ' . get_class($typeDefinition),
                1733344806
            );
        }
        $typeDefinitionArray = [
            'previewRenderer' => PreviewRenderer::class,
            'showitem' => $this->getContentElementStandardShowItem($typeDefinition),
        ];
        if ($typeDefinition->hasSaveAndClose()) {
            $typeDefinitionArray['creationOptions']['saveAndClose'] = true;
        }
        if ($columnsOverrides !== []) {
            $typeDefinitionArray['columnsOverrides'] = $columnsOverrides;
        }
        return $typeDefinitionArray;
    }

    protected function processPageType(ContentTypeInterface $typeDefinition, array $columnsOverrides): array
    {
        $typeDefinitionArray = [
            'showitem' => $this->getPageTypeStandardShowItem($typeDefinition->getShowItems(), $typeDefinition->getTypeName()),
        ];
        if ($columnsOverrides !== []) {
            $typeDefinitionArray['columnsOverrides'] = $columnsOverrides;
        }
        return $typeDefinitionArray;
    }

    protected function processFileType(ContentTypeInterface $typeDefinition, array $columnsOverrides): array
    {
        $typeDefinitionArray = [
            'showitem' => $this->getFileTypeStandardShowItem($typeDefinition->getShowItems()),
        ];
        if ($columnsOverrides !== []) {
            $typeDefinitionArray['columnsOverrides'] = $columnsOverrides;
        }
        return $typeDefinitionArray;
    }

    protected function processRecordType(ContentTypeInterface $typeDefinition, array $columnsOverrides, TableDefinition $tableDefinition): array
    {
        $typeDefinitionArray = [
            'showitem' => $this->getRecordTypeStandardShowItem($typeDefinition->getShowItems(), $tableDefinition->table),
        ];
        if ($tableDefinition->hasTypeField() && $columnsOverrides !== []) {
            $typeDefinitionArray['columnsOverrides'] = $columnsOverrides;
        }
        return $typeDefinitionArray;
    }

    /**
     * @param array<string|PaletteDefinition|TabDefinition> $showItemInput
     */
    protected function processShowItem(array $showItemInput): string
    {
        $showItem = [];
        foreach ($showItemInput as $inputItem) {
            if ($inputItem instanceof PaletteDefinition) {
                $showItem[] = '--palette--;;' . $inputItem->getIdentifier();
            } elseif ($inputItem instanceof TabDefinition) {
                $tab = '--div--;';
                $languagePathLabel = $inputItem->getLanguagePathLabel();
                if ($this->languageFileRegistry->isset($inputItem->getContentBlockName(), $languagePathLabel)) {
                    $tab .= $languagePathLabel;
                } elseif ($inputItem->hasLabel()) {
                    $tab .= $inputItem->getLabel();
                } else {
                    $tab .= $inputItem->getIdentifier();
                }
                $showItem[] = $tab;
            } else {
                $showItem[] = $inputItem;
            }
        }
        $showItemString = implode(',', $showItem);
        return $showItemString;
    }

    /**
     * These fields are required for automatic default SQL schema generation
     * or for relation resolving Core API. Thus, these fields can't be
     * overridden through type overrides, as merged column config is only
     * available in FormEngine context.
     *
     * @return string[]|array{type: string, option: string}
     */
    protected static function getNonOverridableOptions(FieldTypeInterface $fieldType): array
    {
        $nonOverridableOptions = [
            'type',
            'relationship',
            'dbType',
            'nullable',
            'MM',
            'MM_opposite_field',
            'MM_hasUidField',
            'MM_oppositeUsage',
            [
                'type' => 'Relation',
                'option' => 'allowed',
            ],
            'foreign_table',
            'foreign_field',
            'foreign_table_field',
            'foreign_match_fields',
            'foreign_sortby',
            'foreign_default_sortby',
            'symmetric_field',
            'symmetric_sortby',
            'ds',
            'ds_pointerField',
            'exclude',
            // @todo This should be handled correctly with columnsOverrides in TYPO3 Core FormSlugAjaxController
            'generatorOptions',
            'behaviour.allowLanguageSynchronization',
            [
                'type' => 'Number',
                'option' => 'format',
            ],
            'l10n_mode',
            'dbFieldLength',
            'items',
            'multiple',
            'opacity',
        ];
        $fieldNonOverridableOptions = [];
        // @todo experimental. Could be added as interface method later.
        if (method_exists($fieldType, 'getNonOverridableOptions')) {
            $fieldNonOverridableOptions = $fieldType->getNonOverridableOptions();
        }
        $mergedNonOverridableOptions = array_merge($nonOverridableOptions, $fieldNonOverridableOptions);
        return $mergedNonOverridableOptions;
    }

    protected function getColumnsOverrides(ContentTypeInterface $typeDefinition): array
    {
        $columnsOverrides = [];
        foreach ($typeDefinition->getOverrideColumns() as $overrideColumn) {
            $overrideTca = $overrideColumn->getTca();
            $fieldType = $overrideColumn->fieldType;
            $nonOverridableOptions = self::getNonOverridableOptions($fieldType);
            foreach ($nonOverridableOptions as $option) {
                $optionKey = $this->getOptionKey($option, $overrideColumn);
                if ($optionKey === null) {
                    continue;
                }
                // Backwards compatibility for reusing select fields with different items.
                if ($optionKey === 'items' && $overrideColumn->useExistingField) {
                    continue;
                }
                $configKey = 'config.' . $optionKey;
                if (ArrayUtility::isValidPath($overrideTca, $configKey, '.')) {
                    $overrideTca = ArrayUtility::removeByPath($overrideTca, $configKey, '.');
                    $this->cleanupEmptyArrays($overrideTca, $configKey);
                }
                unset($overrideTca[$optionKey]);
            }
            $overrideTca = $this->determineLabelAndDescription(
                $typeDefinition,
                $overrideColumn,
                $overrideTca,
            );
            $overrideTca = $this->processOverrideChildTca($overrideColumn, $overrideTca);
            $columnsOverrides[$overrideColumn->uniqueIdentifier] = $overrideTca;
        }
        return $columnsOverrides;
    }

    /**
     * Automatic `useSortable` if sorting is enabled on foreign table.
     */
    protected function addUseSortableIfEnabled(TcaFieldDefinition $overrideColumn, TableDefinition $tableDefinition, array $overrideTca): array
    {
        $fieldType = $overrideColumn->fieldType;
        if ($fieldType->getTcaType() !== 'inline') {
            return $overrideTca;
        }
        $tcaFieldDefinition = $tableDefinition->tcaFieldDefinitionCollection
            ->getField($overrideColumn->uniqueIdentifier);
        $foreignTable = $tcaFieldDefinition->getTca()['config']['foreign_table'];
        if (!$this->tableDefinitionCollection->hasTable($foreignTable)) {
            return $overrideTca;
        }
        if (($tcaFieldDefinition->getTca()['config']['maxitems'] ?? 0) === 1) {
            return $overrideTca;
        }
        $foreignTableDefinition = $this->tableDefinitionCollection->getTable($foreignTable);
        if ($foreignTableDefinition->capability->isSortable() === false) {
            return $overrideTca;
        }
        if ($foreignTableDefinition->capability->hasSortField()) {
            return $overrideTca;
        }
        $overrideTca['config']['appearance']['useSortable'] ??= true;
        return $overrideTca;
    }

    /**
     * Record Types which didn't define a custom typeField or Collections get their full TCA in their columns section.
     */
    protected function getColumnTcaForTableWithoutTypeField(TableDefinition $tableDefinition, TcaFieldDefinition $column): array
    {
        $standardTypeDefinition = $tableDefinition->getDefaultTypeDefinition();
        $columnTca = $this->determineLabelAndDescription($standardTypeDefinition, $column, $column->getTca());
        $columnTca = $this->processOverrideChildTca($column, $columnTca);
        return $columnTca;
    }

    /**
     * Content Elements, Page Types and Record Types with defined typeField only get minimal (non-shareable) TCA in
     * their columns section. The actual config goes into columnsOverrides for the related type.
     */
    protected function getColumnTcaForTableWithTypeField(TableDefinition $tableDefinition, TcaFieldDefinition $column, array $baseTca): array
    {
        // FlexForm "ds" can be extended without columnsOverrides.
        $extensibleOptions = ['ds'];
        $columnTca = [];
        $fieldType = $column->fieldType;
        $iterateOptions = $column->useExistingField ? $extensibleOptions : self::getNonOverridableOptions($fieldType);
        foreach ($iterateOptions as $option) {
            $optionKey = $this->getOptionKey($option, $column);
            if ($optionKey === null) {
                continue;
            }
            $fullConfiguration = $column->getTca();
            $configKey = 'config.' . $optionKey;
            if (ArrayUtility::isValidPath($fullConfiguration, $configKey, '.')) {
                $configuration = ArrayUtility::getValueByPath($fullConfiguration, $configKey, '.');
                // Support for existing flexForm fields.
                if ($optionKey === 'ds') {
                    if ($column->useExistingField) {
                        $configuration = $this->processExistingFlexForm($column, $tableDefinition, $baseTca);
                        if ($configuration === null) {
                            continue;
                        }
                    } else {
                        // Add default FlexForm definition. This is needed for translation purposes, as special FlexForm
                        // handling is performed even on elements, which didn't define this field in their show items.
                        $configuration['default'] = $this->getDefaultFlexFormDefinition();
                    }
                }
                $columnTca = ArrayUtility::setValueByPath($columnTca, $configKey, $configuration, '.');
            }
            if (array_key_exists($optionKey, $column->getTca())) {
                $columnTca[$optionKey] = $column->getTca()[$optionKey];
            }
        }
        // Set the default allowed record for Collections.
        if (
            $column->fieldType instanceof CollectionFieldType
            && $column->fieldType->getAllowedRecordTypes() !== []
            && $tableDefinition->typeField !== null
        ) {
            $allowedRecordTypes = $column->fieldType->getAllowedRecordTypes();
            $defaultAllowedRecordType = $allowedRecordTypes[0];
            $columnTca['config']['overrideChildTca']['columns'][$tableDefinition->typeField]['config']['default'] = $defaultAllowedRecordType;
        }
        // Add TCA for automatically added typeField.
        if ($tableDefinition->typeField === $column->identifier) {
            $columnTca = $column->getTca();
        }
        return $columnTca;
    }

    protected function processOverrideChildTca(TcaFieldDefinition $tcaFieldDefinition, array $columnTca): array
    {
        $typeOverrides = $tcaFieldDefinition->typeOverrides;
        if ($typeOverrides === null) {
            return $columnTca;
        }
        foreach ($typeOverrides as $typeOverride) {
            $contentType = ContentType::getByTable($typeOverride->getTable());
            $showItem = match ($contentType) {
                ContentType::FILE_TYPE => $this->getFileTypeStandardShowItem($typeOverride->getShowItems()),
                ContentType::RECORD_TYPE => $this->getRecordTypeStandardShowItem($typeOverride->getShowItems(), $typeOverride->getTable()),
                ContentType::CONTENT_ELEMENT => $this->processShowItem($typeOverride->getShowItems()),
                ContentType::PAGE_TYPE => $this->getPageTypeStandardShowItem($typeOverride->getShowItems(), $typeOverride->getTypeName()),
            };
            $columnTca['config']['overrideChildTca']['types'][$typeOverride->getTypeName()]['showitem'] = $showItem;
            $columnTca['config']['overrideChildTca']['types'][$typeOverride->getTypeName()]['columnsOverrides'] = $this->getColumnsOverrides($typeOverride);
        }
        return $columnTca;
    }

    protected function getDefaultFlexFormDefinition(): string
    {
        return '<T3DataStructure>
  <ROOT>
    <type>array</type>
    <el>
      <xmlTitle>
        <label>The Title:</label>
        <config>
            <type>input</type>
            <size>48</size>
        </config>
      </xmlTitle>
    </el>
  </ROOT>
</T3DataStructure>';
    }

    /**
     * Some TCA option have the same key, but have completely different meanings.
     * One example is "allowed" for type group and for type file.
     * In this case, only the one for type group should be ignored for overrides.
     * This is done here by comparing the current field type.
     * Returns null, if this is the option of another type.
     *
     * @param string[]|array{type: string, option: string} $option
     */
    protected function getOptionKey(string|array $option, TcaFieldDefinition $tcaFieldDefinition): ?string
    {
        if (is_string($option)) {
            return $option;
        }
        $fieldType = $tcaFieldDefinition->fieldType;
        $fieldTypeName = $fieldType->getName();
        if ($fieldTypeName === $option['type']) {
            return $option['option'];
        }
        return null;
    }

    /**
     * Helper method to clean up empty arrays after options have been removed.
     */
    protected function cleanupEmptyArrays(array &$configuration, string $optionKey): void
    {
        $parts = explode('.', $optionKey);
        // Last part is already removed.
        array_pop($parts);
        $currentOption = &$configuration;
        foreach ($parts as $part) {
            if ($currentOption[$part] === []) {
                unset($currentOption[$part]);
                break;
            }
            $currentOption = &$currentOption[$part];
        }
    }

    /**
     * Label and description overrides. For core fields, fall back to standard translation.
     * For content block fields, fall back to identifier.
     */
    protected function determineLabelAndDescription(ContentTypeInterface $typeDefinition, TcaFieldDefinition $overrideColumn, array $column): array
    {
        $fieldType = $overrideColumn->fieldType;
        if ($fieldType->getTcaType() === 'passthrough') {
            return $column;
        }
        $name = $typeDefinition->getName();
        $labelPath = $overrideColumn->labelPath;
        if ($this->languageFileRegistry->isset($name, $labelPath)) {
            $column['label'] = $labelPath;
        }
        $descriptionPath = $overrideColumn->descriptionPath;
        if ($this->languageFileRegistry->isset($name, $descriptionPath)) {
            $column['description'] = $descriptionPath;
        }
        $placeholderPath = $overrideColumn->placeholderPath;
        if ($this->languageFileRegistry->isset($name, $placeholderPath)) {
            $column['config']['placeholder'] = $placeholderPath;
        }
        $column = $this->determineItemsLabel($typeDefinition, $overrideColumn, $column);
        return $column;
    }

    protected function determineItemsLabel(ContentTypeInterface $typeDefinition, TcaFieldDefinition $tcaFieldDefinition, array $column): array
    {
        $name = $typeDefinition->getName();
        $fieldType = $tcaFieldDefinition->fieldType;
        $itemsFieldTypes = ['select', 'radio', 'check'];
        if (!in_array($fieldType->getTcaType(), $itemsFieldTypes, true)) {
            return $column;
        }
        $items = $column['config']['items'] ?? [];
        foreach ($items as $index => $item) {
            if (isset($item['labelPath'])) {
                $labelPath = $item['labelPath'];
                unset($column['config']['items'][$index]['labelPath']);
                if ($this->languageFileRegistry->isset($name, $labelPath)) {
                    $column['config']['items'][$index]['label'] = $labelPath;
                }
            }
            if (isset($item['labelCheckedPath'])) {
                $labelCheckedPath = $item['labelCheckedPath'];
                unset($column['config']['items'][$index]['labelCheckedPath']);
                if ($this->languageFileRegistry->isset($name, $labelCheckedPath)) {
                    $column['config']['items'][$index]['labelChecked'] = $labelCheckedPath;
                }
            }
            if (isset($item['labelUncheckedPath'])) {
                $labelUncheckedPath = $item['labelUncheckedPath'];
                unset($column['config']['items'][$index]['labelUncheckedPath']);
                if ($this->languageFileRegistry->isset($name, $labelUncheckedPath)) {
                    $column['config']['items'][$index]['labelUnchecked'] = $labelUncheckedPath;
                }
            }
        }
        return $column;
    }

    /**
     * To be compatible with existing FlexForm fields, the type field has to be present inside `ds_pointerField`.
     * If this is not the case, the flexForm field cannot be reused.
     *
     * An exception is a FlexForm field which only defines `default`. In such a case the whole configuration is
     * reused. It's not possible to add a custom set of fields.
     *
     * Furthermore, this method handles the adjustment for multiple pointer fields. The most prominent example would be
     * `pi_flexform`, which points to `list_type` and `CType`. Content Blocks only uses CType by default for Content
     * Elements. Hence, the identifier needs to be prepended by '*,' to match any `list_type`.
     *
     * Example:
     *
     *     // Default only one pointer field
     *     'ds' => [
     *         'example_flexfield' => '...'
     *     ]
     *
     *     // Core config for pi_flexform with CType at the second position
     *     'ds_pointerField' => 'list_type,CType'
     *
     *     // "*," prepended to match anything at position 1
     *     'ds' => [
     *         '*,example_flexfield' => '...'
     *     ]
     */
    protected function processExistingFlexForm(TcaFieldDefinition $column, TableDefinition $tableDefinition, array $baseTca): ?array
    {
        $baseTcaColumns = $baseTca[$tableDefinition->table]['columns'];
        $existingDsPointerField = $baseTcaColumns[$column->uniqueIdentifier]['config']['ds_pointerField'] ?? null;
        if ($existingDsPointerField === null) {
            return null;
        }
        $existingDsPointerFieldArray = GeneralUtility::trimExplode(',', $existingDsPointerField);
        $dsConfiguration = $column->getTca()['config']['ds'];
        $typeSwitchField = $tableDefinition->typeField;
        $fieldPositionInDsPointerFields = array_search($typeSwitchField, $existingDsPointerFieldArray);
        // type field is not compatible.
        if ($fieldPositionInDsPointerFields === false) {
            return null;
        }
        $pointerFieldsCount = count($existingDsPointerFieldArray);
        // If only one valid field exists, no need to add wildcards.
        if ($pointerFieldsCount === 1) {
            return $dsConfiguration;
        }
        $newDsConfiguration = [];
        foreach (array_keys($dsConfiguration) as $dsKey) {
            $dsKeys = [];
            foreach (range(0, $pointerFieldsCount - 1) as $index) {
                if ($index === $fieldPositionInDsPointerFields) {
                    $dsKeys[] = $dsKey;
                    continue;
                }
                $dsKeys[] = '*';
            }
            $newDsConfiguration[implode(',', $dsKeys)] = $dsConfiguration[$dsKey];
        }
        return $newDsConfiguration;
    }

    protected function resolveLabelField(TableDefinition $tableDefinition): ?string
    {
        $labelCapability = $tableDefinition->capability->getLabelCapability();
        $labelField = null;
        if ($labelCapability->hasLabelField()) {
            $labelFieldIdentifier = $labelCapability->getPrimaryLabelField();
            $tableDefinitionCollection = $tableDefinition->tcaFieldDefinitionCollection;
            if (!$tableDefinitionCollection->hasField($labelFieldIdentifier)) {
                return null;
            }
            $labelField = $tableDefinitionCollection->getField($labelFieldIdentifier);
        }
        // If there is no user-defined label field, look for preferred field type first.
        // Otherwise, use first field as label regardless of the type.
        if ($labelField === null) {
            $labelFieldCandidate = null;
            // These are preferred, as they most often provide a meaningful preview of the record.
            $preferredLabelTypes = ['input', 'text', 'email', 'uuid'];
            $tcaFieldDefinitionCollection = $tableDefinition->getDefaultTypeDefinition()->getOverrideColumns();
            foreach ($tcaFieldDefinitionCollection as $columnFieldDefinition) {
                $fieldType = $columnFieldDefinition->fieldType;
                if (in_array($fieldType->getTcaType(), $preferredLabelTypes, true)) {
                    $labelField = $columnFieldDefinition;
                    break;
                }
                $labelFieldCandidate ??= $columnFieldDefinition;
            }
            $labelField ??= $labelFieldCandidate;
        }
        return $labelField?->uniqueIdentifier;
    }

    protected function getContentElementStandardShowItem(ContentTypeInterface $typeDefinition): string
    {
        $showItemArray = $typeDefinition->getShowItems();
        $showItemArray[] = '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended';
        $showItem = $this->processShowItem($showItemArray);
        return $showItem;
    }

    /**
     * @param array<string|PaletteDefinition|TabDefinition> $showItemArray
     */
    protected function getRecordTypeStandardShowItem(array $showItemArray, string $table): string
    {
        $firstItemIsTab = ($showItemArray[0] ?? null) instanceof TabDefinition;
        $generalTab = '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general';
        if ($firstItemIsTab) {
            $tabDefinition = array_shift($showItemArray);
            $generalTab = $this->processShowItem([$tabDefinition]);
        }
        $showItem = $this->processShowItem($showItemArray);
        $parts = [];
        $parts[] = $generalTab;
        if ($showItem !== '') {
            $parts[] = $showItem;
        }
        if ($this->simpleTcaSchemaFactory->has($table)) {
            $tcaSchema = $this->simpleTcaSchemaFactory->get($table);
            $capability = new NativeTableCapabilityProxy($tcaSchema);
        } else {
            $tableDefinition = $this->tableDefinitionCollection->getTable($table);
            $capability = $tableDefinition->capability;
        }
        $systemFields = $this->buildSystemFields($capability);
        $parts = array_merge($parts, $systemFields);
        $showItem = implode(',', $parts);
        return $showItem;
    }

    /**
     * @param array<string|PaletteDefinition|TabDefinition> $showItemArray
     */
    protected function getFileTypeStandardShowItem(array $showItemArray): string
    {
        $firstItemIsTab = ($showItemArray[0] ?? null) instanceof TabDefinition;
        $generalTab = '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general';
        if ($firstItemIsTab) {
            $tabDefinition = array_shift($showItemArray);
            $generalTab = $this->processShowItem([$tabDefinition]);
        }
        $showItem = $this->processShowItem($showItemArray);
        $parts = [];
        $parts[] = $generalTab;
        if ($showItem !== '') {
            $parts[] = $showItem;
        }
        // Add hidden palette with system fields and uid_local.
        $parts[] = '--palette--;;filePalette';
        $showItem = implode(',', $parts);
        return $showItem;
    }

    protected function buildSystemFields(SystemFieldPalettesInterface $capability): array
    {
        $parts = [];
        if ($capability->isLanguageAware()) {
            $parts[] = '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language';
            $parts[] = '--palette--;;language';
        }
        if ($capability->hasDisabledRestriction() || $capability->hasAccessPalette()) {
            $parts[] = '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access';
            if ($capability->hasDisabledRestriction()) {
                $parts[] = '--palette--;;hidden';
            }
            if ($capability->hasAccessPalette()) {
                $parts[] = '--palette--;;access';
            }
        }
        if ($capability->hasInternalDescription()) {
            $parts[] = $capability->buildInternalDescriptionShowItemTca();
        }
        return $parts;
    }

    /**
     * @param array<string|PaletteDefinition|TabDefinition> $showItemArray
     */
    protected function getPageTypeStandardShowItem(array $showItemArray, string|int $typeName): string
    {
        $general = [
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general',
            '--palette--;;standard',
        ];

        $metaTab = [
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.metadata',
            '--palette--;;metatags',
        ];

        $systemTabs = $this->getPageTypeSystemTabs($typeName);

        $seoTab = [
            '--div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.seo',
            '--palette--;;seo',
            '--palette--;;robots',
            '--palette--;;canonical',
            '--palette--;;sitemap',
            '--div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.socialmedia',
            '--palette--;;opengraph',
            '--palette--;;twittercards',
        ];

        $parts[] = $general;
        $showItem = $this->processShowItem($showItemArray);
        if ($showItem !== '') {
            $parts[] = [$showItem];
        }
        $parts[] = $metaTab;
        if (
            $this->systemExtensionAvailability->isAvailable('seo')
            && !PageTypeNameValidator::isExistingPageType($typeName)
        ) {
            $parts[] = $seoTab;
        }
        $parts[] = $systemTabs;

        $showItem = implode(',', array_merge([], ...$parts));
        return $showItem;
    }

    /**
     * @return string[]
     */
    protected function getPageTypeSystemTabs(string|int $typeName): array
    {
        return match ((int)$typeName) {
            PageRepository::DOKTYPE_LINK => $this->getPageTypeExternalShowItemSystemTabs(),
            PageRepository::DOKTYPE_SHORTCUT => $this->getPageTypeShortcutShowItemSystemTabs(),
            default => $this->getPageTypeStandardShowItemSystemTabs(),
        };
    }

    /**
     * @return string[]
     */
    protected function getPageTypeStandardShowItemSystemTabs(): array
    {
        return [
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.appearance',
            '--palette--;;backend_layout',
            '--palette--;;replace',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.behaviour',
            '--palette--;;links',
            '--palette--;;caching',
            '--palette--;;miscellaneous',
            '--palette--;;module',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.resources',
            '--palette--;;config',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language',
            '--palette--;;language',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.access',
            '--palette--;;visibility',
            '--palette--;;access',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes',
            'rowDescription',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended',
        ];
    }

    /**
     * @return string[]
     */
    protected function getPageTypeExternalShowItemSystemTabs(): array
    {
        return [
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.appearance',
            '--palette--;;layout',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.behaviour',
            '--palette--;;miscellaneous',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.resources',
            '--palette--;;config',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language',
            '--palette--;;language',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.access',
            '--palette--;;visibility',
            '--palette--;;access',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes',
            'rowDescription',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended',
        ];
    }

    /**
     * @return string[]
     */
    protected function getPageTypeShortcutShowItemSystemTabs(): array
    {
        return [
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.appearance',
            '--palette--;;layout',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.behaviour',
            '--palette--;;links',
            '--palette--;;miscellaneous',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.resources',
            '--palette--;;config',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language',
            '--palette--;;language',
            '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.access',
            '--palette--;;visibility',
            '--palette--;;access',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes',
            'rowDescription',
            '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended',
        ];
    }

    /**
     * Generate search fields in order to find content elements in global backend search.
     */
    public function generateSearchFields(TableDefinition $tableDefinition, array $baseTca): string
    {
        $searchFieldsString = $baseTca[$tableDefinition->table]['ctrl']['searchFields'] ?? '';
        $searchFields = GeneralUtility::trimExplode(',', $searchFieldsString, true);
        foreach ($tableDefinition->tcaFieldDefinitionCollection as $field) {
            $fieldType = $field->fieldType;
            if ($fieldType->isSearchable() && !in_array($field->uniqueIdentifier, $searchFields, true)) {
                $searchFields[] = $field->uniqueIdentifier;
            }
        }
        if ($searchFields === []) {
            return '';
        }
        $searchFieldsCommaSeparated = implode(',', $searchFields);
        return $searchFieldsCommaSeparated;
    }

    protected function generateBaseTableTca(TableDefinition $tableDefinition): array
    {
        $defaultTypeDefinition = $tableDefinition->getDefaultTypeDefinition();
        $capability = $tableDefinition->capability;
        $columns = [];
        $title = $defaultTypeDefinition->getTitle();
        $title = $title !== '' ? $title : $defaultTypeDefinition->getTable();
        $languagePathTitle = $defaultTypeDefinition->getLanguagePathTitle();
        if ($this->languageFileRegistry->isset($defaultTypeDefinition->getName(), $languagePathTitle)) {
            $title = $languagePathTitle;
        }
        $ctrl = [];
        $ctrl['title'] = $title;
        $labelField = $this->resolveLabelField($tableDefinition);
        if ($labelField !== null) {
            $ctrl['label'] = $labelField;
        }
        $ctrl['hideTable'] = $tableDefinition->capability->isHiddenInUi() || $tableDefinition->hasParentReferences();
        $ctrl['enablecolumns'] = $capability->buildRestrictionsTca();
        $ctrl['previewRenderer'] = PreviewRenderer::class;
        $labelCapability = $tableDefinition->capability->getLabelCapability();
        if ($labelCapability->hasAdditionalLabelFields()) {
            $ctrl['label_alt'] = $labelCapability->getAdditionalLabelFieldsAsString();
            $ctrl['label_alt_force'] = true;
        }
        if ($labelCapability->hasFallbackLabelFields()) {
            $ctrl['label_alt'] = $labelCapability->getFallbackLabelFieldsAsString();
        }
        if ($tableDefinition->hasTypeField()) {
            $ctrl['type'] = $tableDefinition->typeField;
        }
        // If a table is not localizable, but is used as inline child, then 'origUid' is needed.
        if (!$capability->isLanguageAware() && $tableDefinition->hasParentReferences()) {
            $ctrl['origUid'] = 't3_origuid';
        }
        if ($capability->isEditLockingEnabled()) {
            $ctrl['editlock'] = 'editlock';
        }
        if ($capability->hasSoftDelete()) {
            $ctrl['delete'] = 'deleted';
        }
        if ($capability->shallTrackCreationDate()) {
            $ctrl['crdate'] = 'crdate';
        }
        if ($capability->shallTrackUpdateDate()) {
            $ctrl['tstamp'] = 'tstamp';
        }
        if ($capability->isWorkspaceAware() && $this->systemExtensionAvailability->isAvailable('workspaces')) {
            $ctrl['versioningWS'] = true;
        }
        if ($capability->hasInternalDescription()) {
            $ctrl['descriptionColumn'] = 'internal_description';
        }
        if ($capability->hasSortField()) {
            $ctrl['default_sortby'] = $capability->getSortFieldAsString();
        } elseif ($capability->isSortable()) {
            $ctrl['sortby'] = 'sorting';
        }

        $rootLevelCapability = $capability->getRootLevelCapability();
        if ($rootLevelCapability->getRootLevelType() !== RootLevelType::ONLY_ON_PAGES) {
            $ctrl['rootLevel'] = $rootLevelCapability->getRootLevelType()->getTcaValue();
        }
        if ($rootLevelCapability->shallIgnoreRootLevelRestriction()) {
            $ctrl['security']['ignoreRootLevelRestriction'] = true;
        }
        if ($capability->isIgnoreWebMountRestriction()) {
            $ctrl['security']['ignoreWebMountRestriction'] = true;
        }
        if ($capability->isIgnorePageTypeRestriction()) {
            $ctrl['security']['ignorePageTypeRestriction'] = true;
        }
        if ($capability->isReadOnly()) {
            $ctrl['readOnly'] = true;
        }
        if ($capability->isAdminOnly()) {
            $ctrl['adminOnly'] = true;
        }
        if ($capability->shallBeHiddenAtCopy()) {
            $ctrl['hideAtCopy'] = true;
        }
        if ($capability->hasAppendLabelAtCopy()) {
            $ctrl['prependAtCopy'] = $capability->getAppendLabelAtCopy();
        }
        if ($capability->isLanguageAware()) {
            $ctrl += [
                'translationSource' => 'l10n_source',
                'transOrigDiffSourceField' => 'l10n_diffsource',
                'languageField' => 'sys_language_uid',
            ];
        }

        // This is a child table and can only be created by the parent.
        foreach ($tableDefinition->parentReferences as $parentReference) {
            $parentTcaConfig = $parentReference->getTca()['config'];
            if ($parentTcaConfig['MM'] ?? false) {
                continue;
            }
            if (isset($parentTcaConfig['foreign_field'])) {
                $foreignField = $parentTcaConfig['foreign_field'];
                $columns[$foreignField] = [
                    'config' => [
                        'type' => 'passthrough',
                    ],
                ];
            }
            if (isset($parentTcaConfig['foreign_table_field'])) {
                $foreignTableField = $parentTcaConfig['foreign_table_field'];
                $columns[$foreignTableField] = [
                    'config' => [
                        'type' => 'passthrough',
                    ],
                ];
            }
            foreach ($parentTcaConfig['foreign_match_fields'] ?? [] as $foreignMatchField => $foreignMatchValue) {
                $columns[$foreignMatchField] = [
                    'config' => [
                        'type' => 'passthrough',
                    ],
                ];
            }
        }
        $palettes = $this->buildSystemPalettes($capability);
        return [
            'ctrl' => $ctrl,
            'palettes' => $palettes,
            'columns' => $columns,
        ];
    }

    protected function cleanTableTca(array $tca): array
    {
        if (isset($tca['palettes']) && $tca['palettes'] === []) {
            unset($tca['palettes']);
        }
        foreach ($tca['columns'] ?? [] as $identifier => $column) {
            if ($tca['columns'][$identifier] === []) {
                unset($tca['columns'][$identifier]);
            }
        }
        return $tca;
    }

    /**
     * @return array{language?: array, hidden?: array, access?: array}
     */
    protected function buildSystemPalettes(SystemFieldPalettesInterface $capability): array
    {
        $palettes = [];
        if ($capability->isLanguageAware()) {
            $palettes['language'] = [
                'showitem' => $capability->buildLanguageShowItemTca(),
            ];
        }
        if ($capability->hasDisabledRestriction()) {
            $palettes['hidden'] = [
                'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.visibility',
                'showitem' => $capability->buildHiddenShowItemTca(),
            ];
        }
        $access = $capability->buildAccessShowItemTca();
        if ($access !== '') {
            $palettes['access'] = [
                'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.access',
                'showitem' => $access,
            ];
        }
        return $palettes;
    }
}
