<?php

declare(strict_types=1);

use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\Contracts\LifecycleAware;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\LiveComponentHandler;
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\RenderData;
use Tests\Framework\LiveComponents\ComponentTestCase;

uses(ComponentTestCase::class);

beforeEach(function () {
    $this->setUpComponentTest();
});

describe('Lifecycle Hooks - onMount()', function () {
    it('calls onMount() on initial component creation without state', function () {
        $called = false;

        $component = new class (ComponentId::fromString('test:mount'), null, $called) implements LiveComponentContract, LifecycleAware {
            public function __construct(
                private ComponentId $id,
                private ?ComponentData $data,
                private bool &$mountCalled
            ) {
                $this->data = $data ?? ComponentData::fromArray(['value' => 'test']);
            }

            public function getId(): ComponentId
            {
                return $this->id;
            }

            public function getData(): ComponentData
            {
                return $this->data;
            }

            public function getRenderData(): RenderData
            {
                return new RenderData('test', ['value' => $this->data->get('value')]);
            }

            public function onMount(): void
            {
                $this->mountCalled = true;
            }

            public function onUpdate(): void
            {
            }

            public function onDestroy(): void
            {
            }
        };

        // ComponentRegistry should call onMount() when state is null
        $container = $this->container;
        $registry = $container->get(ComponentRegistry::class);

        // Simulate initial creation (no state)
        $resolvedComponent = $registry->resolve($component->getId(), null);

        expect($called)->toBeTrue();
    });

    it('does NOT call onMount() when re-hydrating with existing state', function () {
        $mountCallCount = 0;

        $componentClass = new class () {
            public static int $mountCallCount = 0;

            public static function create(ComponentId $id, ?ComponentData $data): object
            {
                return new class ($id, $data) implements LiveComponentContract, LifecycleAware {
                    public function __construct(
                        private ComponentId $id,
                        private ?ComponentData $data
                    ) {
                        $this->data = $data ?? ComponentData::fromArray(['value' => 'test']);
                    }

                    public function getId(): ComponentId
                    {
                        return $this->id;
                    }

                    public function getData(): ComponentData
                    {
                        return $this->data;
                    }

                    public function getRenderData(): RenderData
                    {
                        return new RenderData('test', ['value' => $this->data->get('value')]);
                    }

                    public function onMount(): void
                    {
                        $GLOBALS['test_mount_count']++;
                    }

                    public function onUpdate(): void
                    {
                    }

                    public function onDestroy(): void
                    {
                    }
                };
            }
        };

        $GLOBALS['test_mount_count'] = 0;

        $registry = $this->container->get(ComponentRegistry::class);
        $componentId = ComponentId::fromString('test:rehydrate');

        // First call with null state - should call onMount()
        $component1 = $componentClass::create($componentId, null);
        $GLOBALS['test_mount_count'] = 0; // Reset
        $resolved1 = $registry->resolve($componentId, null);
        expect($GLOBALS['test_mount_count'])->toBe(1);

        // Second call with state - should NOT call onMount()
        $existingState = ComponentData::fromArray(['value' => 'rehydrated']);
        $component2 = $componentClass::create($componentId, $existingState);
        $GLOBALS['test_mount_count'] = 0; // Reset
        $resolved2 = $registry->resolve($componentId, $existingState);
        expect($GLOBALS['test_mount_count'])->toBe(0); // onMount not called

        unset($GLOBALS['test_mount_count']);
    });
});

describe('Lifecycle Hooks - onUpdate()', function () {
    it('calls onUpdate() after successful action execution', function () {
        $updateCalled = false;

        $component = new class (ComponentId::fromString('test:update'), null, $updateCalled) implements LiveComponentContract, LifecycleAware {
            public function __construct(
                private ComponentId $id,
                private ?ComponentData $data,
                private bool &$updateCalled
            ) {
                $this->data = $data ?? ComponentData::fromArray(['count' => 0]);
            }

            public function getId(): ComponentId
            {
                return $this->id;
            }

            public function getData(): ComponentData
            {
                return $this->data;
            }

            public function getRenderData(): RenderData
            {
                return new RenderData('test', $this->data->toArray());
            }

            public function increment(): ComponentData
            {
                $state = $this->data->toArray();
                $state['count']++;

                return ComponentData::fromArray($state);
            }

            public function onMount(): void
            {
            }

            public function onUpdate(): void
            {
                $this->updateCalled = true;
            }

            public function onDestroy(): void
            {
            }
        };

        // Execute action via handler
        $handler = $this->container->get(LiveComponentHandler::class);
        $params = ActionParameters::fromArray(['_csrf_token' => $this->generateCsrfToken($component)]);

        $result = $handler->handle($component, 'increment', $params);

        expect($updateCalled)->toBeTrue();
        expect($result->state->data['count'])->toBe(1);
    });

    it('calls onUpdate() even if action returns void', function () {
        $updateCalled = false;

        $component = new class (ComponentId::fromString('test:void'), null, $updateCalled) implements LiveComponentContract, LifecycleAware {
            public function __construct(
                private ComponentId $id,
                private ?ComponentData $data,
                private bool &$updateCalled
            ) {
                $this->data = $data ?? ComponentData::fromArray(['value' => 'initial']);
            }

            public function getId(): ComponentId
            {
                return $this->id;
            }

            public function getData(): ComponentData
            {
                return $this->data;
            }

            public function getRenderData(): RenderData
            {
                return new RenderData('test', $this->data->toArray());
            }

            public function doSomething(): void
            {
                // Action that returns void
            }

            public function onMount(): void
            {
            }

            public function onUpdate(): void
            {
                $this->updateCalled = true;
            }

            public function onDestroy(): void
            {
            }
        };

        $handler = $this->container->get(LiveComponentHandler::class);
        $params = ActionParameters::fromArray(['_csrf_token' => $this->generateCsrfToken($component)]);

        $result = $handler->handle($component, 'doSomething', $params);

        expect($updateCalled)->toBeTrue();
    });
});

describe('Lifecycle Hooks - Optional Implementation', function () {
    it('does NOT call hooks if component does not implement LifecycleAware', function () {
        $component = new class (ComponentId::fromString('test:no-hooks')) implements LiveComponentContract {
            public function __construct(
                private ComponentId $id
            ) {
            }

            public function getId(): ComponentId
            {
                return $this->id;
            }

            public function getData(): ComponentData
            {
                return ComponentData::fromArray(['value' => 'test']);
            }

            public function getRenderData(): RenderData
            {
                return new RenderData('test', ['value' => 'test']);
            }

            public function doAction(): ComponentData
            {
                return $this->getData();
            }
        };

        // Should not throw error even without LifecycleAware implementation
        $handler = $this->container->get(LiveComponentHandler::class);
        $params = ActionParameters::fromArray(['_csrf_token' => $this->generateCsrfToken($component)]);

        $result = $handler->handle($component, 'doAction', $params);

        expect($result)->not->toBeNull();
        expect($result->state->data['value'])->toBe('test');
    });
});

describe('Lifecycle Hooks - Error Handling', function () {
    it('catches exceptions in onMount() without failing component creation', function () {
        $component = new class (ComponentId::fromString('test:mount-error')) implements LiveComponentContract, LifecycleAware {
            public function __construct(private ComponentId $id)
            {
            }

            public function getId(): ComponentId
            {
                return $this->id;
            }

            public function getData(): ComponentData
            {
                return ComponentData::fromArray(['value' => 'test']);
            }

            public function getRenderData(): RenderData
            {
                return new RenderData('test', ['value' => 'test']);
            }

            public function onMount(): void
            {
                throw new \RuntimeException('onMount() error');
            }

            public function onUpdate(): void
            {
            }

            public function onDestroy(): void
            {
            }
        };

        $registry = $this->container->get(ComponentRegistry::class);

        // Should not throw - error is logged but component creation succeeds
        $resolved = $registry->resolve($component->getId(), null);

        expect($resolved)->not->toBeNull();
    });

    it('catches exceptions in onUpdate() without failing action execution', function () {
        $component = new class (ComponentId::fromString('test:update-error')) implements LiveComponentContract, LifecycleAware {
            public function __construct(private ComponentId $id)
            {
            }

            public function getId(): ComponentId
            {
                return $this->id;
            }

            public function getData(): ComponentData
            {
                return ComponentData::fromArray(['count' => 0]);
            }

            public function getRenderData(): RenderData
            {
                return new RenderData('test', $this->getData()->toArray());
            }

            public function increment(): ComponentData
            {
                return ComponentData::fromArray(['count' => 1]);
            }

            public function onMount(): void
            {
            }

            public function onUpdate(): void
            {
                throw new \RuntimeException('onUpdate() error');
            }

            public function onDestroy(): void
            {
            }
        };

        $handler = $this->container->get(LiveComponentHandler::class);
        $params = ActionParameters::fromArray(['_csrf_token' => $this->generateCsrfToken($component)]);

        // Should not throw - error is logged but action succeeds
        $result = $handler->handle($component, 'increment', $params);

        expect($result)->not->toBeNull();
        expect($result->state->data['count'])->toBe(1);
    });
});

describe('Lifecycle Hooks - Timer Component Integration', function () {
    it('Timer component implements all lifecycle hooks correctly', function () {
        $timer = new \App\Application\LiveComponents\Timer\TimerComponent(
            id: ComponentId::fromString('timer:test')
        );

        expect($timer)->toBeInstanceOf(LifecycleAware::class);
        expect($timer)->toBeInstanceOf(LiveComponentContract::class);

        // Check getData() returns proper structure
        $data = $timer->getData();
        expect($data->toArray())->toHaveKeys(['seconds', 'isRunning', 'startedAt', 'logs']);
    });

    it('Timer component actions work correctly', function () {
        $timer = new \App\Application\LiveComponents\Timer\TimerComponent(
            id: ComponentId::fromString('timer:test')
        );

        $handler = $this->container->get(LiveComponentHandler::class);

        // Start timer
        $params = ActionParameters::fromArray(['_csrf_token' => $this->generateCsrfToken($timer)]);
        $result = $handler->handle($timer, 'start', $params);

        expect($result->state->data['isRunning'])->toBeTrue();
        expect($result->state->data['startedAt'])->not->toBeNull();

        // Tick
        $timer2 = new \App\Application\LiveComponents\Timer\TimerComponent(
            id: ComponentId::fromString('timer:test'),
            initialData: ComponentData::fromArray($result->state->data)
        );
        $result2 = $handler->handle($timer2, 'tick', $params);

        expect($result2->state->data['seconds'])->toBe(1);

        // Stop
        $timer3 = new \App\Application\LiveComponents\Timer\TimerComponent(
            id: ComponentId::fromString('timer:test'),
            initialData: ComponentData::fromArray($result2->state->data)
        );
        $result3 = $handler->handle($timer3, 'stop', $params);

        expect($result3->state->data['isRunning'])->toBeFalse();
    });
});
