<?php

namespace App\Agent\Tools\Campaign;

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

/**
 * The button on the ad, chosen from what the platform actually offers.
 *
 * The call to action arrived as free text from the model alongside the copy, and
 * was checked against one hardcoded list of seven - Meta's - whatever the
 * campaign was running on. So LinkedIn would accept BOOK_TRAVEL, which it has no
 * button for, and refuse APPLY, REGISTER, REQUEST_DEMO and VIEW_QUOTE, which are
 * four of the ten it does. The failure lands at publish, as a platform error
 * about an enum, long after the copy was agreed.
 *
 * Each platform already answers this for itself through callsToAction(), so the
 * list offered is the campaign's own. Google answers with none, because a Search
 * ad has no button, and this says so rather than offering seven that cannot be
 * applied.
 *
 * Applied to every ad on the campaign. The button is a property of the offer
 * rather than of a variation: three ads that say Shop Now, Learn More and Sign
 * Up are not an A/B test anybody asked for, and the buyer who picks one means it
 * for the campaign.
 */
class ChooseCallToAction implements Tool
{
    use ActsOnFocusedCampaign;

    /** What each button says, where the enum does not read as English. */
    private const LABELS = [
        'SHOP_NOW' => 'Shop Now',
        'LEARN_MORE' => 'Learn More',
        'SIGN_UP' => 'Sign Up',
        'GET_OFFER' => 'Get Offer',
        'SUBSCRIBE' => 'Subscribe',
        'CONTACT_US' => 'Contact Us',
        'BOOK_TRAVEL' => 'Book Now',
        'APPLY' => 'Apply',
        'DOWNLOAD' => 'Download',
        'REGISTER' => 'Register',
        'JOIN' => 'Join',
        'ATTEND' => 'Attend',
        'REQUEST_DEMO' => 'Request Demo',
        'VIEW_QUOTE' => 'View Quote',
    ];

    public function __construct(
        private readonly Workspace $workspace,
        private readonly Platforms $platforms,
        private readonly Choices $choices,
    ) {}

    public function description(): Stringable|string
    {
        return 'Offer the calls to action this campaign\'s platform supports and set the one the ads '
            .'carry, by passing cta. Call this after the ad copy is written. Never invent a button: '
            .'the supported list differs by platform and an unsupported one fails at publish.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'cta' => $schema->string()
                ->description('The call to action, as the platform names it, such as LEARN_MORE or SHOP_NOW.'),
        ];
    }

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

        $supported = $this->platforms->for($campaign)->callsToAction();

        // Google's Search ads carry no button at all. Offering a list here would
        // be offering a setting the ad cannot have.
        if ($supported === []) {
            return $this->json([
                'ok' => false,
                'error' => sprintf('A %s ad has no call to action button, so there is nothing to choose.', $campaign->platform),
                'note' => 'Do not ask the user about this. Move on to the next step.',
            ]);
        }

        if (! $campaign->ads()->exists()) {
            return $this->json([
                'ok' => false,
                'error' => 'Write the ad copy first. The call to action is set on the ads.',
            ]);
        }

        $arguments = $request->all();

        if (! array_key_exists('cta', $arguments)) {
            return $this->offer($campaign, $supported);
        }

        $chosen = strtoupper(trim((string) $arguments['cta']));

        if ($chosen === '') {
            return $this->json(['ok' => false, 'error' => 'Name the call to action, or leave it unset to be offered the list.']);
        }

        if (! in_array($chosen, $supported, true)) {
            return $this->json([
                'ok' => false,
                'error' => sprintf('[%s] is not a call to action %s supports.', $chosen, $campaign->platform),
                'supported' => $supported,
            ]);
        }

        $campaign->ads()->update(['cta' => $chosen]);
        $this->workspace->recordSource($campaign, 'cta', ArtifactFieldSource::USER_STATED);

        return $this->json([
            'ok' => true,
            'cta' => $chosen,
            'label' => self::LABELS[$chosen] ?? $chosen,
            'ads_updated' => $campaign->ads()->count(),
        ]);
    }

    /**
     * @param  list<string>  $supported
     */
    private function offer(Campaign $campaign, array $supported): Stringable|string
    {
        // Whatever the ads already carry is marked, so the ordinary case stays
        // one click and the buyer can see what they would be changing.
        $current = (string) $campaign->ads()->value('cta');

        $this->choices->offer(
            field: 'cta',
            question: 'Which button should the ads carry?',
            options: array_map(fn (string $cta): array => array_filter([
                'value' => $cta,
                'label' => self::LABELS[$cta] ?? $cta,
                'badge' => $cta === $current ? 'Current' : null,
                'sends' => 'Use the '.(self::LABELS[$cta] ?? $cta).' button',
            ]), $supported),
        );

        return $this->json([
            'ok' => true,
            'offered' => $supported,
            'note' => 'The choice is on screen. Say one short line and wait; do not restate the options.',
        ]);
    }
}
