<?php defined('BASEPATH') OR exit('No direct script access allowed');

use Picqer\Barcode\BarcodeGeneratorSVG;

class Barcode
{
    /** @var CI_Controller */
    protected $CI;

    protected $defaults = [
        // Page & margins
        'page_size'          => 'A4',      // e.g., 'A4', 'Letter', or array(mmW, mmH)
        'orientation'        => 'P',       // 'P' or 'L'
        'margin_left_mm'     => 5,
        'margin_right_mm'    => 5,
        'margin_top_mm'      => 5,
        'margin_bottom_mm'   => 5,

        // Grid layout (stickers on sheet)
        'cols'               => 3,
        'rows'               => 8,
        'label_width_mm'     => 70,        // per label
        'label_height_mm'    => 35,        // per label
        'h_gap_mm'           => 0,         // gap between labels horizontally
        'v_gap_mm'           => 0,         // gap between labels vertically

        // Visuals
        'logo_path'          => null,      // absolute path; if set, shown on each label (unless overridden per item)
        'logo_max_w_mm'      => 12,
        'logo_max_h_mm'      => 12,
        'show_value_text'    => true,
        'value_font_pt'      => 9,
        'value_bold'         => true,
        'title_font_pt'      => 8,         // optional top text
        'subtext_font_pt'    => 7,         // optional bottom subtext

        // Barcode (Picqer)
        'barcode_type'       => 'code128', // 'code128', 'ean13', 'ean8', 'upca', 'code39', 'qr'(*)
        'width_factor'       => 2,         // affects barcode width
        'height_px'          => 48,        // barcode height (px inside SVG)
        // (*) For QR, this lib doesn't generate QR; use endroid/qr-code if you need real QR. Stick to code128 for assets.

        // Output
        'filename'           => 'barcodes.pdf',  // default output name
        'temp_dir'           => null,            // e.g., sys_get_temp_dir().'/mpdf'
    ];

    public function __construct(array $params = [])
    {
        $this->CI =& get_instance();
        $this->options = array_replace($this->defaults, $params);

        // Ensure Composer autoload is present (in case composer_autoload is off)
        if (!class_exists('\Mpdf\Mpdf')) {
            $autoload = FCPATH.'vendor/autoload.php';
            if (file_exists($autoload)) require_once $autoload;
        }
        if (!class_exists('\Mpdf\Mpdf')) {
            throw new \RuntimeException('mPDF not found. Install via composer and enable composer_autoload.');
        }
        if (!class_exists('\Picqer\Barcode\BarcodeGeneratorSVG')) {
            throw new \RuntimeException('Picqer BarcodeGenerator not found. Install via composer and enable composer_autoload.');
        }
    }

    /**
     * Generate a PDF.
     * @param array $items  List of barcodes. Each item can be:
     *   - string (the barcode value), or
     *   - array: [
     *       'value' => 'WS-10001',      // required
     *       'title' => 'Laptop',        // optional (top text)
     *       'subtext' => 'Dell 7410',   // optional (small text bottom)
     *       'logo_path' => '/abs/path/logo.png' // optional override
     *     ]
     * @param array $opts   Override defaults (optional)
     * @param string|null $saveTo  If provided, save PDF to file. Else stream inline.
     * @param string $destination  \Mpdf\Output\Destination::INLINE|DOWNLOAD|FILE|STRING_RETURN
     * @return string|null         Returns file path or raw string when applicable, otherwise null.
     */
    public function generate(array $items, array $opts = [], $saveTo = null, $destination = \Mpdf\Output\Destination::INLINE)
    {
        $o = array_replace($this->options, $opts);

        // mPDF config
        $mpdfCfg = [
            'format'        => $o['page_size'],
            'orientation'   => $o['orientation'],
            'margin_left'   => $o['margin_left_mm'],
            'margin_right'  => $o['margin_right_mm'],
            'margin_top'    => $o['margin_top_mm'],
            'margin_bottom' => $o['margin_bottom_mm'],
        ];
        if (!empty($o['temp_dir'])) $mpdfCfg['tempDir'] = $o['temp_dir'];

        $mpdf = new \Mpdf\Mpdf($mpdfCfg);
        $mpdf->shrink_tables_to_fit = 1;

        // Build pages (grid)
        $pages = $this->paginate($items, $o['cols'] * $o['rows']);
        foreach ($pages as $pageIndex => $slice) {
            $html = $this->baseCss($o) . $this->buildGridHtml($slice, $o);
            if ($pageIndex > 0) $mpdf->AddPage();
            $mpdf->WriteHTML($html);
        }

        if ($destination === \Mpdf\Output\Destination::FILE || $saveTo) {
            $path = $saveTo ?: (sys_get_temp_dir() . DIRECTORY_SEPARATOR . $o['filename']);
            $mpdf->Output($path, \Mpdf\Output\Destination::FILE);
            return $path;
        }

        if ($destination === \Mpdf\Output\Destination::STRING_RETURN) {
            return $mpdf->Output('', \Mpdf\Output\Destination::STRING_RETURN);
        }

        // Default: stream inline
        $mpdf->Output($o['filename'], $destination);
        return null;
    }

    protected function paginate(array $arr, int $perPage): array
    {
        if ($perPage <= 0) return [$arr];
        return array_chunk($arr, $perPage);
    }

   protected function baseCss(array $o): string
{
    $gapH = (float)$o['h_gap_mm'];
    $gapV = (float)$o['v_gap_mm'];
    $w = (float)$o['label_width_mm'];
    $h = (float)$o['label_height_mm'];
    $logoMaxW = (float)$o['logo_max_w_mm'];
    $logoMaxH = (float)$o['logo_max_h_mm'];

    $titlePt = (int)$o['title_font_pt'];
    $valuePt = (int)$o['value_font_pt'];
    $subPt   = (int)$o['subtext_font_pt'];
    $valueWeight = !empty($o['value_bold']) ? 'bold' : 'normal';

    $css = <<<'CSS'
<style>
  .grid { width: 100%; border-collapse: separate; border-spacing: {{gapH}}mm {{gapV}}mm; }
  .cell { width: {{w}}mm; height: {{h}}mm; padding: 2mm; border: 0; }
  .label { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden; }
  .row-top { width: 100%; text-align: center; font-size: {{titlePt}}pt; line-height: 1.1; margin-bottom: 1mm; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .logo { max-width: {{logoMaxW}}mm; max-height: {{logoMaxH}}mm; margin-bottom: 1mm; }
  .barcode-wrap { width: 100%; text-align: center; }
  .barcode-svg { width: 100%; height: auto; }
  .human { font-size: {{valuePt}}pt; font-weight: {{valueWeight}}; margin-top: 0.8mm; text-align: center; }
  .subtext { font-size: {{subPt}}pt; margin-top: 0.5mm; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
</style>
CSS;


    return strtr($css, [
        '{{gapH}}'        => (string)$gapH,
        '{{gapV}}'        => (string)$gapV,
        '{{w}}'           => (string)$w,
        '{{h}}'           => (string)$h,
        '{{logoMaxW}}'    => (string)$logoMaxW,
        '{{logoMaxH}}'    => (string)$logoMaxH,
        '{{titlePt}}'     => (string)$titlePt,
        '{{valuePt}}'     => (string)$valuePt,
        '{{subPt}}'       => (string)$subPt,
        '{{valueWeight}}' => $valueWeight,
    ]);
}


    protected function buildGridHtml(array $items, array $o): string
    {
        $cols = (int)$o['cols'];
        $rows = (int)$o['rows'];

        $cells = [];
        foreach ($items as $it) {
            $cells[] = $this->renderLabelCell($it, $o);
        }

        // Fill remaining cells on the last page so the grid is complete
        $target = $cols * $rows;
        while (count($cells) < $target) $cells[] = '<td class="cell"></td>';

        // Build table rows
        $out = '<table class="grid">';
        for ($r = 0; $r < $rows; $r++) {
            $out .= '<tr>';
            for ($c = 0; $c < $cols; $c++) {
                $idx = ($r * $cols) + $c;
                $out .= $cells[$idx] ?? '<td class="cell"></td>';
            }
            $out .= '</tr>';
        }
        $out .= '</table>';

        return $out;
    }

   protected function renderLabelCell($item, array $o): string
{
    // Normalize item
    if (is_string($item)) {
        $value   = $item;
        $title   = '';
        $subtext = '';
        $logo    = $o['logo_path'] ?? null;
    } else {
        $value   = (string)($item['value'] ?? '');
        $title   = isset($item['title'])   ? (string)$item['title']   : '';
        $subtext = isset($item['subtext']) ? (string)$item['subtext'] : '';
        $logo    = !empty($item['logo_path']) ? $item['logo_path'] : ($o['logo_path'] ?? null);
    }

    if ($value === '') {
        return '<td class="cell"></td>';
    }

    // Create SVG barcode
    $type        = $this->mapType($o['barcode_type'] ?? 'code128');
    $widthFactor = max(1.0, (float)($o['width_factor'] ?? 2));
    $heightPx    = max(24,  (int)($o['height_px'] ?? 48));

    // For very long values, shrink widthFactor a bit (Code128)
    $len = strlen($value);
    if ($type === \Picqer\Barcode\BarcodeGeneratorSVG::TYPE_CODE_128) {
        if ($len > 24) $widthFactor = max(1.0, $widthFactor - 0.8);
        elseif ($len > 18) $widthFactor = max(1.0, $widthFactor - 0.5);
    }

    $generator = new \Picqer\Barcode\BarcodeGeneratorSVG();
    $svg = $generator->getBarcode($value, $type, (float)$widthFactor, (int)$heightPx);

    // 🔧 Strip XML/DOCTYPE headers that would print as text
    $svg = preg_replace('~^\s*<\?xml[^>]*\?>\s*~i', '', $svg);
    $svg = preg_replace('~^\s*<!DOCTYPE[^>]*>\s*~i', '', $svg);
    $svg = trim($svg);

    // Ensure the root <svg> carries our CSS class
    if (strpos($svg, '<svg') === 0) {
        if (preg_match('/^<svg\b[^>]*\bclass="/i', $svg)) {
            $svg = preg_replace('/^<svg\b([^>]*\bclass=")([^"]*)"/i', '<svg$1$2 barcode-svg"', $svg, 1);
        } else {
            $svg = preg_replace('/^<svg\b/i', '<svg class="barcode-svg"', $svg, 1);
        }
    }

    // Optional logo
    $logoHtml = '';
    if ($logo && is_file($logo)) {
        $src = $this->pathToDataUri($logo);
        if ($src) $logoHtml = '<img class="logo" src="'.$src.'" alt="Logo" />';
    }

    // Safe text
    $safeTitle   = $title   !== '' ? '<div class="row-top">'.htmlspecialchars($title,   ENT_QUOTES, 'UTF-8').'</div>' : '';
    $safeValue   = htmlspecialchars($value,   ENT_QUOTES, 'UTF-8');
    $safeSubtext = $subtext !== '' ? '<div class="subtext">'.htmlspecialchars($subtext, ENT_QUOTES, 'UTF-8').'</div>' : '';
    $humanHtml   = !empty($o['show_value_text']) ? '<div class="human">'.$safeValue.'</div>' : '';

    // Build cell
    $html = <<<HTML
<td class="cell">
  <div class="label">
    {$safeTitle}
    {$logoHtml}
    <div class="barcode-wrap">{$svg}</div>
    {$humanHtml}
    {$safeSubtext}
  </div>
</td>
HTML;

    return $html;
}

  protected function mapType(string $t): string
    {
        $t = strtolower(trim($t));
        switch ($t) {
            case 'code128': return BarcodeGeneratorSVG::TYPE_CODE_128;
            case 'code39':  return BarcodeGeneratorSVG::TYPE_CODE_39;
            case 'ean13':   return BarcodeGeneratorSVG::TYPE_EAN_13;
            case 'ean8':    return BarcodeGeneratorSVG::TYPE_EAN_8;
            case 'upca':
            case 'upc-a':   return BarcodeGeneratorSVG::TYPE_UPC_A;
            // Picqer doesn't generate QR; if someone passes 'qr', fall back:
            case 'qr':      return BarcodeGeneratorSVG::TYPE_CODE_128;
            default:        return BarcodeGeneratorSVG::TYPE_CODE_128;
        }
    }
    protected function pathToDataUri(string $path): string
    {
        $mime = $this->guessMime($path);
        $data = @file_get_contents($path);
        if ($data === false) return '';
        return 'data:'.$mime.';base64,'.base64_encode($data);
    }

    protected function guessMime(string $path): string
    {
        $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
        if (in_array($ext, ['jpg','jpeg'])) return 'image/jpeg';
        if ($ext === 'png') return 'image/png';
        if ($ext === 'gif') return 'image/gif';
        return 'application/octet-stream';
    }
}
