<?php
/**
 * Fichier  : api/barcode_pdf.php
 * Version  : 3.4.0
 * Date     : 2026-06-04
 * Description : Génération PDF serveur sécurisé — fallback CODE128 uniquement
 *
 * POST JSON : { products, style, format, bcFormat, options, customCode }
 */
require_once dirname(__DIR__).'/config.php';
startSession(); requireAuth(); requireAdmin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonError('Méthode non supportée.', 405); }
csrfCheck();

$body       = json_decode(file_get_contents('php://input'), true) ?? [];
$products   = $body['products']   ?? [];
$style      = $body['style']      ?? 'img_barcode';
$format     = $body['format']     ?? ['cols'=>3,'rows'=>7,'wmm'=>70,'hmm'=>37,'gapmm'=>2.5,'marginmm'=>8];
$bcFormat   = $body['bcFormat']   ?? 'CODE128';
$options    = $body['options']    ?? ['showName'=>true,'showSku'=>true,'showSize'=>false];
$customCode = trim($body['customCode'] ?? '');

if (empty($products)) { jsonError('Aucun produit sélectionné.'); }

// Le rendu principal PDF est désormais généré côté navigateur pour rester fidèle
// à la prévisualisation (QR, EAN13/UPC, images, styles). Ce endpoint reste un
// fallback serveur sécurisé, volontairement limité au CODE128 simple.
if ($style === 'qr' || strtoupper((string)$bcFormat) !== 'CODE128') {
    jsonError("Export PDF serveur limité au CODE128. Utilisez l'export PDF de la page pour QR/EAN/UPC.", 400);
}

// Expand labels
$labels = [];
foreach ($products as $p) {
    $qty = max(1, (int)($p['qty'] ?? 1));
    for ($i = 0; $i < $qty; $i++) {
        $labels[] = [
            'name'    => (string)($p['name'] ?? ''),
            'sku'     => (string)($p['sku'] ?? ''),
            'barcode' => $customCode !== '' ? $customCode : (string)($p['barcode'] ?? $p['sku'] ?? ''),
            'size'    => (string)($p['size'] ?? ''),
        ];
    }
}

$cols     = max(1,(int)($format['cols']??3));
$rows     = max(1,(int)($format['rows']??7));
$wmm      = max(20,(float)($format['wmm']??70));
$hmm      = max(15,(float)($format['hmm']??37));
$gapmm    = max(0,(float)($format['gapmm']??2.5));
$marginmm = max(0,(float)($format['marginmm']??8));

// Auto-cap rows to what actually fits on A4 (297mm)
$availH   = 297 - 2 * $marginmm;
$maxRows  = max(1, (int)floor(($availH + $gapmm) / ($hmm + $gapmm)));
if ($rows > $maxRows) $rows = $maxRows;

$showName = !empty($options['showName']);
$showSku  = !empty($options['showSku']);
$showSize = !empty($options['showSize']);

$MM  = 2.83465;
$pw  = 210*$MM; $ph = 297*$MM;

// Scale 93% to match print CSS (fits within any printer hardware margins)
$SCALE = 0.93;
$lw  = $wmm*$MM * $SCALE;
$lh  = $hmm*$MM * $SCALE;
$gap = $gapmm*$MM * $SCALE;
$margin = $marginmm*$MM * $SCALE;

$perPage = $cols * $rows;
$gridW = $cols*$lw + ($cols-1)*$gap;
$gridH = $rows*$lh + ($rows-1)*$gap;
// Center the scaled grid on page
$ox = ($pw - $gridW) / 2;
$oyTop = ($ph + $gridH) / 2;
$totalPages = max(1, (int)ceil(count($labels)/$perPage));

$pdf = new MSPDF();

for ($pg = 0; $pg < $totalPages; $pg++) {
    $pdf->addPage($pw, $ph);
    $sl = array_slice($labels, $pg*$perPage, $perPage);
    // Pad to fill grid
    while (count($sl) < $perPage) $sl[] = null;

    for ($i = 0; $i < count($sl); $i++) {
        $col = $i % $cols; $row = (int)floor($i/$cols);
        $x = $ox + $col*($lw+$gap);
        $y = $oyTop - ($row+1)*$lh - $row*$gap;
        $L = $sl[$i]; $pad = 2*$MM;

        // Dashed border for all cells
        $pdf->dashedRect($x, $y, $lw, $lh, $L ? 0.7 : 0.3);

        if (!$L) continue;

        $ix=$x+$pad; $iy=$y+$pad; $iw=$lw-2*$pad; $ih=$lh-2*$pad;
        $name = $L['name']; $sku = $L['sku']; $bc = $L['barcode']?:$sku?:$name; $sz = $L['size'];
        $cy = $iy + $ih;

        $nameH = $showName ? 3.2*$MM : 0;
        $skuH  = $showSku ? 2.5*$MM : 0;
        $sizeH = ($showSize && $sz) ? 2.5*$MM : 0;
        $bcH   = min(max(5*$MM, $ih - $nameH - $skuH - $sizeH - 3*$MM), 11*$MM);

        if ($showName && $name !== '') {
            $cy -= $nameH;
            $pdf->text(_trunc($name,45), $ix+$iw/2, $cy+0.8, 7.5, true, 'c');
            $cy -= 0.8*$MM;
        }
        $cy -= $bcH;
        if ($bc !== '') $pdf->code128($bc, $ix+1, $cy, $iw-2, $bcH);
        $cy -= 1*$MM;
        if ($showSku && $sku !== '') {
            $cy -= $skuH;
            $pdf->text(_trunc($sku,30), $ix+$iw/2, $cy+0.5, 6, false, 'c', true);
        }
        if ($showSize && $sz !== '') {
            $cy -= $sizeH;
            $pdf->text($sz, $ix+$iw/2, $cy+0.5, 6, true, 'c', false, [0.15,0.35,0.85]);
        }
    }
}

header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="etiquettes_'.date('Y-m-d_His').'.pdf"');
$data = $pdf->render();
header('Content-Length: '.strlen($data));
echo $data;
exit;

function _strlen_safe(string $s): int {
    return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen($s);
}

function _substr_safe(string $s, int $start, int $len): string {
    return function_exists('mb_substr') ? mb_substr($s, $start, $len, 'UTF-8') : substr($s, $start, $len);
}

function _trunc(string $s, int $n): string {
    return _strlen_safe($s) <= $n ? $s : _substr_safe($s, 0, $n - 1).'…';
}

function _pdf_text_escape(string $t): string {
    $converted = function_exists('iconv') ? @iconv('UTF-8', 'Windows-1252//TRANSLIT//IGNORE', $t) : false;
    $safe = $converted !== false ? $converted : $t;
    return str_replace(['\\','(',')'], ['\\\\','\\(','\\)'], $safe);
}

// ════════════════════════════════════════════════════════════════
// MSPDF — Micro PDF writer, 100% PHP, zero dependency
// ════════════════════════════════════════════════════════════════
class MSPDF {
    private $objs = [];
    private $pages = [];
    private $stream = '';
    private $pgW = 595.28;
    private $pgH = 841.89;
    private $fIds = [];
    private $pagesId = 0;
    private static $P = null;
    private static $B = [];

    public function __construct(){
        self::initC128();
        $this->fIds['F1'] = $this->obj("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
        $this->fIds['F2'] = $this->obj("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
        $this->fIds['F3'] = $this->obj("<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>");
        $this->pagesId = $this->obj("PLACEHOLDER");
    }

    private function obj(string $c): int {
        $id = count($this->objs) + 1;
        $this->objs[$id] = $c;
        return $id;
    }

    public function addPage(float $w, float $h): void {
        if ($this->stream !== '') $this->closePage();
        $this->pgW = $w; $this->pgH = $h; $this->stream = '';
    }

    private function closePage(): void {
        $sId = $this->obj("<< /Length ".strlen($this->stream)." >>\nstream\n".$this->stream."endstream");
        $f1=$this->fIds['F1']; $f2=$this->fIds['F2']; $f3=$this->fIds['F3'];
        $pId = $this->obj(sprintf(
            "<< /Type /Page /Parent %d 0 R /MediaBox [0 0 %.2f %.2f] /Resources << /Font << /F1 %d 0 R /F2 %d 0 R /F3 %d 0 R >> >> /Contents %d 0 R >>",
            $this->pagesId, $this->pgW, $this->pgH, $f1, $f2, $f3, $sId
        ));
        $this->pages[] = $pId;
        $this->stream = '';
    }

    public function text(string $t, float $x, float $y, float $sz, bool $bold=false, string $align='l', bool $mono=false, ?array $rgb=null): void {
        $font = $mono ? '/F3' : ($bold ? '/F2' : '/F1');
        $r=$rgb[0]??0.07; $g=$rgb[1]??0.07; $b=$rgb[2]??0.07;
        $esc = _pdf_text_escape($t);
        $cw = $mono ? $sz*0.6 : ($bold ? $sz*0.56 : $sz*0.50);
        $tw = _strlen_safe($t) * $cw;
        $tx = $x;
        if ($align==='c') $tx = $x - $tw/2;
        elseif ($align==='r') $tx = $x - $tw;
        $this->stream .= sprintf("BT %s %.1f Tf %.3f %.3f %.3f rg %.2f %.2f Td (%s) Tj ET\n",$font,$sz,$r,$g,$b,$tx,$y,$esc);
    }

    public function dashedRect(float $x, float $y, float $w, float $h, float $gray=0.7): void {
        $this->stream .= sprintf("q %.2f %.2f %.2f RG 0.6 w [2 2] 0 d %.2f %.2f %.2f %.2f re S Q\n",$gray,$gray,$gray,$x,$y,$w,$h);
    }

    public function code128(string $t, float $x, float $y, float $w, float $h): void {
        if ($t==='') return;
        $pats = self::encode128B($t);
        if (!$pats) { $this->text($t,$x+$w/2,$y+$h/2-3,6,false,'c',true); return; }
        $tm = 0; foreach ($pats as $p) $tm += array_sum($p);
        if ($tm<=0) return;
        $mw = $w/$tm; $cx = $x;
        $this->stream .= "q 0 0 0 rg\n";
        foreach ($pats as $pat) {
            $bar = true;
            foreach ($pat as $n) {
                if ($bar && $n>0) $this->stream .= sprintf("%.3f %.3f %.3f %.3f re f\n",$cx,$y,$n*$mw,$h);
                $cx += $n*$mw; $bar = !$bar;
            }
        }
        $this->stream .= "Q\n";
    }

    public function render(): string {
        if ($this->stream !== '') $this->closePage();
        $kids = implode(' ', array_map(fn($id)=>$id.' 0 R', $this->pages));
        $this->objs[$this->pagesId] = "<< /Type /Pages /Kids [".$kids."] /Count ".count($this->pages)." >>";
        $catId = $this->obj("<< /Type /Catalog /Pages ".$this->pagesId." 0 R >>");
        $infId = $this->obj("<< /Title (Etiquettes MageStock) /Producer (MageStock) /Creator (MageStock) >>");

        $buf = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
        $xref = [];
        foreach ($this->objs as $id => $content) {
            $xref[$id] = strlen($buf);
            $buf .= $id." 0 obj\n".$content."\nendobj\n";
        }
        $xp = strlen($buf);
        $n = count($this->objs);
        $buf .= "xref\n0 ".($n+1)."\n0000000000 65535 f \n";
        for ($i=1;$i<=$n;$i++) $buf .= str_pad((string)($xref[$i]??0),10,'0',STR_PAD_LEFT)." 00000 n \n";
        $buf .= "trailer\n<< /Size ".($n+1)." /Root ".$catId." 0 R /Info ".$infId." 0 R >>\nstartxref\n".$xp."\n%%EOF";
        return $buf;
    }

    private static function initC128(): void {
        if (self::$P !== null) return;
        self::$P = [
            [2,1,2,2,2,2],[2,2,2,1,2,2],[2,2,2,2,2,1],[1,2,1,2,2,3],[1,2,1,3,2,2],
            [1,3,1,2,2,2],[1,2,2,2,1,3],[1,2,2,3,1,2],[1,3,2,2,1,2],[2,2,1,2,1,3],
            [2,2,1,3,1,2],[2,3,1,2,1,2],[1,1,2,2,3,2],[1,2,2,1,3,2],[1,2,2,2,3,1],
            [1,1,3,2,2,2],[1,2,3,1,2,2],[1,2,3,2,2,1],[2,2,3,2,1,1],[2,2,1,1,3,2],
            [2,2,1,2,3,1],[2,1,3,2,1,2],[2,2,3,1,1,2],[3,1,2,1,3,1],[3,1,1,2,2,2],
            [3,2,1,1,2,2],[3,2,1,2,2,1],[3,1,2,2,1,2],[3,2,2,1,1,2],[3,2,2,2,1,1],
            [2,1,2,1,2,3],[2,1,2,3,2,1],[2,3,2,1,2,1],[1,1,1,3,2,3],[1,3,1,1,2,3],
            [1,3,1,3,2,1],[1,1,2,3,1,3],[1,3,2,1,1,3],[1,3,2,3,1,1],[2,1,1,3,1,3],
            [2,3,1,1,1,3],[2,3,1,3,1,1],[1,1,2,1,3,3],[1,1,2,3,3,1],[1,3,2,1,3,1],
            [1,1,3,1,2,3],[1,1,3,3,2,1],[1,3,3,1,2,1],[3,1,3,1,2,1],[2,1,1,3,3,1],
            [2,3,1,1,3,1],[2,1,3,1,1,3],[2,1,3,3,1,1],[2,1,3,1,3,1],[3,1,1,1,2,3],
            [3,1,1,3,2,1],[3,3,1,1,2,1],[3,1,2,1,1,3],[3,1,2,3,1,1],[3,3,2,1,1,1],
            [3,1,4,1,1,1],[2,2,1,4,1,1],[4,3,1,1,1,1],[1,1,1,2,2,4],[1,1,1,4,2,2],
            [1,2,1,1,2,4],[1,2,1,4,2,1],[1,4,1,1,2,2],[1,4,1,2,2,1],[1,1,2,2,1,4],
            [1,1,2,4,1,2],[1,2,2,1,1,4],[1,2,2,4,1,1],[1,4,2,1,1,2],[1,4,2,2,1,1],
            [2,4,1,2,1,1],[2,2,1,1,1,4],[4,1,3,1,1,1],[2,4,1,1,1,2],[1,3,4,1,1,1],
            [1,1,1,2,4,2],[1,2,1,1,4,2],[1,2,1,2,4,1],[1,1,4,2,1,2],[1,2,4,1,1,2],
            [1,2,4,2,1,1],[4,1,1,2,1,2],[4,2,1,1,1,2],[4,2,1,2,1,1],[2,1,2,1,4,1],
            [2,1,4,1,2,1],[4,1,2,1,2,1],[1,1,1,1,4,3],[1,1,1,3,4,1],[1,3,1,1,4,1],
            [1,1,4,1,1,3],[1,1,4,3,1,1],[4,1,1,1,1,3],[4,1,1,3,1,1],[1,1,3,1,4,1],
            [1,1,4,1,3,1],[3,1,1,1,4,1],[4,1,1,1,3,1],[2,1,1,4,1,2],[2,1,1,2,1,4],
            [2,1,1,2,3,2],
        ];
        for ($i=32;$i<=127;$i++) self::$B[chr($i)] = $i-32;
    }

    private static function encode128B(string $t): array {
        $vals = [104]; $cs = 104;
        for ($i=0;$i<strlen($t);$i++) {
            $v = self::$B[$t[$i]] ?? 0;
            $vals[] = $v; $cs += $v*($i+1);
        }
        $vals[] = $cs % 103;
        $out = [];
        foreach ($vals as $v) { if (isset(self::$P[$v])) $out[] = self::$P[$v]; }
        $out[] = [2,3,3,1,1,1,2];
        return $out;
    }
}
