<?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 Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

/** Who the campaign in focus reaches, and where. */
class SetTargeting implements Tool
{
    use ActsOnFocusedCampaign;

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

    public function description(): Stringable|string
    {
        return 'Set audience and placement on the campaign in focus: countries, age range, '
            .'genders, devices and platforms. Countries are two letter ISO codes.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'countries' => $schema->array()
                ->items($schema->string())
                ->description('Two letter ISO country codes, for example ["US"].'),
            'age_min' => $schema->integer()->description('Minimum age, 13 to 65.'),
            'age_max' => $schema->integer()->description('Maximum age, 13 to 65.'),
            'genders' => $schema->array()->items($schema->integer())
                ->description('1 for male, 2 for female. Omit to reach everyone.'),
            'devices' => $schema->array()->items($schema->string())
                ->description('Any of mobile, desktop. Omit for both.'),
            'placements' => $schema->array()->items($schema->string())
                ->description('Any of facebook, instagram, messenger, audience_network, threads.'),
        ];
    }

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

        $arguments = $request->all();

        $countries = array_values(array_filter(array_map(
            fn ($c) => strtoupper(trim((string) $c)),
            (array) ($arguments['countries'] ?? $campaign->locations['countries'] ?? []),
        )));

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

        $devices = $this->limitTo($arguments['devices'] ?? null, Objectives::DEVICES);
        $placements = $this->limitTo($arguments['placements'] ?? null, Objectives::PLACEMENTS);

        if (($arguments['devices'] ?? null) && $devices === []) {
            return $this->json(['ok' => false, 'error' => 'Devices must be mobile and/or desktop.']);
        }

        if (($arguments['placements'] ?? null) && $placements === []) {
            return $this->json([
                'ok' => false,
                'error' => 'Unrecognised placements.',
                'supported' => Objectives::PLACEMENTS,
            ]);
        }

        $audience = array_filter([
            'age_min' => isset($arguments['age_min']) ? (int) $arguments['age_min'] : ($campaign->audience['age_min'] ?? null),
            'age_max' => isset($arguments['age_max']) ? (int) $arguments['age_max'] : ($campaign->audience['age_max'] ?? null),
            'genders' => array_map('intval', (array) ($arguments['genders'] ?? $campaign->audience['genders'] ?? [])) ?: null,
            'devices' => $devices ?: ($campaign->audience['devices'] ?? null),
        ], fn ($v) => $v !== null && $v !== []);

        $campaign->update([
            'locations' => ['countries' => $countries],
            'audience' => $audience,
            'placements' => $placements ?: $campaign->placements,
        ]);

        foreach (['locations', 'audience', 'placements'] as $field) {
            $this->workspace->recordSource($campaign, $field, ArtifactFieldSource::USER_STATED);
        }

        return $this->json([
            'ok' => true,
            'campaign_id' => $campaign->id,
            'countries' => $countries,
            'audience' => $audience,
            'placements' => $campaign->placements,
        ]);
    }

    /** @return list<string> */
    private function limitTo(mixed $given, array $allowed): array
    {
        return array_values(array_intersect(
            array_map(fn ($v) => strtolower(trim((string) $v)), (array) $given),
            $allowed,
        ));
    }
}
