<?php
declare(strict_types=1);

namespace Evomedien\Vitec\DataProcessing;

use Evomedien\Vitec\UserFunc\ProductListJsonRenderer;
use Evomedien\Vitec\UserFunc\ProductShowJsonRenderer;
use Evomedien\Vitec\UserFunc\UsecaseListJsonRenderer;
use Evomedien\Vitec\UserFunc\UsecaseShowJsonRenderer;
use Evomedien\Vitec\UserFunc\MarketShowJsonRenderer;
use Evomedien\Vitec\UserFunc\SolutionShowJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;

/**
 * Collect container children grouped by colPos and emit a lean, transport-
 * ready structure. Each child is normalised to:
 *   { id, type, colPos, sorting, appearance, data }
 * `data` only contains non-empty content-relevant fields.
 *
 * VITEC list-plugins nested as container children (productlist, productshow,
 * usecaselist, usecaseshow) are resolved to the SAME JSON the top-level
 * headless rendering produces, injected into `data` under their respective
 * key. The raw pi_flexform XML is then dropped from `data`.
 *
 * Exception-safe.
 */
final class ContainerChildrenProcessor implements DataProcessorInterface
{
    /** Fields that go to the envelope (not to `data`). */
    private const ENVELOPE = [
        'uid', 'CType', 'colPos', 'sorting',
        'layout', 'frame_class', 'space_before_class', 'space_after_class',
    ];

    /** Technical / system / TCA-default fields — never sent to frontend. */
    private const SYSTEM_FIELDS = [
        'pid', 'sys_language_uid', 'l18n_parent', 'l18n_diffsource',
        'l10n_source', 'l10n_state', 'l10n_parent',
        't3_origuid', 'tx_impexp_origuid',
        'tx_container_parent',
        'tstamp', 'crdate', 'cruser_id',
        'hidden', 'deleted', 'starttime', 'endtime', 'fe_group',
        't3ver_oid', 't3ver_wsid', 't3ver_state', 't3ver_stage',
        't3ver_id', 't3ver_label', 't3ver_count', 't3ver_tstamp',
        'editlock', 'sorting_foreign', 'rowDescription',
        'spaceBefore', 'spaceAfter', // legacy
        'imagecols', 'sectionIndex', 'linkToTop', 'recursive', 'date',
        'bullets_type', 'cols',
        'table_delimiter', 'table_enclosure', 'table_header_position',
        'table_tfoot', 'table_caption',
        'filelink_size', 'filelink_sorting', 'filelink_sorting_direction',
        'uploads_description', 'uploads_type',
    ];

    /** Fields whose 0/empty value is still meaningful. */
    private const KEEP_IF_ZERO = [
        'header_layout',
    ];

    /**
     * Nested VITEC list-plugins: list_type => [ rendererClass, jsonKey ].
     * The renderer's renderForRecord() is invoked with the child's own
     * tt_content row so the result is record-accurate (works with multiple
     * containers / plugins on the same page).
     */
    private const PLUGIN_RENDERERS = [
        'vitec_productlist'  => [ProductListJsonRenderer::class, 'products'],
        'vitec_productshow'  => [ProductShowJsonRenderer::class, 'product'],
        'vitec_usecaselist'  => [UsecaseListJsonRenderer::class, 'usecases'],
        'vitec_usecaseshow'  => [UsecaseShowJsonRenderer::class, 'usecase'],
        'vitec_marketshow'   => [MarketShowJsonRenderer::class, 'market'],
        'vitec_solutionshow' => [SolutionShowJsonRenderer::class, 'solution'],
        'vitec_downloadcard' => [DownloadcardJsonRenderer::class, 'downloadcard'],
        'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'],
    ];

    public function process(
        ContentObjectRenderer $cObj,
        array $contentObjectConfiguration,
        array $processorConfiguration,
        array $processedData
    ): array {
        $as = (string)($processorConfiguration['as'] ?? 'items');

        try {
            $parentUid = (int)($cObj->data['uid'] ?? 0);
            if ($parentUid <= 0) {
                $processedData[$as] = [];
                return $processedData;
            }

            $pid = (int)($cObj->data['pid'] ?? 0);
            $sysLanguageUid = (int)($cObj->data['sys_language_uid'] ?? 0);

            $qb = GeneralUtility::makeInstance(ConnectionPool::class)
                ->getQueryBuilderForTable('tt_content');
            $rows = $qb
                ->select('*')
                ->from('tt_content')
                ->where(
                    $qb->expr()->eq('tx_container_parent', $qb->createNamedParameter($parentUid, Connection::PARAM_INT)),
                    $qb->expr()->eq('pid', $qb->createNamedParameter($pid, Connection::PARAM_INT)),
                    $qb->expr()->eq('sys_language_uid', $qb->createNamedParameter($sysLanguageUid, Connection::PARAM_INT))
                )
                ->orderBy('colPos')
                ->addOrderBy('sorting')
                ->executeQuery()
                ->fetchAllAssociative();

            $byColPos = [];
            foreach ($rows as $record) {
                $byColPos[(int)$record['colPos']][] = $this->normalise($record);
            }
            ksort($byColPos);

            $items = [];
            foreach ($byColPos as $colPos => $contentElements) {
                $items[] = [
                    'config' => ['colPos' => $colPos],
                    'contentElements' => $contentElements,
                ];
            }

            $processedData[$as] = $items;
        } catch (\Throwable $e) {
            $processedData[$as] = [];
        }

        return $processedData;
    }

    private function normalise(array $record): array
    {
        $data = [];
        foreach ($record as $field => $value) {
            if (in_array($field, self::ENVELOPE, true)) {
                continue;
            }
            if (in_array($field, self::SYSTEM_FIELDS, true)) {
                continue;
            }
            if ($this->isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) {
                continue;
            }
            $data[$field] = $this->castValue($field, $value);
        }

        // Resolve nested VITEC list-plugins to their headless JSON.
        $this->resolvePluginData($record, $data);

        return [
            'id' => (int)$record['uid'],
            'type' => (string)$record['CType'],
            'colPos' => (int)$record['colPos'],
            'sorting' => (int)($record['sorting'] ?? 0),
            'appearance' => [
                'layout' => (string)($record['layout'] ?? ''),
                'frameClass' => (string)($record['frame_class'] ?? 'default'),
                'spaceBefore' => (string)($record['space_before_class'] ?? ''),
                'spaceAfter' => (string)($record['space_after_class'] ?? ''),
            ],
            'data' => (object)$data,
        ];
    }

    /**
     * If the child is a VITEC list-plugin, run its JSON renderer for THIS
     * record and inject the decoded result under its key. Drops the raw
     * pi_flexform XML afterwards. Never throws.
     *
     * @param array<string,mixed> $record
     * @param array<string,mixed> $data
     */
    private function resolvePluginData(array $record, array &$data): void
    {
        // v14: plugins are their own CType; legacy elements still carry list_type.
        $cType = (string)($record['CType'] ?? '');
        $listType = (string)($record['list_type'] ?? '');
        $key = isset(self::PLUGIN_RENDERERS[$cType]) ? $cType
             : (isset(self::PLUGIN_RENDERERS[$listType]) ? $listType : null);
        if ($key === null) {
            return;
        }

        try {
            [$rendererClass, $jsonKey] = self::PLUGIN_RENDERERS[$key];
            $renderer = GeneralUtility::makeInstance($rendererClass);

            if (!method_exists($renderer, 'renderForRecord')) {
                return;
            }

            $json = $renderer->renderForRecord($record);
            if ($json === '' || $json === null) {
                return;
            }

            $decoded = json_decode($json, true);
            if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
                return;
            }

            $data[$jsonKey] = $decoded;

            // The raw FlexForm XML is noise once the plugin is resolved.
            unset($data['pi_flexform']);
        } catch (\Throwable $e) {
            // Leave the raw data untouched on any failure.
        }
    }

    private function isEmpty(mixed $value): bool
    {
        return $value === null
            || $value === ''
            || $value === 0
            || $value === '0';
    }

    private function castValue(string $field, mixed $value): mixed
    {
        if (in_array($field, ['header_layout'], true)) {
            return (int)$value;
        }
        return $value;
    }
}
