<?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\Core\Schema;

use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidDataStructureException;
use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidTcaSchemaException;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;

/**
 * Low-level API to parse TCA to find field types which should be processed, as they contain
 * a relation.
 *
 * This also parses ALL flexforms available, that's why it juggles through all FlexForm
 * fields and parses the FlexForms as well.
 *
 * Everything is stored in a simple "RelationMap" object with an internal array structure.
 *
 * @internal not part of TYPO3 API as it should not be exposed, although this is really cool and powerful.
 */
final readonly class RelationMapBuilder
{
    public function __construct(
        private FlexFormTools $flexFormTools
    ) {}

    public function buildFromStructure(array $tca): RelationMap
    {
        $relationMap = new RelationMap();
        foreach ($tca as $table => $tableConfig) {
            // What fields can have a relational connection to other tables?
            foreach ($tableConfig['columns'] ?? [] as $fieldName => $fieldConfig) {
                $fieldConfig = $fieldConfig['config'] ?? null;
                if (!in_array($fieldConfig['type'] ?? '', ['select', 'group', 'inline', 'file', 'category', 'flex'], true)) {
                    continue;
                }

                if ($fieldConfig['type'] === 'flex') {
                    $this->addRelationsForFlexFieldToRelationMap($table, $tableConfig, $fieldName, $relationMap);
                } else {
                    $relationMap->add($table, $fieldName, $fieldConfig);
                }
            }
        }
        return $relationMap;
    }

    /**
     * Adds relations for a flex field to the relation map.
     * Note: Inside a section, it is not possible to add a field with a relation (type 'inline', 'file', 'folder', 'group', 'category').
     * See TcaFlexProcess class for details.
     */
    private function addRelationsForFlexFieldToRelationMap(string $tableName, array $tableConfig, string $fieldName, RelationMap $relationMap): void
    {
        foreach (array_merge(['default'], array_keys($tableConfig['types'] ?? [])) as $recordType) {
            try {
                $dataStructure = $this->flexFormTools->parseDataStructureByIdentifier(json_encode([
                    'type' => 'tca',
                    'tableName' => $tableName,
                    'fieldName' => $fieldName,
                    'dataStructureKey' => $recordType,
                ]), $tableConfig);
            } catch (InvalidTcaSchemaException|InvalidIdentifierException|InvalidDataStructureException) {
                // Skip default on error
                continue;
            }

            if (!is_array($dataStructure['sheets'] ?? null)) {
                continue;
            }
            foreach ($dataStructure['sheets'] as $sheetIdentifier => $sheet) {
                foreach ($sheet['ROOT']['el'] as $flexFieldName => $flexFieldConfig) {
                    $fieldIdentifier = $sheetIdentifier . '/' . $flexFieldName;
                    $relationMap->add($tableName, $fieldName, $flexFieldConfig['config'] ?? [], $fieldIdentifier);
                }
            }
        }
    }
}
