<?php

declare(strict_types=1);

namespace App\Framework\View;

use App\Framework\Http\Session\SessionInterface;
use App\Framework\Meta\MetaData;

/**
 * Renders LiveComponent templates
 *
 * Separates rendering concerns from LiveComponent business logic.
 * LiveComponents should only handle state and actions, not template rendering.
 */
final readonly class LiveComponentRenderer
{
    public function __construct(
        private TemplateRenderer $templateRenderer,
        private SessionInterface $session
    ) {
    }

    /**
     * Render a LiveComponent template with data
     *
     * @param string $templatePath Template path (without extension)
     * @param array $data Template data
     * @param string $componentId Full component ID (e.g., "counter:demo")
     * @return string Rendered HTML
     */
    public function render(string $templatePath, array $data, string $componentId): string
    {
        // Merge component ID into data for templates
        $templateData = array_merge($data, [
            'componentId' => $componentId,
        ]);

        // Create component render context
        // Use COMPONENT mode to enable template logic (ForProcessor, IfProcessor, etc.)
        // but skip layout/meta/asset processors
        $context = new RenderContext(
            template: $templatePath,
            metaData: new MetaData(''),
            data: $templateData,
            processingMode: ProcessingMode::COMPONENT
        );

        return $this->templateRenderer->renderPartial($context);
    }

    /**
     * Render component wrapper HTML with state, CSRF protection, SSE support, and polling
     *
     * Generates a component-specific CSRF token for secure action execution.
     * Each component instance gets its own token for isolation.
     *
     * If the component supports SSE (has getSseChannel() method), the SSE channel
     * will be rendered as data-sse-channel attribute for automatic real-time updates.
     *
     * If the component implements Pollable interface, the poll interval (in milliseconds)
     * will be rendered as data-poll-interval attribute for automatic polling.
     *
     * @param string $componentId Full component ID (e.g., "counter:demo")
     * @param string $componentHtml Rendered component HTML
     * @param array $state Component state data
     * @param string|null $sseChannel Optional SSE channel for real-time updates
     * @param int|null $pollInterval Optional poll interval in milliseconds
     * @return string Complete component HTML with wrapper
     */
    public function renderWithWrapper(
        string $componentId,
        string $componentHtml,
        array $state,
        ?string $sseChannel = null,
        ?int $pollInterval = null
    ): string {
        // Extract component name from ID (format: "counter:demo")
        $componentName = explode(':', $componentId)[0] ?? 'unknown';

        // Generate component-specific CSRF token
        // Use component ID as form ID for per-component isolation
        $formId = 'livecomponent:' . $componentId;
        $csrfToken = $this->session->csrf->generateToken($formId);

        $stateJson = json_encode([
            'id' => $componentId,
            'component' => $componentName,
            'data' => $state,
            'version' => 1,
        ]);

        // Build attributes
        $attributes = [
            'data-live-component' => $componentId,
            'data-state' => $stateJson,
            'data-csrf-token' => $csrfToken->toString(),
        ];

        // Add SSE channel if provided
        if ($sseChannel !== null) {
            $attributes['data-sse-channel'] = $sseChannel;
        }

        // Add poll interval if provided
        if ($pollInterval !== null) {
            $attributes['data-poll-interval'] = (string) $pollInterval;
        }

        // Build attribute string
        // IMPORTANT: data-state is already JSON and will be parsed by HTMLDocument
        // which handles escaping correctly. We DON'T htmlspecialchars the JSON
        // because it gets parsed and re-serialized by DOM, which handles escaping.
        $attributeString = implode(' ', array_map(
            function ($key, $value) {
                // For data-state, we use the JSON directly without additional escaping
                // The DOM parser will handle proper escaping when the HTML is parsed
                if ($key === 'data-state') {
                    return sprintf('%s=\'%s\'', $key, $value);
                }
                // For other attributes, use standard escaping
                return sprintf('%s="%s"', $key, htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
            },
            array_keys($attributes),
            $attributes
        ));

        return sprintf(
            '<div %s>%s</div>',
            $attributeString,
            $componentHtml
        );
    }
}
