<?php

declare(strict_types=1);

namespace App\Services;

use App\Enums\QrLabelTemplate;
use App\Http\Controllers\AssetQrController;
use App\Models\Asset;
use Barryvdh\DomPDF\Facade\Pdf;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\Writer\PngWriter;
use Endroid\QrCode\Writer\Result\ResultInterface;
use Endroid\QrCode\Writer\SvgWriter;
use Illuminate\Support\Collection;

/**
 * Encapsulates the QR library and the label-sheet PDF layout, so nothing
 * else in the app imports either directly.
 *
 * SVG is what the asset detail page and QR scanning both use — no image
 * extension needed, and it stays crisp at any size. PNG exists only because
 * a raster format is what a "Download QR" button and a PDF label sheet both
 * need: dompdf's SVG support is inconsistent enough not to trust for print,
 * and PNG is what most people expect a downloaded QR image to be. GD, which
 * `PngWriter` needs, is already a required extension for this app (see the
 * README) — this adds nothing new to deploy.
 */
class QrCodeService
{
    /**
     * The SVG markup for one asset's QR code, encoding the short scan URL
     * that resolves through {@see AssetQrController::scan()}.
     *
     * The QR encodes the asset's UUID, never its `asset_tag`: the tag is
     * sequential and guessable, which would let someone enumerate
     * `/a/AUM-LAP-0001`, `0002`, … before ever reaching the auth check.
     */
    public function svgFor(Asset $asset): string
    {
        return $this->build(new SvgWriter, $asset, 280)->getString();
    }

    /**
     * Raw PNG bytes for one asset's QR code — the "Download PNG" action and
     * the single-label print page.
     */
    public function pngFor(Asset $asset, int $size = 480): string
    {
        return $this->build(new PngWriter, $asset, $size)->getString();
    }

    /**
     * A `data:image/png;base64,…` URI — what actually gets embedded in the
     * label-sheet HTML/PDF, since dompdf resolves an inline data URI without
     * needing to fetch anything.
     */
    public function pngDataUriFor(Asset $asset, int $size): string
    {
        return $this->build(new PngWriter, $asset, $size)->getDataUri();
    }

    /**
     * The `['asset' => Asset, 'qrDataUri' => string]` rows the `_label-grid`
     * partial expects — shared by the on-screen preview (AssetQrController)
     * and labelSheet() below, so the two can never disagree on what a label
     * contains.
     *
     * @param  Collection<int, Asset>  $assets
     * @return Collection<int, array{asset: Asset, qrDataUri: string}>
     */
    public function labelsFor(Collection $assets, QrLabelTemplate $template): Collection
    {
        return $assets->map(fn (Asset $asset): array => [
            'asset' => $asset,
            'qrDataUri' => $this->pngDataUriFor($asset, $template->qrSize()),
        ]);
    }

    /**
     * An A4 sheet of QR + tag + name labels for every given asset, laid out
     * per the chosen template, as PDF bytes.
     *
     * Renders `assets/qr/label-sheet-pdf.blade.php`, which in turn includes
     * the same `_label-grid` partial the on-screen print-preview page uses —
     * the preview and the downloaded PDF are guaranteed to match because
     * they share that one piece of markup, not two implementations that
     * could quietly drift apart.
     *
     * @param  Collection<int, Asset>  $assets
     */
    public function labelSheet(Collection $assets, QrLabelTemplate $template): string
    {
        return Pdf::loadView('assets.qr.label-sheet-pdf', [
            'labels' => $this->labelsFor($assets, $template),
            'template' => $template,
        ])->setPaper('a4', 'portrait')->output();
    }

    private function build(SvgWriter|PngWriter $writer, Asset $asset, int $size): ResultInterface
    {
        return (new Builder(
            writer: $writer,
            data: route('assets.scan', ['uuid' => $asset->uuid]),
            errorCorrectionLevel: ErrorCorrectionLevel::Medium,
            size: $size,
            margin: 10,
        ))->build();
    }
}
