<?php
/**
 * Fichier  : api/product_print_pdf.php
 * Auteur   : KREMER Régis
 * Version  : 2.6.108
 * Date     : 2026-07-20
 * Description : API — Génération PDF A4 des produits sélectionnés depuis le catalogue
 *
 * Changelog :
 *   v2.6.3 (2026-07-08) — Création endpoint PDF produits sélectionnés, sans dépendance externe.
 *   v2.6.8 (2026-07-08) — En-tête PDF aligné modèle d'impression MageStock.
 *   v2.6.18 (2026-07-08) — PDF sélection produits stabilisé : flux propre, headers binaires et streams PDF normalisés.
 *   v2.6.19 (2026-07-08) — En-tête PDF avec vrai logo MageStock issu de assets/img et marges plus propres.
 *   v2.6.24 (2026-07-08) — Cohérence finale : en-tête PDF produit aligné sur les impressions métier.
 *   v2.6.99 (2026-07-20) — Distribution neutre : repli d’organisation générique MageStock.
 *   v2.6.108 (2026-07-21) — Compatibilité PHP 8.4 : paramètres de couleur nullable explicites.
 */
require_once dirname(__DIR__).'/config.php';
startSession();
requireAuth();
requirePerm('products.read');
if($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Méthode non supportée.', 405);
csrfCheck();

$body = json_decode(file_get_contents('php://input'), true) ?? [];
$ids = $body['ids'] ?? [];
if(!is_array($ids)) jsonError('Liste de produits invalide.', 400);
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), fn($v)=>$v > 0)));
if(empty($ids)) jsonError('Aucun produit sélectionné.', 400);
if(count($ids) > 250) jsonError('Maximum 250 produits par PDF.', 400);

$warehouseId = isset($body['warehouseId']) && $body['warehouseId'] !== '' ? (int)$body['warehouseId'] : 0;
$title = trim((string)($body['title'] ?? 'Produits sélectionnés')) ?: 'Produits sélectionnés';

$products = loadData('products');
$stock = loadData('stock');
$warehouses = loadData('warehouses');
$locations = loadData('locations');
$settings = getSettings();
$company = trim((string)($settings['companyName'] ?? '')) ?: 'MageStock';

$byProduct = [];
foreach($products as $p){
    $pid = (int)($p['id'] ?? 0);
    if(in_array($pid, $ids, true)) $byProduct[$pid] = $p;
}
if(empty($byProduct)) jsonError('Aucun produit trouvé pour cette sélection.', 404);

$whById = [];
foreach($warehouses as $w) $whById[(string)($w['id'] ?? '')] = $w;
$locById = [];
foreach($locations as $l) $locById[(string)($l['id'] ?? '')] = $l;

$rows = [];
foreach($ids as $pid){
    if(!isset($byProduct[$pid])) continue;
    $p = $byProduct[$pid];
    $stockRows = array_values(array_filter($stock, function($s) use ($pid, $warehouseId){
        if((string)($s['productId'] ?? '') !== (string)$pid) return false;
        if($warehouseId > 0 && (string)($s['warehouseId'] ?? '') !== (string)$warehouseId) return false;
        return true;
    }));
    $qty = 0.0;
    $whNames = [];
    $locNames = [];
    foreach($stockRows as $s){
        $qty += (float)($s['quantity'] ?? 0);
        $wid = (string)($s['warehouseId'] ?? '');
        if($wid !== ''){
            $whName = (string)($whById[$wid]['name'] ?? ('Magasin '.$wid));
            if(!in_array($whName, $whNames, true)) $whNames[] = $whName;
        }
        $loc = '';
        $locId = (string)($s['locationId'] ?? '');
        if($locId !== '' && isset($locById[$locId])){
            $l = $locById[$locId];
            $loc = implode(' · ', array_filter([(string)($l['code'] ?? ''), (string)($l['label'] ?? ''), (string)($l['zone'] ?? '')]));
        } else {
            $loc = implode(' · ', array_filter([(string)($s['picking'] ?? ''), (string)($s['location'] ?? ''), (string)($s['rack'] ?? ''), (string)($s['shelf'] ?? '')]));
        }
        if($loc !== '' && !in_array($loc, $locNames, true)) $locNames[] = $loc;
    }
    if(empty($whNames)){
        $allowed = $p['allowedWarehouses'] ?? [];
        if(is_string($allowed)){
            $decoded = json_decode($allowed, true);
            $allowed = is_array($decoded) ? $decoded : [];
        }
        if(is_array($allowed)){
            foreach($allowed as $wid){
                $wid = (string)$wid;
                $whName = (string)($whById[$wid]['name'] ?? '');
                if($whName !== '' && !in_array($whName, $whNames, true)) $whNames[] = $whName;
            }
        }
    }
    $unit = product_pdf_base_unit($p);
    $rows[] = [
        'name' => (string)($p['name'] ?? ''),
        'sku' => (string)($p['sku'] ?? ''),
        'barcode' => (string)($p['barcode'] ?? ''),
        'category' => (string)($p['category'] ?? ''),
        'brand' => (string)($p['brand'] ?? ''),
        'warehouses' => implode(', ', $whNames),
        'locations' => implode(', ', array_slice($locNames, 0, 2)),
        'qty' => product_pdf_qty($qty).' '.($unit === 'piece' ? 'u' : $unit),
    ];
}

$pdf = new ProductListPDF(APP_ROOT . '/assets/img/magestock-print-logo.jpg');
$pdf->build($title, $company, $rows, $warehouseId > 0 ? ((string)($whById[(string)$warehouseId]['name'] ?? '')) : 'Tous magasins');
$out = $pdf->render();
$filename = 'produits_selection_'.date('Y-m-d_His').'.pdf';
while (ob_get_level() > 0) { @ob_end_clean(); }
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Content-Transfer-Encoding: binary');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
header('Content-Length: '.strlen($out));
echo $out;
exit;

function product_pdf_base_unit(array $p): string {
    $u = trim((string)($p['packBaseUnit'] ?? ''));
    if($u !== '') return $u;
    $legacy = trim((string)($p['unitType'] ?? 'piece'));
    if($legacy === 'liter') return 'litre';
    return $legacy !== '' ? $legacy : 'piece';
}

function product_pdf_qty(float $v): string {
    if(abs($v - round($v)) < 0.00001) return (string)(int)round($v);
    return rtrim(rtrim(number_format($v, 2, ',', ' '), '0'), ',');
}

function product_pdf_clean(string $text): string {
    $text = preg_replace('/\s+/u', ' ', trim($text));
    return $text ?? '';
}

function product_pdf_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);
}

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

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

function product_pdf_trunc(string $s, int $n): string {
    $s = product_pdf_clean($s);
    return product_pdf_len($s) <= $n ? $s : product_pdf_sub($s, 0, max(1, $n - 1)).'…';
}

class ProductListPDF {
    private array $objs = [];
    private array $pages = [];
    private string $stream = '';
    private int $pagesId = 0;
    private array $fontIds = [];
    private int $logoImageId = 0;
    private int $logoImageW = 0;
    private int $logoImageH = 0;
    private float $w = 595.28;
    private float $h = 841.89;
    private int $pageNo = 0;

    public function __construct(string $logoPath = ''){
        if($logoPath !== '') $this->loadJpegLogo($logoPath);
        $this->fontIds['F1'] = $this->obj('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>');
        $this->fontIds['F2'] = $this->obj('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>');
        $this->fontIds['F3'] = $this->obj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>');
        $this->pagesId = $this->obj('PLACEHOLDER');
    }

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


    private function loadJpegLogo(string $path): void {
        if(!is_file($path) || !is_readable($path)) return;
        $info = @getimagesize($path);
        if(!$info || ($info[2] ?? 0) !== IMAGETYPE_JPEG) return;
        $data = @file_get_contents($path);
        if($data === false || $data === '') return;
        $this->logoImageW = (int)$info[0];
        $this->logoImageH = (int)$info[1];
        $this->logoImageId = $this->obj("<< /Type /XObject /Subtype /Image /Width {$this->logoImageW} /Height {$this->logoImageH} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ".strlen($data)." >>\nstream\n".$data."\nendstream");
    }

    private function imageLogo(float $x, float $y, float $maxW, float $maxH): void {
        if(!$this->logoImageId || !$this->logoImageW || !$this->logoImageH) return;
        $ratio = min($maxW / $this->logoImageW, $maxH / $this->logoImageH);
        $w = $this->logoImageW * $ratio;
        $h = $this->logoImageH * $ratio;
        $this->stream .= sprintf("q %.2f 0 0 %.2f %.2f %.2f cm /ImLogo Do Q\n", $w, $h, $x, $y + ($maxH - $h) / 2);
    }

    private function addPage(): void {
        if($this->stream !== '') $this->closePage();
        $this->pageNo++;
        $this->stream = '';
    }

    private function closePage(): void {
        $sid = $this->obj("<< /Length ".strlen($this->stream)." >>\nstream\n".$this->stream."\nendstream");
        $xObject = $this->logoImageId ? sprintf(' /XObject << /ImLogo %d 0 R >>', $this->logoImageId) : '';
        $p = $this->obj(sprintf('<< /Type /Page /Parent %d 0 R /MediaBox [0 0 %.2f %.2f] /Resources << /ProcSet [/PDF /Text /ImageC] /Font << /F1 %d 0 R /F2 %d 0 R /F3 %d 0 R >>%s >> /Contents %d 0 R >>',
            $this->pagesId, $this->w, $this->h, $this->fontIds['F1'], $this->fontIds['F2'], $this->fontIds['F3'], $xObject, $sid));
        $this->pages[] = $p;
        $this->stream = '';
    }

    private function text(string $t, float $x, float $y, float $size=9, bool $bold=false, string $align='l', array $rgb=[0.07,0.09,0.14], bool $mono=false): void {
        $t = product_pdf_escape($t);
        $font = $mono ? '/F3' : ($bold ? '/F2' : '/F1');
        $cw = $size * ($mono ? 0.60 : ($bold ? 0.55 : 0.50));
        $raw = str_replace(['\\(', '\\)', '\\\\'], '', $t);
        $tw = strlen($raw) * $cw;
        if($align === 'c') $x -= $tw / 2;
        if($align === 'r') $x -= $tw;
        $this->stream .= sprintf("BT %s %.2f Tf %.3f %.3f %.3f rg %.2f %.2f Td (%s) Tj ET\n", $font, $size, $rgb[0], $rgb[1], $rgb[2], $x, $y, $t);
    }

    private function rect(float $x, float $y, float $w, float $h, ?array $fill=null, ?array $stroke=null, float $lineWidth=0.5): void {
        $this->stream .= "q\n";
        if($fill) $this->stream .= sprintf("%.3f %.3f %.3f rg\n", $fill[0], $fill[1], $fill[2]);
        if($stroke) $this->stream .= sprintf("%.3f %.3f %.3f RG %.2f w\n", $stroke[0], $stroke[1], $stroke[2], $lineWidth);
        $op = $fill && $stroke ? 'B' : ($fill ? 'f' : 'S');
        $this->stream .= sprintf("%.2f %.2f %.2f %.2f re %s\nQ\n", $x, $y, $w, $h, $op);
    }

    private function line(float $x1, float $y1, float $x2, float $y2, array $rgb=[0.82,0.86,0.91], float $width=0.5): void {
        $this->stream .= sprintf("q %.3f %.3f %.3f RG %.2f w %.2f %.2f m %.2f %.2f l S Q\n", $rgb[0], $rgb[1], $rgb[2], $width, $x1, $y1, $x2, $y2);
    }

    private function header(string $title, string $company, string $scope, int $total): float {
        // En-tête commun MageStock : vrai logo livré dans assets/img, société, titre, date et périmètre.
        if($this->logoImageId){
            $this->imageLogo(36, 783, 124, 36);
        } else {
            $this->rect(36, 781, 36, 34, [0.11,0.30,0.85], null);
            $this->text('MS', 54, 794, 12, true, 'c', [1,1,1]);
        }
        $this->text($company, 170, 796, 11, true, 'l', [0.06,0.09,0.16]);
        $this->text('Gestion de stock · MageStock', 170, 784, 7.2, false, 'l', [0.34,0.40,0.49]);
        $this->text('Page '.$this->pageNo, 559, 803, 8.5, false, 'r', [0.34,0.40,0.49]);
        $this->line(36, 772, 559, 772, [0.11,0.30,0.85], 1.2);
        $this->text($title, 36, 746, 18, true, 'l', [0.06,0.09,0.16]);
        $meta = date('d/m/Y H:i').' · '.$total.' produit(s) · '.($scope ?: 'Tous magasins');
        $this->text($meta, 36, 729, 9, false, 'l', [0.34,0.40,0.49]);
        $this->line(36, 716, 559, 716, [0.82,0.86,0.91], 0.7);
        return 692;
    }

    private function tableHeader(float $y): float {
        $this->rect(36, $y - 18, 523, 22, [0.94,0.97,1.0], [0.82,0.86,0.91], 0.4);
        $this->text('Produit', 44, $y - 11, 8, true, 'l', [0.18,0.24,0.34]);
        $this->text('SKU', 222, $y - 11, 8, true, 'l', [0.18,0.24,0.34]);
        $this->text('Catégorie / marque', 300, $y - 11, 8, true, 'l', [0.18,0.24,0.34]);
        $this->text('Magasin / rangement', 408, $y - 11, 8, true, 'l', [0.18,0.24,0.34]);
        $this->text('Stock', 550, $y - 11, 8, true, 'r', [0.18,0.24,0.34]);
        return $y - 48;
    }

    public function build(string $title, string $company, array $rows, string $scope=''): void {
        $total = count($rows);
        $this->addPage();
        $y = $this->header($title, $company, $scope, $total);
        $y = $this->tableHeader($y);
        $rowH = 36;
        foreach($rows as $idx => $r){
            if($y < 74){
                $this->footer();
                $this->addPage();
                $y = $this->header($title, $company, $scope, $total);
                $y = $this->tableHeader($y);
            }
            $fill = ($idx % 2 === 0) ? [0.995,0.997,1.0] : null;
            if($fill) $this->rect(36, $y - 8, 523, $rowH, $fill, null);
            $this->line(36, $y - 8, 559, $y - 8, [0.90,0.92,0.95], 0.35);
            $this->text(product_pdf_trunc($r['name'], 31), 44, $y + 13, 9.5, true);
            $sub = trim(($r['barcode'] ?? '') !== '' ? 'Code: '.$r['barcode'] : '');
            if($sub !== '') $this->text(product_pdf_trunc($sub, 32), 44, $y, 7.5, false, 'l', [0.40,0.47,0.56], true);
            $this->text(product_pdf_trunc($r['sku'] ?: '-', 17), 222, $y + 8, 8.5, false, 'l', [0.16,0.24,0.36], true);
            $catBrand = trim(($r['category'] ?: '-').(($r['brand'] ?? '') !== '' ? ' · '.$r['brand'] : ''));
            $this->text(product_pdf_trunc($catBrand, 22), 300, $y + 8, 8);
            $this->text(product_pdf_trunc($r['warehouses'] ?: '-', 25), 408, $y + 13, 8);
            if(($r['locations'] ?? '') !== '') $this->text(product_pdf_trunc($r['locations'], 25), 408, $y, 7.3, false, 'l', [0.40,0.47,0.56]);
            $this->text($r['qty'] ?: '0', 550, $y + 8, 8.5, true, 'r', [0.06,0.09,0.16]);
            $y -= $rowH;
        }
        $this->footer();
    }

    private function footer(): void {
        $this->line(36, 44, 559, 44, [0.86,0.89,0.93], 0.4);
        $this->text('MageStock - Liste de produits selectionnes', 36, 28, 7.5, false, 'l', [0.46,0.53,0.62]);
        $this->text('Document genere automatiquement', 559, 28, 7.5, false, 'r', [0.46,0.53,0.62]);
    }

    public function render(): string {
        if($this->stream !== '') $this->closePage();
        $kids = implode(' ', array_map(fn($id)=>$id.' 0 R', $this->pages));
        $this->objs[$this->pagesId] = sprintf('<< /Type /Pages /Kids [%s] /Count %d >>', $kids, count($this->pages));
        $catalog = $this->obj('<< /Type /Catalog /Pages '.$this->pagesId.' 0 R >>');
        $out = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
        $xref = [0];
        foreach($this->objs as $id => $content){
            $xref[$id] = strlen($out);
            $out .= $id." 0 obj\n".$content."\nendobj\n";
        }
        $start = strlen($out);
        $out .= "xref\n0 ".(count($this->objs)+1)."\n0000000000 65535 f \n";
        for($i=1;$i<=count($this->objs);$i++) $out .= sprintf("%010d 00000 n \n", $xref[$i]);
        $out .= "trailer\n<< /Size ".(count($this->objs)+1)." /Root ".$catalog." 0 R >>\nstartxref\n".$start."\n%%EOF";
        return $out;
    }
}
