<?php

declare(strict_types=1);

namespace Evomedien\Vitec\Service;

use Doctrine\DBAL\ParameterType;
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 TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Resolves a TYPO3 typolink string (as stored by `inputLink` fields like
 * Product.contentelement / Product.contentelementcta) to the JSON
 * representation of the referenced tt_content element.
 *
 * Mirrors the shape produced by
 * {@see \Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor}:
 *   { id, type, colPos, sorting, appearance, data }
 *
 * Nested VITEC list-plugins are resolved through their `renderForRecord()`
 * just like container children, so a referenced productlist / productshow /
 * usecaselist / usecaseshow appears with its full headless JSON in `data`.
 *
 * Exception-safe: every public entry point returns `null` on any failure
 * so the surrounding JSON output stays clean.
 */
final class ContentElementResolver
{
    /** 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',
        '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',
    ];

    private const KEEP_IF_ZERO = ['header_layout'];

    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'],
    ];

    /**
     * Resolve a typolink string to a normalised tt_content JSON element.
     *
     * Accepted forms (all may include an optional anchor fragment):
     *   - "t3://record?identifier=tt_content&uid=N"
     *   - "t3://page?uid=PAGE#N"                   (link-popup: pick a CE on a page;
     *                                               the fragment is the tt_content uid)
     *   - "<numeric>"                              (legacy: bare tt_content uid)
     *   - everything else                          → null  (pure page links etc.)
     *
     * @return array<string,mixed>|null
     */
    public static function resolveLink(?string $link): ?array
    {
        try {
            $link = trim((string)$link);
            if ($link === '') {
                return null;
            }

            $uid = self::extractTtContentUid($link);
            if ($uid <= 0) {
                return null;
            }

            $qb = GeneralUtility::makeInstance(ConnectionPool::class)
                ->getQueryBuilderForTable('tt_content');
            $row = $qb
                ->select('*')
                ->from('tt_content')
                ->where(
                    $qb->expr()->eq('uid', $qb->createNamedParameter($uid, ParameterType::INTEGER)),
                    $qb->expr()->eq('deleted', 0),
                    $qb->expr()->eq('hidden', 0)
                )
                ->executeQuery()
                ->fetchAssociative();

            if (!$row) {
                return null;
            }

            return self::normaliseRecord($row);
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * Normalise a tt_content DB row to the same envelope shape that
     * {@see \Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor}
     * emits for container children. VITEC list-plugin children are
     * resolved to their headless JSON.
     *
     * @param array<string,mixed> $record
     * @return array<string,mixed>
     */
    public static function normaliseRecord(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 (self::isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) {
                continue;
            }
            $data[$field] = self::castValue($field, $value);
        }

        self::resolvePluginData($record, $data);

        return [
            'id' => (int)$record['uid'],
            'type' => (string)$record['CType'],
            'colPos' => (int)($record['colPos'] ?? 0),
            '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,
        ];
    }

    /**
     * Extract the tt_content uid from a typolink string.
     *
     * Handles four forms:
     *   1. plain numeric                                 → tt_content uid
     *   2. t3://record?identifier=tt_content&uid=N       → uid N
     *   3. t3://record?identifier=tt_content&uid=N#X     → uid N (anchor ignored)
     *   4. t3://page?uid=PAGE#N                          → uid N from fragment
     *      (link-popup picks a content element on a page; the fragment is
     *      the tt_content uid, the query is the page uid)
     */
    private static function extractTtContentUid(string $link): int
    {
        // Form 1: bare numeric uid
        if (ctype_digit($link)) {
            return (int)$link;
        }

        $parts = parse_url($link);
        if (!is_array($parts)) {
            return 0;
        }

        // Forms 2 & 3: t3://record?identifier=tt_content&uid=N
        if (str_starts_with($link, 't3://record') && !empty($parts['query'])) {
            $params = [];
            parse_str((string)$parts['query'], $params);
            $identifier = (string)($params['identifier'] ?? '');
            $uid = (int)($params['uid'] ?? 0);
            if ($identifier === 'tt_content' && $uid > 0) {
                return $uid;
            }
        }

        // Form 4: t3://page?uid=PAGE#N  → tt_content uid lives in the fragment
        if (str_starts_with($link, 't3://page')) {
            $fragment = (string)($parts['fragment'] ?? '');
            if (ctype_digit($fragment)) {
                return (int)$fragment;
            }
        }

        return 0;
    }

    /**
     * If the record is a VITEC list-plugin, run its renderer for THIS row
     * and inject the decoded result under its key. Drops raw pi_flexform.
     *
     * @param array<string,mixed> $record
     * @param array<string,mixed> $data
     */
    private static function resolvePluginData(array $record, array &$data): void
    {
        $listType = (string)($record['list_type'] ?? '');
        if ($listType === '' || !isset(self::PLUGIN_RENDERERS[$listType])) {
            return;
        }

        try {
            [$rendererClass, $jsonKey] = self::PLUGIN_RENDERERS[$listType];
            $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;
            unset($data['pi_flexform']);
        } catch (\Throwable $e) {
            // leave raw data on failure
        }
    }

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

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