<?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\Form\Utility;

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\SystemResource\Exception\SystemResourceException;
use TYPO3\CMS\Core\SystemResource\Publishing\SystemResourcePublisherInterface;
use TYPO3\CMS\Core\SystemResource\SystemResourceFactory;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;

/**
 * This is a static, internal and intermediate helper class for various
 * FormEngine related tasks.
 *
 * This class was introduced to help disentangling FormEngine and
 * its sub classes. It MUST NOT be used in other extensions and will
 * change or vanish without further notice.
 *
 * @internal
 * @todo: These helpers are target to be dropped if further FormEngine refactoring is done
 */
class FormEngineUtility
{
    /**
     * Whitelist that allows TCA field configuration to be overridden by TSconfig
     *
     * @see overrideFieldConf()
     * @var array
     */
    protected static $allowOverrideMatrix = [
        'input' => ['size', 'max', 'readOnly'],
        'number' => ['size', 'readOnly'],
        'email' => ['size', 'readOnly'],
        'link' => ['size', 'readOnly'],
        'password' => ['size', 'readOnly'],
        'datetime' => ['size', 'readOnly'],
        'color' => ['size', 'readOnly'],
        'uuid' => ['size', 'enableCopyToClipboard'],
        'text' => ['cols', 'rows', 'wrap', 'max', 'readOnly'],
        'json' => ['cols', 'rows', 'readOnly'],
        'check' => ['cols', 'readOnly'],
        'select' => ['size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'treeConfig', 'fileFolderConfig'],
        'category' => ['size', 'maxitems', 'minitems', 'readOnly', 'treeConfig'],
        'group' => ['size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'elementBrowserEntryPoints'],
        'folder' => ['size', 'autoSizeMax', 'maxitems', 'minitems', 'readOnly', 'elementBrowserEntryPoints'],
        'inline' => ['appearance', 'behaviour', 'foreign_label', 'foreign_selector', 'foreign_unique', 'maxitems', 'minitems', 'size', 'autoSizeMax', 'symmetric_label', 'readOnly'],
        'file' => ['appearance', 'behaviour', 'maxitems', 'minitems', 'readOnly'],
        'imageManipulation' => ['ratios', 'cropVariants'],
    ];

    /**
     * Overrides the TCA field configuration by TSconfig settings.
     *
     * Example TSconfig: TCEform.<table>.<field>.config.appearance.useSortable = 1
     * This overrides the setting in $GLOBALS['TCA'][<table>]['columns'][<field>]['config']['appearance']['useSortable'].
     *
     * @param array $fieldConfig $GLOBALS['TCA'] field configuration
     * @param array $TSconfig TSconfig
     * @return array Changed TCA field configuration
     * @internal
     */
    public static function overrideFieldConf($fieldConfig, $TSconfig)
    {
        if (is_array($TSconfig)) {
            $TSconfig = GeneralUtility::removeDotsFromTS($TSconfig);
            $type = $fieldConfig['type'] ?? '';
            if (isset($TSconfig['config']) && is_array($TSconfig['config']) && is_array(static::$allowOverrideMatrix[$type] ?? null)) {
                // Check if the keys in TSconfig['config'] are allowed to override TCA field config:
                foreach ($TSconfig['config'] as $key => $_) {
                    if (!in_array($key, static::$allowOverrideMatrix[$type], true)) {
                        unset($TSconfig['config'][$key]);
                    }
                }
                // Override $GLOBALS['TCA'] field config by remaining TSconfig['config']:
                if (!empty($TSconfig['config'])) {
                    ArrayUtility::mergeRecursiveWithOverrule($fieldConfig, $TSconfig['config']);
                }
            }
        }
        return $fieldConfig;
    }

    /**
     * Returns TSconfig for given table and row
     *
     * @param string $table The table name
     * @param array $row The table row - Must at least contain the "uid" value, even if "NEW..." string.
     *                   The "pid" field is important as well, negative values will be interpreted as pointing to a record from the same table.
     * @param string $field Optionally specify the field name as well. In that case the TSconfig for this field is returned.
     * @return mixed The TSconfig values - probably in an array
     * @internal
     */
    public static function getTSconfigForTableRow($table, $row, $field = '')
    {
        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
        $cache = $runtimeCache->get('formEngineUtilityTsConfigForTableRow') ?: [];
        $cacheIdentifier = $table . ':' . $row['uid'];
        if (!isset($cache[$cacheIdentifier])) {
            $cache[$cacheIdentifier] = self::getTCEFORM_TSconfig($table, $row);
            $runtimeCache->set('formEngineUtilityTsConfigForTableRow', $cache);
        }
        if ($field && isset($cache[$cacheIdentifier][$field])) {
            return $cache[$cacheIdentifier][$field];
        }
        return $cache[$cacheIdentifier];
    }

    /**
     * Returns TSConfig for the TCEFORM object in page TSconfig.
     * Used in TCEFORMs
     *
     * @param string $table Table name present in TCA
     * @param array $row Row from table
     */
    public static function getTCEFORM_TSconfig(string $table, array $row): array
    {
        $res = [];
        $uid = $row['uid'] ?? 0;
        $pid = $row['pid'] ?? 0;
        // Get main config for the table
        // If pid is negative (referring to another record) the pid of the other record is fetched and returned.
        $cPid = BackendUtility::getTSconfig_pidValue($table, $uid, $pid);
        // $TScID is the id of $table = pages, else it's the pid of the record.
        $TScID = $table === 'pages' && MathUtility::canBeInterpretedAsInteger($uid) ? $uid : $cPid;
        if ($TScID >= 0) {
            $tsConfig = BackendUtility::getPagesTSconfig($TScID)['TCEFORM.'][$table . '.'] ?? [];
            $typeVal = BackendUtility::getTCAtypeValue($table, $row, true);
            foreach ($tsConfig as $key => $val) {
                if (is_array($val)) {
                    $fieldN = substr($key, 0, -1);
                    $res[$fieldN] = $val;
                    unset($res[$fieldN]['types.']);
                    if ($typeVal !== null && is_array($val['types.'][$typeVal . '.'] ?? false)) {
                        $res[$fieldN] = array_replace_recursive($res[$fieldN], $val['types.'][$typeVal . '.']);
                    }
                }
            }
        }
        $res['_CURRENT_PID'] = $cPid;
        $res['_THIS_UID'] = $row['uid'] ?? 0;
        // So the row will be passed to foreign_table_where_query()
        $res['_THIS_ROW'] = $row;
        return $res;
    }

    /**
     * Renders the $icon, supports a filename for skinImg or sprite-icon-name
     *
     * @param string $icon The icon passed, could be a file-reference or a sprite Icon name
     * @param string $alt Alt attribute of the icon returned
     * @param string $title Title attribute of the icon return
     * @return string A tag representing to show the asked icon
     * @internal
     */
    public static function getIconHtml($icon, $alt = '', $title = '')
    {
        $icon = (string)$icon;
        try {
            $resourceFactory = GeneralUtility::makeInstance(SystemResourceFactory::class);
            $resource = $resourceFactory->createPublicResource($icon);
            $resourcePublisher = GeneralUtility::makeInstance(SystemResourcePublisherInterface::class);
            $iconUri = $resourcePublisher->generateUri($resource, null);
            return '<img'
                . ' loading="lazy" '
                . ' src="' . htmlspecialchars((string)$iconUri) . '"'
                . ' alt="' . htmlspecialchars($alt) . '" '
                . ($title ? 'title="' . htmlspecialchars($title) . '"' : '')
                . ' />';
        } catch (SystemResourceException) {
            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
            return $iconFactory
                ->getIcon($icon, IconSize::SMALL)
                ->setTitle($title)
                ->render('inline');
        }
    }
}
