<?php

namespace App\Http\Controllers;

use App\Filament\Support\Perm;
use App\Models\Inventory\BarcodeLabel;
use App\Services\BarcodeLabelService;
use App\Services\CurrentContext;
use App\Support\BarcodeGenerator;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;

/**
 * Renders a run of labels as a bare print document.
 *
 * A plain controller rather than a Filament page for the same reason invoice
 * printing is one: a label sheet needs its own `@page` size and nothing else on
 * it — no sidebar, no topbar, nothing that would land on the roll.
 *
 * Barcodes are inlined as SVG rather than hot-linked. A sheet of 200 labels
 * would otherwise fire 200 image requests, and browsers routinely start
 * printing before late images land — a label that prints without its barcode is
 * worse than one that never printed.
 */
class BarcodeLabelPrintController extends Controller
{
    /**
     * Physical label stock, in millimetres. Keys are what the print action
     * submits; the CSS in the view derives `@page` from the same numbers.
     *
     * @var array<string, array{width: float, height: float, label: string}>
     */
    public const SIZES = [
        '38x25' => ['width' => 38, 'height' => 25, 'label' => '38 × 25 mm roll'],
        '50x25' => ['width' => 50, 'height' => 25, 'label' => '50 × 25 mm roll'],
        '100x50' => ['width' => 100, 'height' => 50, 'label' => '100 × 50 mm roll'],
        'a4' => ['width' => 70, 'height' => 37, 'label' => 'A4 sheet (3 × 8)'],
    ];

    public const DEFAULT_SIZE = '50x25';

    /** One run is one operator's batch — a runaway `copies` is a jammed printer. */
    public const MAX_COPIES = 200;

    /** Guards the URL against a hand-typed `ids` list long enough to time out. */
    public const MAX_LABELS = 500;

    /**
     * Size options as the print action's Select needs them.
     *
     * @return array<string, string>
     */
    public static function sizeOptions(): array
    {
        return array_map(fn (array $size): string => $size['label'], self::SIZES);
    }

    public function __invoke(Request $request): View
    {
        abort_unless(Perm::can('inventory_barcode_labels', 'print'), 403);

        $size = $request->string('size')->toString();
        abort_unless(isset(self::SIZES[$size]), 404);

        $copies = max(1, min((int) $request->integer('copies', 1), self::MAX_COPIES));

        $ids = collect(explode(',', $request->string('ids')->toString()))
            ->map(fn (string $id): int => (int) trim($id))
            ->filter()
            ->unique()
            ->take(self::MAX_LABELS);

        abort_if($ids->isEmpty(), 404, 'No labels to print.');

        // Tenant-scoped by the model's own global scope, so an id from another
        // workspace simply is not found. Branch scope is deliberately NOT
        // bypassed: an operator prints the stock they can see.
        $labels = BarcodeLabel::query()
            ->with(['product.baseUnit', 'batch', 'serial', 'unit', 'branch'])
            ->whereIn('id', $ids)
            ->orderBy('code')
            ->get();

        abort_if($labels->isEmpty(), 404, 'No labels to print.');

        app(BarcodeLabelService::class)->markPrinted($labels);

        $generator = app(BarcodeGenerator::class);
        $context = app(CurrentContext::class);

        return view('barcode-labels.print', [
            'labels' => $labels,
            'copies' => $copies,
            'size' => self::SIZES[$size],
            'sizeKey' => $size,
            'currency' => $context->currencySymbol(),
            // Rendered once per distinct code, then reused across its copies —
            // a 200-copy run must not re-encode the same barcode 200 times.
            'svgByCode' => $labels->mapWithKeys(fn (BarcodeLabel $label): array => [
                $label->code => $generator->svg($label->code),
            ])->all(),
        ]);
    }
}
