<?php

namespace App\Campaigns\Publishing;

use App\Campaigns\Publisher;
use App\Models\Campaign;

/**
 * How one platform turns a campaign we hold into a campaign it holds.
 *
 * {@see Publisher} owns everything that is true whatever the
 * platform: the recorded decision, the human approval, the re-check against the
 * gate, the refusal to publish twice, the status transitions and the rollback
 * on failure. None of that is allowed to live in here, because the moment it
 * does a second platform inherits none of it.
 *
 * What is left is genuinely platform work: the shape of the payload, the order
 * the objects have to be created in, and how to remove them again.
 *
 * Everything created must be created paused. That is not this interface's
 * choice to make: nothing this product builds is allowed to start spending on
 * creation, and a platform that cannot create paused cannot be published to.
 */
interface PlatformPublisher
{
    /**
     * Create the campaign and everything under it, paused.
     *
     * The returned array is both the audit record and the rollback instruction,
     * so it must be filled in as objects are created rather than at the end:
     * a failure on the third call still has to know about the first two.
     *
     * `campaign_id` is the one key the caller reads by name, because it is what
     * marks the campaign as published and what stops it being published again.
     *
     * @param  array<string,mixed>  $created  filled in as work proceeds
     * @return array<string,mixed> ids of everything created, including campaign_id
     */
    public function create(Campaign $campaign, string $actor, array &$created): array;

    /**
     * Undo whatever create() got through, deepest first.
     *
     * Called with a partial result after a failure. Must not throw: a rollback
     * that fails has to leave the original error as the thing the user is told
     * about, not replace it with a second one.
     *
     * @param  array<string,mixed>  $created
     */
    public function rollBack(Campaign $campaign, array $created, string $actor): void;

    /**
     * Send approved changes to the live campaign.
     *
     * CampaignUpdater used to do this itself, against Meta, for every platform:
     * it read the diff, grouped the fields by Meta's entity types and called the
     * Graph API. A Google campaign reaching it would have sent Google ids and a
     * Google customer id to Meta, and then recorded the change as applied,
     * because the snapshot moves on success and nothing checked which platform
     * had been written to.
     *
     * Which fields can arrive here is decided by the platform's own
     * editableAfterPublish(), so an implementation must handle everything its
     * rules claim: a field declared editable and silently not sent is worse than
     * one refused outright, because the campaign then disagrees with the
     * platform and our snapshot says it does not.
     *
     * @param  array<string, array{from: mixed, to: mixed}>  $changes  field to before and after
     */
    public function update(Campaign $campaign, array $changes, string $actor): void;
}
