<?php

use App\Campaigns\Platforms\GoogleRules;
use App\Campaigns\Platforms\LinkedInRules;
use App\Campaigns\Platforms\MetaRules;
use App\Campaigns\Platforms\TaboolaRules;
use App\Campaigns\Platforms\TikTokRules;
use App\Campaigns\Publishing\GooglePublisher;
use App\Campaigns\Publishing\LinkedInPublisher;
use App\Campaigns\Publishing\MetaPublisher;

return [

    /*
    |--------------------------------------------------------------------------
    | Platform Rules
    |--------------------------------------------------------------------------
    |
    | What each platform accepts: objectives, bid strategies, placements,
    | minimum spend, and which fields can still be edited once a campaign is
    | live. Adding a platform is a class here plus an adapter below; nothing in
    | the campaign builder needs to know the list.
    |
    */

    'rules' => [
        'meta' => MetaRules::class,
        'google' => GoogleRules::class,
        'tiktok' => TikTokRules::class,
        'taboola' => TaboolaRules::class,
        'linkedin' => LinkedInRules::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Platform Publishers
    |--------------------------------------------------------------------------
    |
    | Which platforms a campaign can actually be created on. Deliberately a
    | shorter list than the rules above: holding a campaign for a platform and
    | being able to build one there are different questions, and answering them
    | with one list is how a campaign for a platform we cannot publish to would
    | be sent to the one we can.
    |
    | A platform absent from here is refused by name at the publish gate rather
    | than failing somewhere inside an API client.
    |
    */

    'publishers' => [
        'meta' => MetaPublisher::class,
        'google' => GooglePublisher::class,
        'linkedin' => LinkedInPublisher::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Guardrails
    |--------------------------------------------------------------------------
    |
    | Checked inside the API client, below every platform adapter and below the
    | agent, so there is no path around them and a new platform inherits them by
    | existing. Neither a language model nor a user typing in the chat box can
    | get past these.
    |
    */

    'guardrails' => [

        /** Only these accounts may be reached at all. Reads included. */
        'allowed_ad_accounts' => array_values(array_filter(array_map(
            trim(...),
            explode(',', (string) env('PLATFORM_ALLOWED_AD_ACCOUNTS', '')),
        ))),

        /**
         * Ceiling on a single campaign's daily budget, in whole currency units.
         *
         * The currency it is expressed in. A bare number meant nothing: the
         * ceiling was compared against a budget in whatever the ad account
         * bills in, so 20 stopped a 20 dollar campaign and also a 20 rupee one,
         * and on a yen account it blocked every campaign worth running.
         */
        'budget_ceiling_currency' => env('PLATFORM_BUDGET_CURRENCY', 'USD'),

        // The most this product will let one campaign spend in a day.
        //
        // Was 20, which refused amounts real buyers actually wanted: Mohsin
        // asked for 50 and Koushik hit it repeatedly, and neither was doing
        // anything unreasonable. 20 is a development guard, not a product
        // limit.
        'max_daily_budget' => (float) env('PLATFORM_MAX_DAILY_BUDGET', 500),

        // Above this, the amount is read back and confirmed before it is set.
        //
        // A ceiling alone is a cliff: everything under it is silent and the
        // first thing over it is refused. A budget is the one field where a
        // mistyped zero spends someone's money, so the middle ground is to
        // repeat it back.
        'confirm_daily_budget_above' => (float) env('PLATFORM_CONFIRM_DAILY_BUDGET_ABOVE', 50),

        /**
         * Ceilings for accounts billing in something else.
         *
         * Deliberately a list rather than a conversion: an exchange rate that
         * moves would quietly move a spending limit with it. An account whose
         * currency is not named here cannot be published to, which fails closed
         * rather than falling back to a number that means nothing.
         *
         * PLATFORM_MAX_DAILY_BUDGET_BY_CURRENCY="INR:1700,GBP:16,EUR:18"
         */
        'max_daily_budget_by_currency' => collect(explode(',', (string) env('PLATFORM_MAX_DAILY_BUDGET_BY_CURRENCY', '')))
            ->filter()
            ->mapWithKeys(function (string $pair): array {
                [$currency, $amount] = array_pad(explode(':', trim($pair), 2), 2, null);

                return [strtoupper(trim((string) $currency)) => (float) $amount];
            })
            ->filter()
            ->all(),

        /** Most ads one publish may create. */
        'max_ads_per_publish' => (int) env('PLATFORM_MAX_ADS_PER_PUBLISH', 5),

        /**
         * Our share of the platform's hourly limit, so anything else using the
         * same token keeps its own headroom.
         */
        'max_calls_per_hour' => (int) env('PLATFORM_MAX_CALLS_PER_HOUR', 60),

        /**
         * How long to wait before the same campaign may be changed again on the
         * platform. Stops a disagreeing user and agent, or a retry loop, from
         * rewriting a live campaign repeatedly.
         */
        'change_cooldown_seconds' => (int) env('PLATFORM_CHANGE_COOLDOWN', 60),

        /** Most changes one campaign may have pushed to a platform in a day. */
        'max_changes_per_day' => (int) env('PLATFORM_MAX_CHANGES_PER_DAY', 20),

        /**
         * Pause between attempts when a platform refuses a call as throttled,
         * growing with each attempt. Zero disables the wait, which is what the
         * test suite wants.
         */
        'retry_backoff_ms' => (int) env('PLATFORM_RETRY_BACKOFF_MS', 500),

        /**
         * Halts everything that writes to a platform, without a deploy. Reads
         * keep working so the product can still explain itself.
         */
        'paused' => (bool) env('PLATFORM_PAUSE_ALL_WRITES', false),
    ],

    /*
    |--------------------------------------------------------------------------
    | Tracking
    |--------------------------------------------------------------------------
    |
    | What gets appended to a destination URL so a click can be attributed once
    | it lands. There were none at all: every ad published so far sent traffic
    | to a bare URL, and nothing downstream could tell it came from Meta.
    |
    | The braced names are Meta's own macros, filled in by Meta at click time,
    | so one string works for every campaign rather than being rewritten per
    | campaign. A campaign can still hold its own.
    |
    | Settled before publish deliberately. Changing a URL afterwards means a new
    | creative and a new ad, because Meta treats a creative as immutable.
    |
    */

    'tracking' => [
        // Ids, not names, and Meta's own spelling of itself.
        //
        // This tagged utm_campaign with {{campaign.name}}, so every click
        // arrived labelled with a name that changes whenever the campaign is
        // renamed and collides whenever two campaigns share one. Ids are stable
        // and joinable, which is the whole point of tagging a click.
        //
        // utm_source=meta rather than facebook, because the campaign may run on
        // Instagram and Threads too, and utm_medium=cpc rather than
        // paid_social, which is what the rest of the reporting expects.
        'default_utm' => env(
            'PLATFORM_DEFAULT_UTM',
            'utm_source=meta&utm_medium=cpc&utm_campaign={{campaign.id}}&utm_content={{adset.id}}&utm_term={{ad.id}}',
        ),
    ],

    /*
    |--------------------------------------------------------------------------
    | Meta
    |--------------------------------------------------------------------------
    |
    | Two tokens, on purpose. The system user token is long lived and is what
    | Graph calls use. The MCP requires a user token carrying the
    | ads_mcp_management scope, which expires and has to be renewed.
    |
    */

    'meta' => [
        'token' => env('META_ACCESS_TOKEN'),
        'version' => env('META_API_VERSION', 'v23.0'),

        /**
         * How long to wait for Meta to finish transcoding an uploaded video.
         *
         * Meta accepts the upload straight away and processes afterwards, and an
         * ad built against a video that is not ready is refused. Ten attempts
         * three seconds apart covers the short creatives this product makes;
         * zero wait is what the test suite wants.
         */
        'video_ready_attempts' => (int) env('META_VIDEO_READY_ATTEMPTS', 10),
        'video_ready_wait_ms' => (int) env('META_VIDEO_READY_WAIT_MS', 3000),

        'mcp' => [
            'enabled' => (bool) env('META_USE_MCP', true),
            'token' => env('META_MCP_TOKEN'),
            'endpoint' => env('META_MCP_ENDPOINT', 'https://mcp.facebook.com/ads'),
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | LinkedIn
    |--------------------------------------------------------------------------
    */

    'linkedin' => [
        'client_id' => env('LINKEDIN_CLIENT_ID'),
        'client_secret' => env('LINKEDIN_CLIENT_SECRET'),
        'token' => env('LINKEDIN_ACCESS_TOKEN'),
        'refresh_token' => env('LINKEDIN_REFRESH_TOKEN'),
        'version' => env('LINKEDIN_API_VERSION', '202609'),
        'organization_id' => env('LINKEDIN_ORGANIZATION_ID'),
        'ad_account_id' => env('LINKEDIN_AD_ACCOUNT_ID'),

        'mcp' => [
            // Off unless somebody turns it on. Defaulting to true meant an
            // environment with no LinkedIn credentials at all still badged
            // LinkedIn "Connected" in the platform chooser, so the one thing
            // the badge is for was the one thing it did not say.
            'enabled' => (bool) env('LINKEDIN_USE_MCP', false),
            'token' => env('LINKEDIN_MCP_TOKEN'),
            'endpoint' => env('LINKEDIN_MCP_ENDPOINT', '/mcp/linkedin'),
        ],
    ],

];
