Source path: app/Services/Publishers/FacebookPublisher.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 FacebookPublisher 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();
        $token = $acc->access_token; // Page access token

        if ($media && $media->type === 'image') {
            $res = Http::post("{$this->base()}/{$acc->external_id}/photos", [
                'url' => $media->url(), 'caption' => $post->caption, 'access_token' => $token,
            ]);
        } elseif ($media && $media->type === 'video') {
            $res = Http::timeout(120)->post("{$this->base()}/{$acc->external_id}/videos", [
                'file_url' => $media->url(), 'description' => $post->caption, 'access_token' => $token,
            ]);
        } else {
            $res = Http::post("{$this->base()}/{$acc->external_id}/feed", [
                'message' => $post->caption, 'access_token' => $token,
            ]);
        }

        $res->throw();
        $id = $res->json('post_id') ?? $res->json('id');

        return ['id' => $id, 'url' => "https://facebook.com/{$id}"];
    }

    public function refreshToken(SocialAccount $account): void
    {
        // Page tokens obtained from a long-lived user token don't expire.
        // Re-run the OAuth connect flow if Meta invalidates it.
    }
}
