<?php

namespace App\Services\Meta;

/**
 * The one thing the rest of the application talks to.
 *
 * Chooses a transport per operation: the Ads MCP for creation when it is
 * enabled, the Graph API for uploads and deletion, which the MCP cannot do.
 * Callers never need to know which ran; the audit log records it either way.
 */
class Meta
{
    public function __construct(
        private readonly GraphApi $graph,
        private readonly ?AdsMcp $mcp,
        private readonly CallLogger $log,
    ) {}

    /** Attribute everything that follows to a draft, for the audit trail. */
    public function on(?int $campaignId, ?string $actor): static
    {
        $this->log->attributeTo($campaignId, $actor);

        return $this;
    }

    // ------------------------------------------------------------------ read

    public function adAccounts(): array
    {
        return $this->graph->adAccounts();
    }

    public function adAccount(string $adAccountId): array
    {
        return $this->graph->adAccount($adAccountId);
    }

    public function pages(): array
    {
        return $this->graph->pages();
    }

    public function pixels(string $adAccountId): array
    {
        return $this->graph->pixels($adAccountId);
    }

    public function uploadImage(string $adAccountId, string $path, string $filename): string
    {
        return $this->graph->uploadImage($adAccountId, $path, $filename);
    }

    public function uploadVideo(string $adAccountId, string $path, string $filename): string
    {
        return $this->graph->uploadVideo($adAccountId, $path, $filename);
    }

    // ---------------------------------------------------------------- create

    /**
     * Ask Meta to check a campaign payload without creating anything.
     *
     * Only the Graph API offers this, so it runs there even when the MCP is
     * handling creation. Cheap, and it turns "half a campaign then a rollback"
     * into a clean refusal when Meta changes its requirements.
     */
    public function validateCampaign(string $adAccountId, string $name, string $objective): void
    {
        $this->graph->createCampaign($adAccountId, $name, $objective, dryRun: true);
    }

    public function createCampaign(string $adAccountId, string $name, string $objective): string
    {
        return ObjectId::require(
            $this->mcp?->createCampaign($adAccountId, $name, $objective)
                ?? $this->graph->createCampaign($adAccountId, $name, $objective),
            'campaign',
        );
    }

    public function createAdSet(AdSetSpec $spec): string
    {
        return ObjectId::require(
            $this->mcp?->createAdSet($spec) ?? $this->graph->createAdSet($spec),
            'ad set',
        );
    }

    public function createCreative(CreativeSpec $spec): string
    {
        return ObjectId::require(
            $this->mcp?->createCreative($spec) ?? $this->graph->createCreative($spec),
            'ad creative',
        );
    }

    public function createAd(string $adAccountId, string $adSetId, string $creativeId, string $name): string
    {
        return ObjectId::require(
            $this->mcp?->createAd($adAccountId, $adSetId, $creativeId, $name)
                ?? $this->graph->createAd($adAccountId, $adSetId, $creativeId, $name),
            'ad',
        );
    }

    /**
     * Change a live object.
     *
     * @param  array<string,mixed>  $fields  platform field names, not create arguments
     */
    public function updateEntity(string $adAccountId, string $entityId, string $entityType, array $fields): void
    {
        $this->mcp?->updateEntity($adAccountId, $entityId, $entityType, $fields)
            ?? $this->graph->updateEntity($entityId, $fields, $adAccountId);
    }

    /** Graph only: the MCP has no delete tool for campaigns, ad sets or ads. */
    public function delete(string $objectId): bool
    {
        return $this->graph->delete($objectId);
    }

    public function usingMcp(): bool
    {
        return $this->mcp !== null;
    }
}
