<?php

namespace App\Agent;

use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Contracts\RemembersConversations as RemembersConversationsContract;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Promptable;
use Stringable;

/**
 * The one agent the product talks to.
 *
 * It holds no domain knowledge. Tools come from the registry, which each module
 * fills from its own service provider, and state comes from the workspace. Add
 * a capability by registering a tool, never by editing this class.
 *
 * laravel/ai owns the transcript and the tool-calling loop. Two behaviours here
 * are load-bearing:
 *
 *   - instructions() is rebuilt per generation, so the model is re-shown current
 *     state every turn and cannot drift from what is stored.
 *   - binding the workspace to the conversation id is what lets a tool know
 *     which campaign "set the budget to $30" refers to.
 */
class MarketingAgent implements Agent, HasTools, RemembersConversationsContract
{
    use Promptable, RemembersConversations;

    public function __construct(
        private readonly ToolRegistry $registry,
        private readonly Workspace $workspace,
        private readonly Instructions $instructions,
    ) {
        // The conversation does not exist when instructions are first built,
        // but it does by the time a tool runs. Handing the workspace a resolver
        // rather than an id closes that window: without it, anything created on
        // the very first turn is written against no conversation and orphaned.
        $this->workspace->resolveIdUsing(fn (): ?string => $this->currentConversation());
    }

    public function instructions(): Stringable|string
    {
        $this->syncWorkspace();

        return $this->instructions->build();
    }

    /** @return list<Tool> */
    public function tools(): iterable
    {
        $this->syncWorkspace();

        return $this->registry->resolve();
    }

    public function workspace(): Workspace
    {
        return $this->workspace;
    }

    /**
     * Keep the workspace pointed at whichever conversation is running.
     *
     * Continuing an older conversation replaces the id outright; the resolver
     * only fills a blank one, so a rebind still has to be explicit.
     */
    private function syncWorkspace(): void
    {
        $conversationId = $this->currentConversation();

        if ($conversationId && $conversationId !== $this->workspace->id()) {
            $this->workspace->bindTo($conversationId);
        }
    }
}
