<?php

namespace App\Models;

use App\Assets\Thumbnails;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Facades\Storage;

/**
 * Raw bytes: an upload, a generated image, something crawled.
 *
 * Deliberately not a creative. An upload only becomes advertising once it is
 * paired with copy, and the same asset can back several ads across several
 * campaigns.
 */
#[Fillable(['owner_type', 'owner_id', 'kind', 'source', 'disk', 'path', 'original_name', 'mime', 'bytes', 'checksum'])]
class Asset extends Model
{
    public function owner(): MorphTo
    {
        return $this->morphTo();
    }

    public function platformRefs(): HasMany
    {
        return $this->hasMany(AssetPlatformRef::class);
    }

    /** Whether this asset is already on the given platform account. */
    public function refFor(string $platform, string $adAccountId): ?AssetPlatformRef
    {
        return $this->platformRefs()
            ->where('platform', $platform)
            ->where('ad_account_id', $adAccountId)
            ->first();
    }

    /**
     * Whether there is a still image to show for this asset.
     *
     * An image is its own preview. A video has one only once it has been
     * uploaded, because the still comes from the platform's own transcode
     * rather than from anything we can produce here.
     *
     * A database check rather than a call to {@see Thumbnails}:
     * this decides whether to render an <img> at all, and it is asked once per
     * ad on every panel render, so it must not reach the network.
     */
    public function hasStill(): bool
    {
        if ($this->kind === 'image') {
            return true;
        }

        return $this->kind === 'video'
            && $this->platformRefs()
                ->where('platform', 'meta')
                ->whereNotNull('external_id')
                ->exists();
    }

    /**
     * The width and height of this asset in pixels, if it is an image and can be read.
     *
     * @return array{0: int, 1: int}|null
     */
    public function dimensions(): ?array
    {
        if ($this->kind !== 'image' || blank($this->path)) {
            return null;
        }

        $disk = Storage::disk($this->disk ?: 'local');
        if (! $disk->exists($this->path)) {
            return null;
        }

        $contents = $disk->get($this->path);
        if ($contents === null || $contents === '') {
            return null;
        }

        $size = @getimagesizefromstring($contents);
        if (! is_array($size) || $size[0] <= 0 || $size[1] <= 0) {
            return null;
        }

        return [$size[0], $size[1]];
    }

    /**
     * Standard marketing aspect ratio for this image asset, or null if unknown/video.
     */
    public function aspectRatio(): ?string
    {
        $dimensions = $this->dimensions();
        if ($dimensions === null) {
            return null;
        }

        [$width, $height] = $dimensions;
        $ratio = $width / $height;

        return match (true) {
            abs($ratio - 1.0) < 0.05 => '1:1',
            abs($ratio - (9 / 16)) < 0.05 => '9:16',
            abs($ratio - (4 / 5)) < 0.05 => '4:5',
            abs($ratio - (4 / 3)) < 0.05 => '4:3',
            abs($ratio - (16 / 9)) < 0.05 => '16:9',
            abs($ratio - 1.91) < 0.06 => '1.91:1',
            default => sprintf('%.2f:1', $ratio),
        };
    }
}
