<?php
namespace boru\ai\Tests\Stream;

use boru\ai\Stream\Listener\CompletionStatusListener;
use boru\ai\Stream\Listener\FunctionCallListener;
use boru\ai\Stream\Listener\OutputBufferListener;
use boru\ai\Stream\StreamEvent;
use boru\ai\Stream\StreamEventDispatcher;
use boru\ai\Stream\StreamEventListenerInterface;
use PHPUnit\Framework\TestCase;

class StreamEventDispatcherTest extends TestCase
{
    public function testDispatcherDispatchesToListenersAndCallbacks()
    {
        $dispatcher = new StreamEventDispatcher();
        $listener = new TestStreamListener();
        $captured = array();

        $dispatcher->addListener($listener);
        $dispatcher->addCallback(function (StreamEvent $event) use (&$captured) {
            $captured[] = $event->type();
        });

        $event = new StreamEvent(StreamEvent::TEXT_DELTA, array('delta' => 'Hi'));
        $dispatcher->dispatch($event);

        $this->assertCount(1, $listener->events);
        $this->assertSame(StreamEvent::TEXT_DELTA, $listener->events[0]->type());
        $this->assertSame(array(StreamEvent::TEXT_DELTA), $captured);
    }

    public function testOutputBufferListenerAccumulatesText()
    {
        $listener = new OutputBufferListener();
        $listener->handle(new StreamEvent(StreamEvent::TEXT_DELTA, array('delta' => 'Hello')));
        $listener->handle(new StreamEvent(StreamEvent::TEXT_DELTA, array('delta' => ' world')));

        $this->assertSame('Hello world', $listener->buffer());

        $listener->handle(new StreamEvent(StreamEvent::TEXT_COMPLETED, array('text' => 'Hello world!')));
        $this->assertSame('Hello world!', $listener->text());
    }

    public function testCompletionStatusListenerTracksCompletedFailedAndError()
    {
        $listener = new CompletionStatusListener();

        $listener->handle(new StreamEvent(StreamEvent::TURN_COMPLETED));
        $this->assertTrue($listener->completed());

        $listener->handle(new StreamEvent(StreamEvent::TURN_FAILED));
        $this->assertTrue($listener->failed());

        $listener->handle(new StreamEvent(StreamEvent::TURN_ERROR));
        $this->assertTrue($listener->errored());
    }

    public function testFunctionCallListenerTracksToolLifecycle()
    {
        $listener = new FunctionCallListener();

        $listener->handle(new StreamEvent(StreamEvent::TOOL_EXECUTION_STARTED, array(
            'tool_call_id' => 'toolcall-1',
            'tool_name' => 'get_current_temperature',
            'arguments' => array('location' => 'Atlanta, GA'),
            'arguments_json' => '{"location":"Atlanta, GA"}',
        )));

        $listener->handle(new StreamEvent(StreamEvent::TOOL_EXECUTION_COMPLETED, array(
            'tool_call_id' => 'toolcall-1',
            'tool_name' => 'get_current_temperature',
            'output' => '{"temperature":69}',
            'success' => true,
            'metadata' => array('source' => 'test'),
        )));

        $calls = $listener->calls();
        $this->assertArrayHasKey('toolcall-1', $calls);
        $this->assertTrue($calls['toolcall-1']['started']);
        $this->assertTrue($calls['toolcall-1']['completed']);
        $this->assertFalse($calls['toolcall-1']['failed']);
        $this->assertSame('completed', $calls['toolcall-1']['status']);
        $this->assertSame('get_current_temperature', $calls['toolcall-1']['tool_name']);
    }
}

class TestStreamListener implements StreamEventListenerInterface
{
    public $events = array();

    public function handle(StreamEvent $event)
    {
        $this->events[] = $event;
    }
}
