<?php

declare(strict_types=1);

namespace App\Services\NovaCards;

use App\Models\NovaCard;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use RuntimeException;
use Symfony\Component\Process\Process;

/**
 * Renders a Nova Card by loading the editor's React canvas preview inside a
 * headless Chromium (via Playwright) and screenshotting it.
 *
 * This produces a pixel-perfect image that matches what users see in the editor,
 * including web fonts, CSS container queries, and CSS gradients/blend modes.
 *
 * Prerequisites:
 *   - Node.js must be in PATH.
 *   - Playwright browsers must be installed (`npx playwright install chromium`).
 *   - NOVA_CARDS_PLAYWRIGHT_RENDER=true in .env (default: false).
 *   - APP_URL must be reachable from the queue worker (used for the signed URL).
 */
class NovaCardPlaywrightRenderService
{
    public function isAvailable(): bool
    {
        if (! config('nova_cards.playwright_render', false)) {
            return false;
        }

        // Quick check: verify `node` is executable.
        $probe = new Process(['node', '--version']);
        $probe->run();

        return $probe->isSuccessful();
    }

    public function render(NovaCard $card): array
    {
        if (! $this->isAvailable()) {
            throw new RuntimeException('Playwright rendering is disabled or Node.js is not available.');
        }

        $format = Arr::get(config('nova_cards.formats'), $card->format, config('nova_cards.formats.square'));
        $width  = (int) Arr::get($format, 'width',  1080);
        $height = (int) Arr::get($format, 'height', 1080);

        // Signed URL is valid for 10 minutes — enough for the queue job.
        $signedUrl = URL::temporarySignedRoute(
            'nova-cards.render-frame',
            now()->addMinutes(10),
            ['uuid' => $card->uuid],
        );

        $tmpDir  = rtrim(sys_get_temp_dir(), '/\\') . DIRECTORY_SEPARATOR . 'nova-render-' . $card->uuid;
        $pngPath = $tmpDir . DIRECTORY_SEPARATOR . 'canvas.png';

        if (! is_dir($tmpDir)) {
            mkdir($tmpDir, 0755, true);
        }

        $script  = base_path('scripts/render-nova-card.cjs');
        $process = new Process(
            ['node', $script, "--url={$signedUrl}", "--out={$pngPath}", "--width={$width}", "--height={$height}"],
            null,
            null,
            null,
            60, // seconds
        );

        $process->run();

        if (! $process->isSuccessful()) {
            throw new RuntimeException('Playwright render failed: ' . trim($process->getErrorOutput()));
        }

        if (! file_exists($pngPath) || filesize($pngPath) < 100) {
            throw new RuntimeException('Playwright render produced no output or an empty file.');
        }

        $pngBlob = (string) file_get_contents($pngPath);
        @unlink($pngPath);
        @rmdir($tmpDir);

        // Convert the PNG to WebP + JPEG using GD (already a project dependency).
        $img = @imagecreatefromstring($pngBlob);
        if ($img === false) {
            throw new RuntimeException('Could not decode Playwright PNG output with GD.');
        }

        ob_start();
        imagewebp($img, null, (int) config('nova_cards.render.preview_quality', 86));
        $webpBinary = (string) ob_get_clean();

        ob_start();
        imagejpeg($img, null, (int) config('nova_cards.render.og_quality', 88));
        $jpgBinary = (string) ob_get_clean();

        imagedestroy($img);

        $disk     = Storage::disk((string) config('nova_cards.storage.public_disk', 'public'));
        $basePath = trim((string) config('nova_cards.storage.preview_prefix', 'cards/previews'), '/') . '/' . $card->user_id;
        $previewPath = $basePath . '/' . $card->uuid . '.webp';
        $ogPath      = $basePath . '/' . $card->uuid . '-og.jpg';

        $disk->put($previewPath, $webpBinary, 'public');
        $disk->put($ogPath,      $jpgBinary,  'public');

        $card->forceFill([
            'preview_path'     => $previewPath,
            'preview_width'    => $width,
            'preview_height'   => $height,
            'last_rendered_at' => now(),
        ])->save();

        return [
            'preview_path' => $previewPath,
            'og_path'      => $ogPath,
            'width'        => $width,
            'height'       => $height,
        ];
    }
}
