<?php

declare(strict_types=1);

use App\Framework\DI\Container;
use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\LiveComponentHandler;
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentUpdate;
use App\Framework\View\LiveComponentRenderer;

/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/

// uses(Tests\TestCase::class)->in('Feature');

/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/

expect()->extend('toBeOne', function () {
    return $this->toBe(1);
});

// LiveComponent Custom Expectations

/**
 * Assert HTML contains text
 * Usage: expect($html)->toContainHtml('Count: 5')
 */
expect()->extend('toContainHtml', function (string $needle) {
    $haystack = is_string($this->value) ? $this->value : (string) $this->value;

    expect($haystack)->toContain($needle);

    return $this;
});

/**
 * Assert state equals expected array
 * Usage: expect($state)->toHaveState(['count' => 5])
 */
expect()->extend('toHaveState', function (array $expected) {
    $actual = is_array($this->value) ? $this->value : $this->value->toArray();

    expect($actual)->toBe($expected);

    return $this;
});

/**
 * Assert specific state key equals value
 * Usage: expect($state)->toHaveStateKey('count', 5)
 */
expect()->extend('toHaveStateKey', function (string $key, mixed $value) {
    $actual = is_array($this->value) ? $this->value : $this->value->toArray();

    expect($actual)->toHaveKey($key);
    expect($actual[$key])->toBe($value);

    return $this;
});

/**
 * Assert event was dispatched
 * Usage: expect($events)->toHaveDispatchedEvent('counter-updated')
 */
expect()->extend('toHaveDispatchedEvent', function (string $eventName) {
    $events = $this->value; // Array von ComponentEvent VOs

    $found = false;
    foreach ($events as $event) {
        if ($event->name === $eventName) {
            $found = true;
            break;
        }
    }

    expect($found)->toBeTrue("Event '{$eventName}' was not dispatched");

    return $this;
});

/**
 * Assert event was dispatched with specific data
 * Usage: expect($events)->toHaveDispatchedEventWithData('user-updated', ['id' => 123])
 */
expect()->extend('toHaveDispatchedEventWithData', function (string $eventName, array $expectedData) {
    $events = $this->value;

    $found = false;
    foreach ($events as $event) {
        if ($event->name === $eventName && $event->data === $expectedData) {
            $found = true;
            break;
        }
    }

    expect($found)->toBeTrue(
        "Event '{$eventName}' with data " . json_encode($expectedData) . " was not dispatched"
    );

    return $this;
});

/**
 * Assert HTML contains fragment with specific content
 * Usage: expect($html)->toHaveFragment('user-stats', 'Total: 10')
 */
expect()->extend('toHaveFragment', function (string $fragmentName, string $needle) {
    $html = is_string($this->value) ? $this->value : (string) $this->value;

    // Check if fragment exists
    $pattern = '/data-lc-fragment=["\']' . preg_quote($fragmentName, '/') . '["\']/';
    expect($html)->toMatch($pattern, "Fragment '{$fragmentName}' not found in HTML");

    // Check if fragment contains content
    expect($html)->toContain($needle);

    return $this;
});

/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/

/**
 * Get DI Container instance
 */
function container(): Container
{
    static $container = null;

    if ($container === null) {
        // Bootstrap container for tests
        require_once __DIR__ . '/../bootstrap/app.php';
        $container = $GLOBALS['container'] ?? throw new RuntimeException('Container not bootstrapped');
    }

    return $container;
}

/**
 * Mount a LiveComponent with initial state
 *
 * @param string $componentId Component ID (e.g., 'counter:test')
 * @param array $initialData Initial component data
 * @return array ['html' => string, 'state' => array, 'componentId' => string, 'component' => LiveComponentContract]
 */
function mountComponent(string $componentId, array $initialData = []): array
{
    $registry = container()->get(ComponentRegistry::class);
    $renderer = container()->get(LiveComponentRenderer::class);

    // Resolve component instance
    $component = $registry->resolve(ComponentId::fromString($componentId), $initialData);

    // Render component
    $html = $renderer->render($component);

    // Get current state
    $state = $component->state->toArray();

    return [
        'html' => $html,
        'state' => $state,
        'componentId' => $componentId,
        'component' => $component,
        'events' => [], // Initial mount has no events
    ];
}

/**
 * Call action on a LiveComponent
 *
 * @param array $component Component array from mountComponent()
 * @param string $action Action name
 * @param array $params Action parameters
 * @return array ['html' => string, 'state' => array, 'events' => ComponentEvent[], 'componentId' => string]
 */
function callAction(array $component, string $action, array $params = []): array
{
    $handler = container()->get(LiveComponentHandler::class);
    $renderer = container()->get(LiveComponentRenderer::class);

    // Execute action
    $result = $handler->handleAction(
        $component['component'],
        $action,
        ActionParameters::fromArray($params)
    );

    // Render updated component
    $html = $renderer->render($result->component);

    return [
        'html' => $html,
        'state' => $result->component->state->toArray(),
        'events' => $result->events,
        'componentId' => $component['componentId'],
        'component' => $result->component,
    ];
}

/**
 * Call action and expect fragments to be updated
 *
 * @param array $component Component array from mountComponent()
 * @param string $action Action name
 * @param array $params Action parameters
 * @param array $fragments Fragment names to extract
 * @return array ['fragments' => array, 'state' => array, 'events' => ComponentEvent[]]
 */
function callActionWithFragments(array $component, string $action, array $params = [], array $fragments = []): array
{
    $handler = container()->get(LiveComponentHandler::class);
    $fragmentRenderer = container()->get(\App\Framework\LiveComponents\Rendering\FragmentRenderer::class);

    // Execute action
    $result = $handler->handleAction(
        $component['component'],
        $action,
        ActionParameters::fromArray($params)
    );

    // Render fragments if requested
    $fragmentsHtml = [];
    if (!empty($fragments)) {
        $fragmentCollection = $fragmentRenderer->renderFragments(
            $result->component,
            $fragments
        );

        foreach ($fragmentCollection as $fragment) {
            $fragmentsHtml[$fragment->name] = $fragment->content;
        }
    }

    return [
        'fragments' => $fragmentsHtml,
        'state' => $result->component->state->toArray(),
        'events' => $result->events,
        'componentId' => $component['componentId'],
        'component' => $result->component,
    ];
}
