<?php
require_once __DIR__ . '/includes/db.php';

$id = (int)($_GET['id'] ?? 0);

$reception = null;
$items     = [];
$dbError   = null;

try {
    $reception = db_select("SELECT * FROM fbe_receptions WHERE id = ?", [$id])[0] ?? null;
    if ($reception) {
        // Doar items cu cantitate (ce împachetăm efectiv), în ordinea sesiunii.
        // Produsele legate se grupează pe phone_series_id (în client).
        $items = db_select(
            "SELECT i.id, i.ean, i.qty, i.product_id, i.product_name, i.packed, i.packed_qty, i.position,
                    p.phone_series_id, p.units_per_box, p.pnk,
                    p.custom_name, p.product_name AS live_name,
                    (SELECT pb.name || ' ' || pm.name || ' ' || ps.name
                     FROM phone_series ps
                     JOIN phone_models pm ON pm.id = ps.model_id
                     JOIN phone_brands pb ON pb.id = pm.brand_id
                     WHERE ps.id = p.phone_series_id) AS phone_label,
                    (SELECT GROUP_CONCAT(pb.name || ' ' || pm.name || ' ' || ps.name, ', ')
                     FROM product_phone_series_alt a
                     JOIN phone_series ps ON ps.id = a.phone_series_id
                     JOIN phone_models pm ON pm.id = ps.model_id
                     JOIN phone_brands pb ON pb.id = pm.brand_id
                     WHERE a.product_id = p.id) AS alt_labels
             FROM fbe_reception_items i
             LEFT JOIN products p ON p.id = i.product_id
             WHERE i.reception_id = ? AND i.qty IS NOT NULL AND i.qty > 0
             ORDER BY i.position ASC",
            [$id]
        );
    }
} catch (Throwable $ex) {
    $dbError = $ex->getMessage();
}

$labelW = (int)setting_get('fbe_label_w', '40');
$labelH = (int)setting_get('fbe_label_h', '30');
$qzPrinter = setting_get('qz_printer', '') ?? '';
$labelTitle = setting_get('fbe_label_title', 'Folie Sticlă Securizată') ?? '';

// Opțiuni de print label (densitate, interpolare, rotație, culoare, scalare).
$labelOptsDefault = [
    'density'      => 203,
    'interpolation'=> 'nearest-neighbor',
    'rotation'     => 0,
    'colorType'    => 'blackwhite',
    'scaleContent' => true,
];
$labelOpts = $labelOptsDefault;
$savedOpts = setting_get('fbe_label_opts', '');
if ($savedOpts) {
    $decoded = json_decode($savedOpts, true);
    if (is_array($decoded)) $labelOpts = array_merge($labelOptsDefault, $decoded);
}

$pageTitle  = 'Împachetare';
$breadcrumb = [
    ['label' => 'Generare recepție', 'url' => url('receptie.php')],
    ['label' => 'Împachetare'],
];

require __DIR__ . '/includes/header.php';
?>

<!-- JsBarcode + QZ Tray pentru cod de bare și print direct -->
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js"></script>
<script>
  window.QZ_CERT_URL = '<?= url('api/qz-cert.php') ?>';
  window.QZ_SIGN_URL = '<?= url('api/qz-sign.php') ?>';
</script>
<script src="<?= url('assets/qz-labels.js') ?>?v=<?= @filemtime(APP_ROOT . '/assets/qz-labels.js') ?: APP_VERSION ?>"></script>

<style>
  .label {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    box-sizing: border-box; padding: 1mm; overflow: hidden; background: #fff;
    border: 1px dashed #cbd5e1;
  }
  .label svg { max-width: 100%; height: auto; }
  .label-name  { font-size: 8px; line-height: 1.1; text-align: center; margin-top: 0.5mm; color: #334155; }
  .label-title { font-size: 9px; font-weight: 700; line-height: 1.1; text-align: center; margin-top: 0.5mm; color: #0f172a; }
  .label-phone { font-size: 9px; font-weight: 700; line-height: 1.1; text-align: center; margin-top: 0.3mm; color: #0f172a; }
  /* Highlight + animație la scanare cu pistolul */
  @keyframes scanFlash {
    0%   { background-color: rgba(16,185,129,0.55); box-shadow: inset 0 0 0 2px rgba(16,185,129,0.8); }
    60%  { background-color: rgba(16,185,129,0.22); }
    100% { background-color: rgba(16,185,129,0); box-shadow: inset 0 0 0 0 rgba(16,185,129,0); }
  }
  @keyframes scanPop { 0% { transform: scale(1); } 25% { transform: scale(1.015); } 100% { transform: scale(1); } }
  .scan-flash { animation: scanFlash 1s ease-out, scanPop .35s ease-out; }
  @media print {
    body * { visibility: hidden !important; }
    #singleLabelArea, #singleLabelArea * { visibility: visible !important; }
    #singleLabelArea { position: absolute !important; left: 0 !important; top: 0 !important; margin: 0; padding: 0; }
    #singleLabelArea .label { border: none !important; }
  }
</style>

<?php if ($dbError): ?>
  <div class="mb-6 bg-amber-50 border border-amber-200 rounded-xl p-4 text-sm text-amber-900">
    Eroare DB. <details class="mt-2"><summary class="cursor-pointer">Detalii</summary><pre class="mt-2 text-xs"><?= e($dbError) ?></pre></details>
  </div>
<?php endif; ?>

<?php if (!$reception): ?>
  <div class="bg-white border border-slate-100 rounded-xl shadow-soft p-10 text-center">
    <div class="mx-auto w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center mb-3">
      <svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/></svg>
    </div>
    <p class="text-slate-500">Sesiunea de recepție nu există.</p>
    <a href="<?= url('receptie.php') ?>" class="inline-block mt-4 text-sm font-medium text-brand-600 hover:text-brand-700">← Înapoi la recepții</a>
  </div>
<?php else: ?>

<div
  x-data="packingPage({
    items: <?= htmlspecialchars(json_encode($items), ENT_QUOTES, 'UTF-8') ?>,
    labelW: <?= (int)$labelW ?>,
    labelH: <?= (int)$labelH ?>,
    qzPrinter: '',
    labelTitle: <?= htmlspecialchars(json_encode($labelTitle, JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8') ?>,
    labelOpts: <?= htmlspecialchars(json_encode($labelOpts), ENT_QUOTES, 'UTF-8') ?>,
    csrf: '<?= e(csrf_token()) ?>'
  })"
  x-init="initLabels()"
>
  <!-- @page-ul de print pentru un singur label (dimensiunea setată) -->
  <style x-ref="pageCss"></style>

  <!-- Header -->
  <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
    <div class="flex items-center gap-3 min-w-0">
      <a href="<?= url('receptie.php') ?>" class="flex-shrink-0 inline-flex items-center justify-center w-9 h-9 text-slate-500 hover:text-brand-600 hover:bg-slate-100 rounded-lg transition" title="Înapoi la recepții">
        <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
      </a>
      <div class="min-w-0">
        <h1 class="text-2xl font-bold text-slate-900 truncate">Împachetare: <?= e($reception['name']) ?></h1>
        <p class="text-sm text-slate-500 mt-0.5">
          <?php if ($reception['reception_number']): ?>Nr. recepție <span class="font-mono"><?= e($reception['reception_number']) ?></span> · <?php endif; ?>
          <span x-text="packedPieces()"></span> / <span x-text="totalPieces()"></span> bucăți împachetate ·
          <span x-text="packedCount()"></span>/<span x-text="items.length"></span> produse gata
        </p>
      </div>
    </div>
    <div class="flex items-center gap-2 flex-shrink-0">
      <a href="<?= url('labels.php') ?>?id=<?= (int)$id ?>" class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-brand-700 bg-brand-50 border border-brand-100 hover:bg-brand-100 rounded-lg shadow-soft transition" title="Printează labeluri pentru produsele din această recepție">
        <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
        Labeluri
      </a>
      <button @click="setAll(true)" class="px-3 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-200 hover:bg-slate-50 rounded-lg shadow-soft transition">Toate gata</button>
      <button @click="setAll(false)" class="px-3 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-200 hover:bg-slate-50 rounded-lg shadow-soft transition">Resetează</button>
    </div>
  </div>

  <!-- Status imprimantă QZ Tray -->
  <div class="bg-white border border-slate-100 rounded-xl shadow-soft p-3 mb-4 flex flex-wrap items-center gap-3">
    <div class="flex items-center gap-2">
      <span class="w-2.5 h-2.5 rounded-full flex-shrink-0"
            :class="qzConnected ? 'bg-emerald-400' : (qzAvailable ? 'bg-amber-400' : 'bg-rose-400')"></span>
      <span class="text-sm font-medium text-slate-700" x-text="qzConnected ? 'Conectat la imprimantă' : (qzAvailable ? 'Neconectat' : 'QZ Tray indisponibil')"></span>
    </div>

    <div class="flex items-center gap-2" x-show="qzPrinters.length > 0">
      <svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
      <select x-model="qzPrinter" @change="selectPrinter()"
        class="px-2 py-1.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white max-w-[260px]">
        <option value="">— alege imprimanta —</option>
        <template x-for="p in qzPrinters" :key="p">
          <option :value="p" x-text="p"></option>
        </template>
      </select>
    </div>
    <div class="flex items-center gap-2" x-show="qzPrinters.length === 0 && qzPrinter">
      <span class="text-sm text-slate-500">Imprimantă: <span class="font-medium text-slate-700" x-text="qzPrinter"></span></span>
    </div>

    <button @click="connectQz()" :disabled="qzBusy"
      class="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-slate-700 bg-white border border-slate-200 hover:bg-slate-50 rounded-lg transition disabled:opacity-50">
      <span x-show="qzBusy" class="spinner"></span>
      <span x-text="qzConnected ? 'Reîncarcă' : 'Conectează'"></span>
    </button>

    <a href="<?= url('settings.php') ?>" class="text-xs text-slate-400 hover:text-brand-600 ml-auto">Setări imprimantă →</a>
  </div>

  <!-- Hint scanare cu pistolul -->
  <div class="mb-4 flex items-center gap-2 text-sm text-sky-800 bg-sky-50 border border-sky-100 rounded-lg px-3 py-2">
    <svg class="w-4 h-4 flex-shrink-0 text-sky-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v1m0 14v1m8-8h1M3 12H2m3.6-6.4l.7.7m11.4-.7l-.7.7M6.3 17.7l-.7.7M4 8h1v8H4zm15 0h1v8h-1zM8 8h1v8H8zm3 0h2v8h-2zm5 0h1v8h-1z"/></svg>
    Scanează codul de bare cu pistolul — produsul scanat primește automat <strong>+1</strong> la împachetat.
  </div>

  <!-- Toast feedback scanare -->
  <div x-show="scanMsg" x-cloak x-transition.opacity
       class="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 px-4 py-2.5 rounded-lg shadow-lg text-sm font-medium flex items-center gap-2"
       :class="scanOk ? 'bg-emerald-600 text-white' : 'bg-rose-600 text-white'">
    <svg x-show="scanOk" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
    <svg x-show="!scanOk" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
    <span x-text="scanMsg"></span>
  </div>

  <!-- Bară de progres -->
  <div class="mb-6">
    <div class="h-3 bg-slate-100 rounded-full overflow-hidden">
      <div class="h-full bg-emerald-500 transition-all duration-300" :style="'width:' + progressPct() + '%'"></div>
    </div>
    <div class="mt-1 text-xs text-slate-500 text-right" x-text="progressPct() + '% gata'"></div>
  </div>

  <!-- Setări dimensiune label -->
  <div class="bg-white border border-slate-100 rounded-xl shadow-soft p-3 mb-5 flex flex-wrap items-end gap-3">
    <span class="text-sm font-medium text-slate-700 mr-1">Label:</span>
    <div class="min-w-[180px]">
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Titlu (deasupra modelului)</label>
      <input type="text" x-model="labelTitle" @input="saveLabelTitle()" maxlength="100"
        placeholder="ex: Folie Sticlă Securizată"
        class="w-full px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500">
    </div>
    <div>
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Lățime (mm)</label>
      <input type="number" min="10" max="200" step="1" x-model.number="labelW"
        class="w-20 px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500 tabular-nums">
    </div>
    <div>
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Înălțime (mm)</label>
      <input type="number" min="10" max="200" step="1" x-model.number="labelH"
        class="w-20 px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500 tabular-nums">
    </div>
    <div class="flex flex-wrap gap-1.5">
      <template x-for="preset in presets" :key="preset.w + 'x' + preset.h">
        <button @click="labelW = preset.w; labelH = preset.h"
          class="px-2 py-1 text-xs border border-slate-200 rounded hover:bg-slate-50 transition"
          x-text="preset.w + '×' + preset.h"></button>
      </template>
    </div>

    <div class="w-px self-stretch bg-slate-200 mx-1 hidden sm:block"></div>

    <!-- Setări de print -->
    <div>
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Densitate (DPI)</label>
      <select x-model.number="labelOpts.density" @change="saveLabelOpts()"
        class="px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white">
        <option :value="152">152</option>
        <option :value="203">203</option>
        <option :value="300">300</option>
        <option :value="600">600</option>
      </select>
    </div>
    <div>
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Calitate (interpolare)</label>
      <select x-model="labelOpts.interpolation" @change="saveLabelOpts()"
        class="px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white">
        <option value="nearest-neighbor">Net (nearest) — recomandat coduri bare</option>
        <option value="bilinear">Bilinear</option>
        <option value="bicubic">Bicubic (neted)</option>
      </select>
    </div>
    <div>
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Culoare</label>
      <select x-model="labelOpts.colorType" @change="saveLabelOpts()"
        class="px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white">
        <option value="blackwhite">Alb-negru (termal)</option>
        <option value="grayscale">Tonuri de gri</option>
        <option value="color">Color</option>
      </select>
    </div>
    <div>
      <label class="block text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">Rotație</label>
      <select x-model.number="labelOpts.rotation" @change="saveLabelOpts()"
        class="px-2 py-1 text-sm border border-slate-200 rounded focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white">
        <option :value="0">0°</option>
        <option :value="90">90°</option>
        <option :value="180">180°</option>
        <option :value="270">270°</option>
      </select>
    </div>
    <label class="inline-flex items-center gap-2 text-sm text-slate-700 cursor-pointer pb-1">
      <input type="checkbox" x-model="labelOpts.scaleContent" @change="saveLabelOpts()" class="rounded border-slate-300 text-brand-600 focus:ring-brand-500">
      Scalează
    </label>
  </div>

  <template x-if="items.length === 0">
    <div class="bg-white border border-slate-100 rounded-xl shadow-soft p-10 text-center text-slate-400">
      Nicio cantitate de împachetat în această sesiune.
    </div>
  </template>

  <!-- Grupuri de produse legate -->
  <div class="space-y-5">
    <template x-for="g in groups()" :key="g.key">
      <div class="bg-white border border-slate-100 rounded-xl shadow-soft overflow-hidden">
        <div class="px-4 py-2.5 bg-slate-50 border-b border-slate-100 flex items-center justify-between">
          <div class="flex items-center gap-2 text-sm font-semibold text-slate-700">
            <svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>
            <span x-text="g.label"></span>
          </div>
          <span class="text-[11px] text-slate-400" x-text="g.items.filter(i => i.packed).length + '/' + g.items.length"></span>
        </div>
        <ul class="divide-y divide-slate-100">
          <template x-for="(it, idx) in g.items" :key="it.id">
            <li :id="'row-' + it.id"
                class="flex items-center gap-3 px-4 py-3 transition select-none"
                :class="{ 'bg-emerald-50/60': it.packed, 'scan-flash': it._flash }">
              <!-- Checkbox „gata" (toggle complet) -->
              <button type="button" @click="toggle(it)"
                   class="flex-shrink-0 w-6 h-6 rounded-md border-2 flex items-center justify-center transition cursor-pointer"
                   :class="it.packed ? 'bg-emerald-500 border-emerald-500' : 'border-slate-300 bg-white hover:border-emerald-400'"
                   title="Marchează complet împachetat / resetează">
                <svg x-show="it.packed" class="w-4 h-4 text-white" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
              </button>
              <!-- Info produs (nume trunchiat la 60 char) -->
              <div class="flex-1 min-w-0">
                <div class="text-sm leading-snug break-words" :class="it.packed ? 'line-through text-slate-400' : 'text-slate-800'"
                     :title="displayName(it)" x-text="shortName(displayName(it))"></div>
                <div class="text-[11px] text-slate-400 font-mono mt-0.5 flex flex-wrap gap-x-3">
                  <span x-show="it.ean" x-text="'EAN ' + it.ean"></span>
                  <span x-show="it.pnk" x-text="'PNK ' + it.pnk"></span>
                  <span x-show="it.units_per_box" x-text="it.units_per_box + ' buc/cutie'"></span>
                </div>
              </div>
              <!-- Label inline (cod de bare + titlu + model compatibil) -->
              <div class="label flex-shrink-0" :style="`width:${labelW}mm; height:${labelH}mm`">
                <svg :id="'inline-bc-' + it.id"></svg>
                <div class="label-title" x-show="labelTitle" x-text="labelTitle"></div>
                <div class="label-phone" x-text="compatLabel(it) || '—'"></div>
              </div>
              <!-- Împachetate deja (input) / total -->
              <div class="flex-shrink-0 text-right">
                <div class="text-[10px] uppercase tracking-wide text-slate-400 mb-0.5">împachetate</div>
                <div class="inline-flex items-center gap-1">
                  <input type="number" min="0" :max="it.qty" step="1"
                    :value="it.packed_qty"
                    @change="setQty(it, $event.target.value)"
                    @click.stop
                    @keydown.enter.prevent="$event.target.blur()"
                    class="w-16 px-2 py-1 text-sm text-right border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500 tabular-nums"
                    :class="it.packed ? 'border-emerald-300 bg-emerald-50 text-emerald-700 font-semibold' : 'border-slate-200'">
                  <span class="text-sm text-slate-400 tabular-nums">/ <span x-text="it.qty"></span></span>
                </div>
              </div>
              <button type="button" @click="printOne(it)"
                class="flex-shrink-0 inline-flex items-center justify-center w-8 h-8 text-slate-600 hover:text-slate-800 hover:bg-slate-100 rounded-lg transition"
                :title="'Printează ' + copiesFor(it) + ' labeluri (cantitate + 3)'">
                <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
              </button>
              <span x-show="savingIds.includes(it.id)" class="spinner flex-shrink-0"></span>
            </li>
          </template>
        </ul>
      </div>
    </template>
  </div>

  <!-- Zona efectiv printată (fallback browser, off-screen pe ecran) -->
  <div id="singleLabelArea" style="position:fixed; left:-10000px; top:0;">
    <template x-for="i in printCopies" :key="i">
      <div class="label" :style="`width:${labelW}mm; height:${labelH}mm`">
        <svg :id="'print-bc-' + i"></svg>
        <div class="label-title" x-show="labelTitle" x-text="labelTitle"></div>
        <div class="label-phone" x-text="(printItem && compatLabel(printItem)) || '—'"></div>
      </div>
    </template>
  </div>
</div>

<script>
function packingPage({ items, labelW, labelH, qzPrinter, labelTitle, labelOpts, csrf }) {
  return {
    items: (items || []).map(it => ({
      ...it,
      packed: it.packed == 1,
      qty: Number(it.qty) || 0,
      packed_qty: Number(it.packed_qty) || 0,
      _flash: false,
    })),
    csrf,
    savingIds: [],
    // scanare cu pistolul
    scanBuf: '',
    scanLast: 0,
    scanMsg: '',
    scanOk: true,
    scanMsgTimer: null,
    // label
    labelW: labelW || 40,
    labelH: labelH || 30,
    labelTitle: labelTitle || '',
    labelOpts: Object.assign({ density: 203, interpolation: 'nearest-neighbor', rotation: 0, colorType: 'blackwhite', scaleContent: true }, labelOpts || {}),
    printItem: null,       // produsul randat în zona de print (fallback browser)
    printCopies: [],       // [1..N] copii pentru fallback browser
    labelSaveTimer: null,
    optsSaveTimer: null,
    titleSaveTimer: null,
    presets: [{ w: 40, h: 30 }, { w: 50, h: 30 }, { w: 58, h: 40 }, { w: 60, h: 40 }, { w: 100, h: 50 }],
    // QZ Tray
    qzPrinter: qzPrinter || '',
    qzConnected: false,
    qzAvailable: false,
    qzPrinters: [],
    qzBusy: false,

    initLabels() {
      this.updateLabelPageCss();
      this.$nextTick(() => this.renderInlineBarcodes());
      // Salvează dimensiunea în DB (debounced) și actualizează @page la fiecare schimbare.
      this.$watch('labelW', () => { this.updateLabelPageCss(); this.saveLabelSize(); });
      this.$watch('labelH', () => { this.updateLabelPageCss(); this.saveLabelSize(); });
      // Imprimanta e per-PC (localStorage), nu globală — fiecare calculator
      // își reține propria imprimantă.
      try { const lsP = localStorage.getItem('qz_printer'); if (lsP) this.qzPrinter = lsP; } catch (e) {}
      // QZ: încearcă conexiune silențioasă (dacă permisiunea e deja acordată).
      this.qzAvailable = !!(window.QZLabels && QZLabels.available());
      if (this.qzAvailable) {
        QZLabels.listPrinters()
          .then(list => { this.qzPrinters = list; this.qzConnected = QZLabels.isConnected(); })
          .catch(() => { this.qzConnected = false; });
      }
      this.initScan();
    },

    // ---- scanare cu pistolul (acționează ca o tastatură: tastează rapid + Enter) ----
    initScan() {
      window.addEventListener('keydown', (e) => {
        const now = Date.now();
        const gap = now - this.scanLast;
        if (gap > 120) this.scanBuf = '';   // pauză mare → secvență nouă (tastare manuală)
        const fast = gap < 35;
        this.scanLast = now;

        if (e.key === 'Enter') {
          if (this.scanBuf.length >= 4) { e.preventDefault(); this.handleScan(this.scanBuf); }
          this.scanBuf = '';
          return;
        }
        if (e.key.length === 1) {
          this.scanBuf += e.key;
          // Tastare rapidă = pistol → ține caracterele în afara câmpurilor.
          if (fast) {
            e.preventDefault();
            const a = document.activeElement;
            if (a && /^(INPUT|TEXTAREA|SELECT)$/.test(a.tagName)) a.blur();
          }
        }
      });
    },
    handleScan(code) {
      code = (code || '').toString().trim();
      const it = this.items.find(x => (x.ean || '').toString().trim() === code);
      if (!it) { this.showScan('EAN ' + code + ' negăsit în această recepție', false); return; }
      this.flashRow(it);
      this.scrollToRow(it);
      const cur = Number(it.packed_qty) || 0;
      if (cur >= it.qty) {
        this.showScan(this.displayName(it) + ' — deja complet (' + it.qty + ')', false);
        return;
      }
      this.persist(it, cur + 1);
      this.showScan('+1 · ' + this.displayName(it) + ' (' + (cur + 1) + '/' + it.qty + ')', true);
    },
    flashRow(it) {
      it._flash = false;
      this.$nextTick(() => {
        it._flash = true;
        setTimeout(() => { it._flash = false; }, 1000);
      });
    },
    scrollToRow(it) {
      this.$nextTick(() => {
        const el = document.getElementById('row-' + it.id);
        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
      });
    },
    showScan(msg, ok) {
      this.scanMsg = msg; this.scanOk = ok;
      this.beep(ok);
      clearTimeout(this.scanMsgTimer);
      this.scanMsgTimer = setTimeout(() => { this.scanMsg = ''; }, 2500);
    },
    // Beep generat (Web Audio): ton scurt înalt la succes, buzz jos la eroare.
    beep(ok) {
      try {
        if (!window.__scanAudioCtx) window.__scanAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
        const ctx = window.__scanAudioCtx;
        if (ctx.state === 'suspended') ctx.resume();
        const t = ctx.currentTime;
        const play = (freq, start, dur, type) => {
          const o = ctx.createOscillator(), g = ctx.createGain();
          o.type = type; o.frequency.value = freq;
          o.connect(g); g.connect(ctx.destination);
          g.gain.setValueAtTime(0.0001, t + start);
          g.gain.exponentialRampToValueAtTime(0.3, t + start + 0.008);
          g.gain.exponentialRampToValueAtTime(0.0001, t + start + dur);
          o.start(t + start); o.stop(t + start + dur);
        };
        if (ok) {
          play(1046, 0, 0.12, 'sine');          // „bip" scurt, înalt
        } else {
          play(220, 0, 0.18, 'square');         // buzz jos, dublu
          play(180, 0.2, 0.22, 'square');
        }
      } catch (e) { /* audio indisponibil — ignorăm */ }
    },
    async connectQz() {
      this.qzAvailable = !!(window.QZLabels && QZLabels.available());
      if (!this.qzAvailable) { alert('QZ Tray nu este disponibil. Instalează și pornește aplicația, apoi reîncarcă pagina.'); return; }
      this.qzBusy = true;
      try {
        this.qzPrinters = await QZLabels.listPrinters();
        this.qzConnected = QZLabels.isConnected();
      } catch (e) {
        this.qzConnected = false;
        alert('Conectare QZ Tray eșuată: ' + e.message);
      } finally {
        this.qzBusy = false;
      }
    },
    selectPrinter() { try { localStorage.setItem('qz_printer', this.qzPrinter); } catch (e) {} },
    saveLabelOpts() {
      clearTimeout(this.optsSaveTimer);
      this.optsSaveTimer = setTimeout(() => this.persistSetting('fbe_label_opts', JSON.stringify(this.labelOpts)), 400);
    },
    saveLabelTitle() {
      clearTimeout(this.titleSaveTimer);
      this.titleSaveTimer = setTimeout(() => this.persistSetting('fbe_label_title', this.labelTitle), 500);
    },
    // Randează codul de bare inline pentru fiecare produs din tabel.
    renderInlineBarcodes() {
      for (const it of this.items) {
        this.drawBarcode('inline-bc-' + it.id, it.ean);
      }
    },
    async persistSetting(key, val) {
      try {
        const fd = new FormData();
        fd.append('csrf_token', this.csrf);
        fd.append('key', key);
        fd.append('value', val);
        await fetch('<?= url('api/save-setting.php') ?>', { method: 'POST', body: fd, credentials: 'same-origin' });
      } catch (e) { /* preferință UI — ignorăm erorile */ }
    },
    saveLabelSize() {
      clearTimeout(this.labelSaveTimer);
      this.labelSaveTimer = setTimeout(() => {
        this.persistSetting('fbe_label_w', this.labelW);
        this.persistSetting('fbe_label_h', this.labelH);
      }, 400);
    },

    displayName(it) {
      return it.custom_name || it.live_name || it.product_name || ('EAN ' + (it.ean || ''));
    },
    compatLabel(it) {
      return [it.phone_label, it.alt_labels].filter(Boolean).join(', ');
    },
    updateLabelPageCss() {
      if (this.$refs.pageCss) {
        this.$refs.pageCss.textContent =
          '@media print { @page { size: ' + this.labelW + 'mm ' + this.labelH + 'mm; margin: 0; } }';
      }
    },
    // Randează un cod de bare în elementul cu id-ul dat.
    drawBarcode(elId, value) {
      const el = document.getElementById(elId);
      if (!el) return;
      const val = (value || '').toString().trim();
      if (!val) { el.innerHTML = ''; return; }
      let fmt = 'CODE128';
      if (/^\d{13}$/.test(val)) fmt = 'EAN13';
      else if (/^\d{12}$/.test(val)) fmt = 'UPC';
      const opts = { format: fmt, height: 38, width: 1.4, displayValue: true, fontSize: 12, margin: 2 };
      try { JsBarcode(el, val, opts); }
      catch (e) { try { JsBarcode(el, val, { ...opts, format: 'CODE128' }); } catch (_) { el.innerHTML = ''; } }
    },
    // Câte labeluri printăm pentru un produs: cantitatea de împachetat + 3 (de rezervă).
    copiesFor(it) {
      return Math.max(1, (Number(it.qty) || 0) + 3);
    },
    // SVG-ul codului de bare (reutilizează cel inline; altfel îl randează temporar).
    barcodeSvg(it) {
      const el = document.getElementById('inline-bc-' + it.id);
      if (el && el.innerHTML) return el.outerHTML;
      const tmp = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      const val = (it.ean || '').toString().trim();
      if (!val) return '';
      let fmt = 'CODE128';
      if (/^\d{13}$/.test(val)) fmt = 'EAN13';
      else if (/^\d{12}$/.test(val)) fmt = 'UPC';
      try { JsBarcode(tmp, val, { format: fmt, height: 38, width: 1.4, displayValue: true, fontSize: 12, margin: 2 }); }
      catch (e) { try { JsBarcode(tmp, val, { format: 'CODE128', height: 38, width: 1.4, displayValue: true, fontSize: 12, margin: 2 }); } catch (_) { return ''; } }
      return tmp.outerHTML;
    },
    // Print direct al unui produs: qty+3 labeluri. Prin QZ Tray dacă e configurat,
    // altfel cade pe dialogul browserului.
    async printOne(it) {
      if (!it) return;
      const copies = this.copiesFor(it);
      if (this.qzPrinter && window.QZLabels && QZLabels.available()) {
        try {
          const html = QZLabels.buildLabelHtml(this.barcodeSvg(it), this.compatLabel(it), this.labelW, this.labelH, this.labelTitle);
          await QZLabels.printLabel(this.qzPrinter, html, {
            widthMm: this.labelW, heightMm: this.labelH, copies,
            density: this.labelOpts.density,
            interpolation: this.labelOpts.interpolation,
            rotation: this.labelOpts.rotation,
            colorType: this.labelOpts.colorType,
            scaleContent: this.labelOpts.scaleContent,
          });
        } catch (e) {
          alert('Print QZ Tray eșuat: ' + e.message + '\nVerifică dacă aplicația QZ Tray e pornită.');
        }
        return;
      }
      // Fallback browser: randează qty+3 labeluri off-screen și deschide dialogul.
      this.printItem = it;
      this.printCopies = Array.from({ length: copies }, (_, i) => i + 1);
      this.updateLabelPageCss();
      this.$nextTick(() => {
        for (const i of this.printCopies) this.drawBarcode('print-bc-' + i, it.ean);
        setTimeout(() => window.print(), 150);
      });
    },
    shortName(s) {
      s = (s || '').toString();
      return s.length > 60 ? s.slice(0, 60) + '…' : s;
    },
    packedCount() { return this.items.filter(i => i.packed).length; },
    packedPieces() { return this.items.reduce((t, i) => t + (Number(i.packed_qty) || 0), 0); },
    totalPieces()  { return this.items.reduce((t, i) => t + (Number(i.qty) || 0), 0); },
    progressPct() {
      const tot = this.totalPieces();
      if (!tot) return 0;
      return Math.round(this.packedPieces() / tot * 100);
    },
    // Grupare pe phone_series_id (produsele legate), păstrând ordinea de apariție.
    groups() {
      const map = new Map();
      this.items.forEach((it, i) => {
        const key = (it.phone_series_id != null) ? ('g' + it.phone_series_id) : ('solo' + i);
        if (!map.has(key)) {
          map.set(key, { key, first: i, label: it.phone_label || 'Fără model setat', items: [] });
        }
        map.get(key).items.push(it);
      });
      return [...map.values()].sort((a, b) => a.first - b.first);
    },
    // Checkbox „gata": comută între complet (qty) și 0.
    async toggle(it) {
      await this.persist(it, it.packed ? 0 : it.qty);
    },
    async setQty(it, val) {
      let n = parseInt(val, 10);
      if (isNaN(n) || n < 0) n = 0;
      if (n > it.qty) n = it.qty;
      await this.persist(it, n);
    },
    async setAll(val) {
      for (const it of this.items) {
        const target = val ? it.qty : 0;
        if (it.packed_qty !== target) await this.persist(it, target);
      }
    },
    async persist(it, packedQty) {
      if (this.savingIds.includes(it.id)) return;
      const prevQty = it.packed_qty, prevPacked = it.packed;
      it.packed_qty = packedQty;                       // optimist
      it.packed = (it.qty > 0 && packedQty >= it.qty);
      this.savingIds.push(it.id);
      try {
        const fd = new FormData();
        fd.append('csrf_token', this.csrf);
        fd.append('item_id', it.id);
        fd.append('packed_qty', packedQty);
        const r = await fetch('<?= url('api/set-reception-packed.php') ?>', { method: 'POST', body: fd, credentials: 'same-origin' });
        const d = await r.json();
        if (!d.ok) throw new Error(d.error || 'Eroare');
        it.packed_qty = d.packed_qty;                  // valori normalizate de server
        it.packed = d.packed == 1;
      } catch (e) {
        it.packed_qty = prevQty; it.packed = prevPacked; // revert la eroare
        alert('Nu am putut salva: ' + e.message);
      } finally {
        this.savingIds = this.savingIds.filter(x => x !== it.id);
      }
    }
  };
}
</script>

<?php endif; ?>

<?php require __DIR__ . '/includes/footer.php'; ?>
