Source path: app/Services/Publishers/InstagramPublisher.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Services\Publishers;

use App\Models\PostTarget;
use App\Models\SocialAccount;
use Illuminate\Support\Facades\Http;

class InstagramPublisher implements Publisher
{
    private function base(): string
    {
        return 'https://graph.facebook.com/' . config('services.meta.version');
    }

    public function publish(PostTarget $target): array
    {
        $acc   = $target->socialAccount;
        $post  = $target->post;
        $media = $post->media()->where('status', 'ready')->first();
        abort_if(!$media, 422, 'Instagram requires an image or video.');

        $params = ['caption' => $post->caption, 'access_token' => $acc->access_token];
        if ($media->type === 'video') {
            $params += ['media_type' => 'REELS', 'video_url' => $media->url()];
        } else {
            $params += ['image_url' => $media->url()];
        }

        $container = Http::post("{$this->base()}/{$acc->external_id}/media", $params)->throw()->json('id');

        // Poll until Instagram finishes processing (videos can take a while)
        for ($i = 0; $i < 30; $i++) {
            $status = Http::get("{$this->base()}/{$container}", [
                'fields' => 'status_code', 'access_token' => $acc->access_token,
            ])->json('status_code');

            if ($status === 'FINISHED') break;
            if ($status === 'ERROR') throw new \RuntimeException('Instagram media processing failed.');
            sleep(5);
        }

        $id = Http::post("{$this->base()}/{$acc->external_id}/media_publish", [
            'creation_id' => $container, 'access_token' => $acc->access_token,
        ])->throw()->json('id');

        $permalink = Http::get("{$this->base()}/{$id}", [
            'fields' => 'permalink', 'access_token' => $acc->access_token,
        ])->json('permalink');

        return ['id' => $id, 'url' => $permalink];
    }

    public function refreshToken(SocialAccount $account): void {}
}
