<?php

namespace App\Agent\Tools\Campaign;

use App\Agent\Tools\Campaign\Concerns\ActsOnFocusedCampaign;
use App\Agent\Workspace;
use App\Campaigns\Publisher;
use App\Campaigns\PublishGate;
use App\Support\Actor;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
use Throwable;

/**
 * Publish the campaign in focus, from the chat.
 *
 * The only tool that can spend money, and the only one that always pauses for a
 * human. InteractsWithApprovals stops the run before this executes: the user is
 * shown the tool and its arguments and must approve, and a rejection comes back
 * to the model as an ordinary result it can talk about.
 *
 * The approval is a second lock, not the only one. PublishGate still decides
 * whether the campaign may go at all, and it is re-checked inside the publisher
 * after approval, because a campaign can change between the two.
 */
class PublishCampaign implements Approvable, Tool
{
    // The trait supplies the approval methods; the interface is what the run
    // loop actually tests for. With the trait alone the tool looks gated and is
    // executed straight through, which is how publishing ran with no pause.
    use ActsOnFocusedCampaign, InteractsWithApprovals;

    public function __construct(
        private readonly Workspace $workspace,
        private readonly PublishGate $gate,
        private readonly Publisher $publisher,
        private readonly Actor $actor,
    ) {}

    public function description(): Stringable|string
    {
        return 'Publish the campaign in focus to the advertising platform. Everything is created '
            .'paused, so nothing spends until the user switches it on in the platform. This always '
            .'asks the user to approve first. Only call it when they have clearly asked to publish.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [];
    }

    /** Always. Publishing is never done on the model's say-so alone. */
    protected function needsApproval(Request $request): bool
    {
        return true;
    }

    public function handle(Request $request): Stringable|string
    {
        if (! $campaign = $this->focused()) {
            return $this->noCampaign();
        }

        if ($blocking = $this->gate->check($campaign)) {
            return $this->json([
                'ok' => false,
                'published' => false,
                'still_needed' => $blocking,
            ]);
        }

        try {
            $decision = $this->publisher->approve(
                $this->publisher->propose($campaign, $this->actor->describe()),
                $this->actor->describe(),
            );
        } catch (Throwable $e) {
            // The publisher already rolled back whatever it had created. Report
            // the failure as a result so the model can explain it rather than
            // the turn dying.
            return $this->json([
                'ok' => false,
                'published' => false,
                'error' => $e->getMessage(),
                'note' => 'Anything already created was removed. The campaign was not published.',
            ]);
        }

        if (! $decision->wasApplied()) {
            return $this->json([
                'ok' => false,
                'published' => false,
                'error' => $decision->reason,
            ]);
        }

        return $this->json([
            'ok' => true,
            'published' => true,
            'created' => $decision->result,
            'note' => 'Created paused. Tell the user nothing spends until they switch it on.',
        ]);
    }
}
