<?php

namespace App\Agent\Tools\Campaign;

use App\Agent\Tools\Campaign\Concerns\ActsOnFocusedCampaign;
use App\Agent\Workspace;
use App\Campaigns\Objectives;
use App\Models\ArtifactFieldSource;
use App\Models\Asset;
use App\Models\CampaignAd;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

/**
 * Write the ads for the campaign in focus.
 *
 * Replaces the existing set rather than appending, so re-running after an edit
 * does not quietly double the number of ads. Assets are paired round robin, so
 * three copy variations over one image still ship three ads that differ by copy.
 */
class WriteAdCopy implements Tool
{
    use ActsOnFocusedCampaign;

    public function __construct(private readonly Workspace $workspace) {}

    public function description(): Stringable|string
    {
        return 'Write one or more ad variations for the campaign in focus. Replaces any '
            .'existing ads. Write the copy yourself rather than asking the user for it.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'ads' => $schema->array()->items(
                $schema->object([
                    'headline' => $schema->string()->description('Short headline, under 40 characters.')->required(),
                    'primary_text' => $schema->string()->description('The main body copy.')->required(),
                    'description' => $schema->string()->description('Optional supporting line.'),
                    'cta' => $schema->string()->description('One of '.implode(', ', Objectives::CALLS_TO_ACTION).'.')->required(),
                ])
            )->description('The ad variations to write.')->required(),
        ];
    }

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

        $ads = (array) ($request->all()['ads'] ?? []);

        if ($ads === []) {
            return $this->json(['ok' => false, 'error' => 'At least one ad variation is required.']);
        }

        $limit = (int) config('platforms.guardrails.max_ads_per_publish');

        if (count($ads) > $limit) {
            return $this->json([
                'ok' => false,
                'error' => sprintf('%d variations given, but at most %d may be published at once.', count($ads), $limit),
            ]);
        }

        foreach ($ads as $index => $ad) {
            foreach (['headline', 'primary_text', 'cta'] as $field) {
                if (blank($ad[$field] ?? null)) {
                    return $this->json(['ok' => false, 'error' => 'Ad '.($index + 1)." is missing {$field}."]);
                }
            }

            if (! in_array(strtoupper((string) $ad['cta']), Objectives::CALLS_TO_ACTION, true)) {
                return $this->json([
                    'ok' => false,
                    'error' => "[{$ad['cta']}] is not a supported call to action.",
                    'supported' => Objectives::CALLS_TO_ACTION,
                ]);
            }
        }

        // Assets already attached to this conversation, paired round robin.
        $assets = $this->workspace->artifacts(Asset::class)
            ->map(fn ($link) => $link->artifactable)
            ->filter()
            ->values();

        // Remember the pairing before replacing the rows, so rewriting copy
        // does not silently drop creatives the user already chose.
        $previous = $campaign->ads()->orderBy('id')->pluck('asset_id')->all();

        $campaign->ads()->delete();
        $written = [];

        foreach ($ads as $index => $ad) {
            $written[] = CampaignAd::create([
                'campaign_id' => $campaign->id,
                'asset_id' => $previous[$index]
                    ?? ($assets->isNotEmpty() ? $assets[$index % $assets->count()]->id : null),
                'headline' => trim((string) $ad['headline']),
                'primary_text' => trim((string) $ad['primary_text']),
                'description' => filled($ad['description'] ?? null) ? trim((string) $ad['description']) : null,
                'cta' => strtoupper((string) $ad['cta']),
            ])->only(['id', 'headline', 'cta']);
        }

        $this->workspace->recordSource($campaign, 'ads', ArtifactFieldSource::MODEL_INFERRED);

        return $this->json([
            'ok' => true,
            'campaign_id' => $campaign->id,
            'ads' => $written,
            'assets_attached' => $assets->count(),
        ]);
    }
}
