<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Playground;

use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\LiveComponentHandler;
use App\Framework\LiveComponents\Performance\CompiledComponentMetadata;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;

/**
 * PlaygroundService
 *
 * Development tool for testing and previewing LiveComponents in isolation.
 *
 * Features:
 * - List all registered components
 * - Get component metadata (properties, actions, lifecycle hooks)
 * - Preview components with custom state
 * - Execute component actions for testing
 * - Performance metrics collection
 */
final readonly class PlaygroundService
{
    public function __construct(
        private ComponentRegistry $componentRegistry,
        private LiveComponentHandler $handler
    ) {
    }

    /**
     * List all registered LiveComponents with metadata
     *
     * @return array{
     *     total: int,
     *     components: array<array{
     *         name: string,
     *         class: string,
     *         properties: int,
     *         actions: int,
     *         lifecycle_hooks: array<string>,
     *         has_cache: bool
     *     }>
     * }
     */
    public function listComponents(): array
    {
        $componentNames = $this->componentRegistry->getAllComponentNames();
        $components = [];

        foreach ($componentNames as $name) {
            $className = $this->componentRegistry->getClassName($name);
            $metadata = $this->componentRegistry->getMetadata($name);

            $components[] = [
                'name' => $name,
                'class' => $className ?? '',
                'properties' => count($metadata->properties),
                'actions' => count($metadata->actions),
                'lifecycle_hooks' => $this->getLifecycleHooks($metadata),
                'has_cache' => $this->hasCacheSupport($className ?? ''),
            ];
        }

        // Sort by name
        usort($components, fn ($a, $b) => strcmp($a['name'], $b['name']));

        return [
            'total' => count($components),
            'components' => $components,
        ];
    }

    /**
     * Get detailed metadata for specific component
     *
     * @return array{
     *     name: string,
     *     class: string,
     *     properties: array<array{
     *         name: string,
     *         type: string,
     *         nullable: bool,
     *         hasDefault: bool
     *     }>,
     *     actions: array<array{
     *         name: string,
     *         parameters: array<array{
     *             name: string,
     *             type: string,
     *             nullable: bool,
     *             hasDefault: bool
     *         }>
     *     }>,
     *     lifecycle_hooks: array<string>,
     *     has_cache: bool
     * }
     */
    public function getComponentMetadata(string $componentName): array
    {
        if (! $this->componentRegistry->isRegistered($componentName)) {
            throw new \InvalidArgumentException("Component not registered: {$componentName}");
        }

        $className = $this->componentRegistry->getClassName($componentName);
        $metadata = $this->componentRegistry->getMetadata($componentName);

        return [
            'name' => $componentName,
            'class' => $className ?? '',
            'properties' => $this->formatProperties($metadata),
            'actions' => $this->formatActions($metadata),
            'lifecycle_hooks' => $this->getLifecycleHooks($metadata),
            'has_cache' => $this->hasCacheSupport($className ?? ''),
        ];
    }

    /**
     * Preview component with custom state
     *
     * @param string $componentName Component name (e.g., 'counter')
     * @param array<string, mixed> $state Custom state data
     * @param string|null $instanceId Instance ID (default: 'playground')
     * @return array{
     *     success: bool,
     *     html: string,
     *     state: array<string, mixed>,
     *     component_id: string,
     *     render_time_ms: float,
     *     metadata: array
     * }
     */
    public function previewComponent(
        string $componentName,
        array $state = [],
        ?string $instanceId = null
    ): array {
        if (! $this->componentRegistry->isRegistered($componentName)) {
            throw new \InvalidArgumentException("Component not registered: {$componentName}");
        }

        $startTime = microtime(true);
        $instanceId = $instanceId ?? 'playground';

        // Create component ID
        $componentId = ComponentId::create($componentName, $instanceId);

        // Create state
        $componentData = ! empty($state)
            ? ComponentData::fromArray($state)
            : null;

        // Resolve component
        $component = $this->componentRegistry->resolve($componentId, $componentData);

        // Render with wrapper
        $html = $this->componentRegistry->renderWithWrapper($component);

        $renderTime = (microtime(true) - $startTime) * 1000;

        return [
            'success' => true,
            'html' => $html,
            'state' => $component->getData()->toArray(),
            'component_id' => $componentId->toString(),
            'render_time_ms' => round($renderTime, 2),
            'metadata' => $this->getComponentMetadata($componentName),
        ];
    }

    /**
     * Execute component action for testing
     *
     * @param string $componentId Component ID (e.g., 'counter:playground')
     * @param string $actionName Action name
     * @param array<string, mixed> $parameters Action parameters
     * @param array<string, mixed> $currentState Current component state
     * @return array{
     *     success: bool,
     *     new_state: array<string, mixed>,
     *     html: string,
     *     execution_time_ms: float,
     *     error?: string
     * }
     */
    public function executeAction(
        string $componentId,
        string $actionName,
        array $parameters = [],
        array $currentState = []
    ): array {
        $startTime = microtime(true);

        try {
            // Parse component ID
            [$componentName, $instanceId] = explode(':', $componentId, 2);

            if (! $this->componentRegistry->isRegistered($componentName)) {
                throw new \InvalidArgumentException("Component not registered: {$componentName}");
            }

            // Check if action exists
            if (! $this->componentRegistry->hasAction($componentName, $actionName)) {
                throw new \InvalidArgumentException("Action not found: {$actionName}");
            }

            // Create component with current state
            $componentData = ! empty($currentState)
                ? ComponentData::fromArray($currentState)
                : null;

            $component = $this->componentRegistry->resolve(
                ComponentId::fromString($componentId),
                $componentData
            );

            // Execute action
            $this->handler->callAction($component, $actionName, $parameters);

            // Re-render component
            $html = $this->componentRegistry->renderWithWrapper($component);

            $executionTime = (microtime(true) - $startTime) * 1000;

            return [
                'success' => true,
                'new_state' => $component->getData()->toArray(),
                'html' => $html,
                'execution_time_ms' => round($executionTime, 2),
            ];
        } catch (\Throwable $e) {
            $executionTime = (microtime(true) - $startTime) * 1000;

            return [
                'success' => false,
                'new_state' => $currentState,
                'html' => '',
                'execution_time_ms' => round($executionTime, 2),
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Get lifecycle hooks for component
     *
     * @return array<string>
     */
    private function getLifecycleHooks(CompiledComponentMetadata $metadata): array
    {
        $hooks = [];

        // Check for lifecycle methods in actions list
        $lifecycleMethods = ['onMount', 'onUpdated', 'onDestroy'];

        foreach ($lifecycleMethods as $method) {
            if ($metadata->hasAction($method)) {
                $hooks[] = $method;
            }
        }

        return $hooks;
    }

    /**
     * Check if component has cache support
     */
    private function hasCacheSupport(string $className): bool
    {
        if (empty($className) || ! class_exists($className)) {
            return false;
        }

        $interfaces = class_implements($className);

        return isset($interfaces['App\\Framework\\LiveComponents\\Contracts\\Cacheable']);
    }

    /**
     * Format properties for API response
     *
     * @return array<array{
     *     name: string,
     *     type: string,
     *     nullable: bool,
     *     hasDefault: bool
     * }>
     */
    private function formatProperties(CompiledComponentMetadata $metadata): array
    {
        $formatted = [];

        foreach ($metadata->properties as $name => $property) {
            $formatted[] = [
                'name' => $name,
                'type' => $property->type,
                'nullable' => $property->nullable,
                'hasDefault' => $property->hasDefaultValue,
            ];
        }

        return $formatted;
    }

    /**
     * Format actions for API response
     *
     * @return array<array{
     *     name: string,
     *     parameters: array<array{
     *         name: string,
     *         type: string,
     *         nullable: bool,
     *         hasDefault: bool
     *     }>
     * }>
     */
    private function formatActions(CompiledComponentMetadata $metadata): array
    {
        $formatted = [];

        foreach ($metadata->actions as $name => $action) {
            $parameters = [];

            foreach ($action->parameters as $paramName => $param) {
                $parameters[] = [
                    'name' => $paramName,
                    'type' => $param->type,
                    'nullable' => $param->nullable,
                    'hasDefault' => $param->hasDefaultValue,
                ];
            }

            $formatted[] = [
                'name' => $name,
                'parameters' => $parameters,
            ];
        }

        return $formatted;
    }
}
