<?php

namespace App\Campaigns\Platforms;

use App\Models\Campaign;

/**
 * A platform we can hold a campaign for but cannot publish to yet.
 *
 * Everything shared still applies, so a campaign for it is validated normally
 * and refused at the last step rather than half built. Naming the platform in
 * the refusal matters: "TikTok is not connected yet" is actionable, a generic
 * failure is not.
 */
abstract class UnsupportedPlatformRules implements PlatformRules
{
    /**
     * The steps any ad platform has, for one we cannot publish to yet.
     *
     * Deliberately generic. A campaign for an unconnected platform is built and
     * validated like any other and refused at the last step, so it needs enough
     * steps to be a real campaign, without claiming features we have not
     * implemented for it.
     */
    public function steps(): array
    {
        return [
            ['key' => 'ad_account', 'label' => 'ad account', 'tool' => 'list_ad_accounts', 'offers' => true,
                'ask' => 'Which ad account should this campaign run on?'],
            ['key' => 'objective', 'label' => 'objective', 'tool' => 'choose_objective', 'offers' => true,
                'ask' => 'What should this campaign optimise for?'],
            ['key' => 'bidding', 'label' => 'bid strategy', 'tool' => 'set_bidding', 'offers' => true,
                'ask' => 'How should it bid?'],
            ['key' => 'targeting', 'label' => 'countries and placements', 'tool' => 'set_targeting', 'offers' => true,
                'ask' => 'Where should this run, and on which devices?'],
            // No utm step. Click tracking is set from the platform's own
            // macros when the campaign is created and the buyer is told,
            // rather than asked to choose the value that was going to be
            // used anyway. set_tracking still changes it on request.
        ];
    }

    public function objectives(): array
    {
        return ['OUTCOME_SALES', 'OUTCOME_LEADS', 'OUTCOME_TRAFFIC'];
    }

    /** @return list<string> */
    public function campaignTypes(): array
    {
        return [];
    }

    /** @return array{label: string, hint: string} */
    public function describeCampaignType(string $type): array
    {
        return ['label' => $this->humanise($type), 'hint' => ''];
    }

    public function suggestedCampaignType(?Campaign $campaign = null): ?array
    {
        return null;
    }

    public function bidStrategyFor(string $intent, ?string $objective = null): ?string
    {
        return null;
    }

    public function bidStrategies(): array
    {
        return ['LOWEST_COST_WITHOUT_CAP', 'COST_CAP'];
    }

    /**
     * Built from whatever the platform declared, so an override of
     * bidStrategies() alone still produces usable buttons.
     *
     * @return list<array{value: string, label: string, hint: string, sends: string}>
     */
    public function bidStrategyOptions(): array
    {
        return array_map(fn (string $strategy): array => [
            'value' => $strategy,
            'label' => $this->humanise($strategy),
            'hint' => '',
            'sends' => 'Use '.$this->humanise($strategy),
        ], $this->bidStrategies());
    }

    public function bidStrategyNeedsTarget(string $strategy): bool
    {
        return $strategy !== 'LOWEST_COST_WITHOUT_CAP';
    }

    public function objectiveFor(string $outcome): ?string
    {
        $objective = 'OUTCOME_'.strtoupper($outcome);

        return in_array($objective, $this->objectives(), true) ? $objective : null;
    }

    /** @return array{label: string, hint: string} */
    public function describeObjective(string $objective): array
    {
        return ['label' => $this->humanise($objective), 'hint' => ''];
    }

    /** OUTCOME_SALES reads as "Sales"; MAXIMIZE_CONVERSIONS as "Maximize conversions". */
    protected function humanise(string $value): string
    {
        return ucfirst(strtolower(str_replace(['OUTCOME_', '_'], ['', ' '], $value)));
    }

    public function placements(): array
    {
        return [];
    }

    public function callsToAction(): array
    {
        return ['SHOP_NOW', 'LEARN_MORE', 'SIGN_UP'];
    }

    /** @return list<string> */
    public function specialAdCategories(): array
    {
        return [];
    }

    /** Nothing is connected, so there is no account to default to. */
    public function defaultAdAccountId(): ?string
    {
        return null;
    }

    /**
     * Plain UTM parameters, with no macros at all.
     *
     * Every platform substitutes different ones and none of these can be
     * published to, so naming the platform is all that can be said truthfully.
     * A macro guessed from another platform's syntax would reach the landing
     * page as literal braces.
     *
     * @return list<array{value: string, label: string, hint: string, sends: string, badge?: string|null}>
     */
    public function trackingOptions(): array
    {
        return [
            [
                'value' => 'utm_source='.$this->key().'&utm_medium=cpc',
                'label' => 'Standard tagging',
                'hint' => 'Source and medium only, because '.$this->name().' is not connected yet.',
                'badge' => 'Suggested',
                'sends' => 'Use the standard UTM tagging',
            ],
        ];
    }

    /**
     * The one mode every platform here certainly has.
     *
     * Unlike minimumDailyBudget() below, an incomplete list is not a wrong
     * answer: TikTok, Taboola and LinkedIn all take a daily campaign budget,
     * and none of them can be published to from here, so nothing is lost by
     * not offering lifetime amounts whose exact rules nobody has checked.
     * Claiming them would be the guess.
     *
     * Whichever of these becomes publishable first should override this with
     * what that platform actually accepts.
     *
     * @return list<array{value: string, label: string, hint: string, sends: string}>
     */
    public function budgetModes(): array
    {
        return [
            [
                'value' => 'campaign:daily',
                'label' => 'Daily',
                'hint' => 'Spend this much every day until it is stopped.',
                'sends' => 'Use a daily campaign budget',
            ],
        ];
    }

    // minimumDailyBudget() is deliberately not defined here.
    //
    // It used to return 5.00, which is Meta's floor, inherited by every
    // platform that had not thought about it. That is how Google came to tell
    // buyers "Google Ads needs at least 5.00 a day" about a platform that
    // enforces no minimum at all. A shared default for a number that differs
    // per platform is not a safe fallback, it is a wrong answer given
    // confidently, and the interface already requires every platform to supply
    // one. Leaving it out means a new platform cannot forget.

    /**
     * Nothing known, so nothing enforced.
     *
     * A platform that cannot be published to anyway gets no copy limits rather
     * than borrowed ones, which would be a guess presented as a rule.
     *
     * @return array<string, int>
     */
    public function copyLimits(): array
    {
        return [];
    }

    /** @return list<string> */
    public function placementsFor(string $objective): array
    {
        return $this->placements();
    }

    public function optimizationGoal(string $objective): ?string
    {
        return null;
    }

    public function conversionEvent(string $objective): ?string
    {
        return null;
    }

    public function requiresPixel(string $objective): bool
    {
        return false;
    }

    /**
     * Nothing is demanded of a platform that has not declared it needs one.
     *
     * A platform that does publish from a page says so by overriding this, the
     * same way it declares its objectives and placements.
     */
    public function requiresPage(): bool
    {
        return false;
    }

    public function requiresCreativeAsset(?Campaign $campaign = null): bool
    {
        return false;
    }

    /** @return list<string> */
    public function additionalProblems(Campaign $campaign): array
    {
        return [$this->name().' is not connected yet, so this campaign cannot be published'];
    }

    /** @return list<string> */
    public function isConfigured(): bool
    {
        // Nothing to configure: there is no client for this platform at all.
        return false;
    }

    public function editableAfterPublish(): array
    {
        return [];
    }

    public function supportedAssetRatios(): array
    {
        return ['1:1', '1.91:1', '9:16'];
    }
}
