<?php
/**
 * SimplePDF - Lightweight pure-PHP PDF generator
 * No external libraries needed. Generates proper PDF binary.
 * Supports: text, lines, rectangles, basic tables for invoices/quotations.
 */
class SimplePDF {
    private $pages = [];
    private $currentPage = 0;
    private $objects = [];
    private $fonts = [];
    private $pageWidth  = 595.28; // A4
    private $pageHeight = 841.89;
    private $margin = 40;
    private $y;
    private $lineHeight = 14;

    // Colors (r,g,b 0-255)
    private $primaryColor   = [29, 78, 74];   // #1D4E4A
    private $textColor      = [43, 42, 40];   // #2B2A28
    private $mutedColor     = [117, 113, 106]; // #75716A
    private $borderColor    = [224, 219, 206]; // #E0DBCE
    private $successColor   = [59, 109, 17];   // #3B6D11
    private $dangerColor    = [163, 45, 45];   // #A32D2D

    public function __construct() {
        $this->addPage();
    }

    public function addPage() {
        $this->pages[] = '';
        $this->currentPage = count($this->pages) - 1;
        $this->y = $this->pageHeight - $this->margin;
    }

    private function setColor($r, $g, $b, $fill = false) {
        $op = $fill ? 'rg' : 'RG';
        $this->write(sprintf("%.3f %.3f %.3f $op\n", $r/255, $g/255, $b/255));
    }

    private function write($data) {
        $this->pages[$this->currentPage] .= $data;
    }

    private function text($x, $y, $text, $size = 10, $bold = false, $color = null) {
        $font = $bold ? 'F2' : 'F1';
        if ($color) {
            $this->setColor($color[0], $color[1], $color[2]);
        } else {
            $this->setColor($this->textColor[0], $this->textColor[1], $this->textColor[2]);
        }
        $text = $this->escapeText($text);
        $this->write("BT /$font $size Tf $x " . $y . " Td ($text) Tj ET\n");
    }

    private function escapeText($text) {
        $text = mb_convert_encoding($text, 'ISO-8859-1', 'UTF-8');
        return str_replace(['\\','(',')',"\n"], ['\\\\','\\(','\\)',''], $text);
    }

    private function line($x1, $y1, $x2, $y2, $width = 0.5, $color = null) {
        if ($color) {
            $this->setColor($color[0], $color[1], $color[2]);
        } else {
            $this->setColor($this->borderColor[0], $this->borderColor[1], $this->borderColor[2]);
        }
        $this->write("$width w $x1 $y1 m $x2 $y2 l S\n");
    }

    private function rect($x, $y, $w, $h, $fillColor = null, $strokeColor = null) {
        if ($fillColor) {
            $this->setColor($fillColor[0], $fillColor[1], $fillColor[2], true);
            $this->write("$x $y $w $h re f\n");
        }
        if ($strokeColor) {
            $this->setColor($strokeColor[0], $strokeColor[1], $strokeColor[2]);
            $this->write("0.5 w $x $y $w $h re S\n");
        }
    }

    private function checkPageBreak($needed = 20) {
        if ($this->y - $needed < $this->margin + 20) {
            $this->addPage();
        }
    }

    // ── Public API ────────────────────────────────────────────

    public function addHeader($companyName, $docTitle, $docNo, $date, $validUntil = null) {
        $y = $this->y;
        // Company name
        $this->text($this->margin, $y, $companyName, 18, true, $this->primaryColor);
        // Doc title on right
        $this->text(420, $y, $docTitle, 16, true, $this->primaryColor);
        $y -= 18;
        $this->text(420, $y, $docNo, 11, false, $this->mutedColor);
        $y -= 14;
        $this->text(420, $y, 'Date: ' . $date, 9, false, $this->mutedColor);
        if ($validUntil) {
            $y -= 12;
            $this->text(420, $y, 'Valid Until: ' . $validUntil, 9, false, $this->mutedColor);
        }
        $this->y = $y - 8;
        $this->line($this->margin, $this->y, $this->pageWidth - $this->margin, $this->y, 1, $this->primaryColor);
        $this->y -= 12;
    }

    public function addCompanyDetails($address, $gstin, $phone, $email) {
        $y = $this->y;
        if ($address) { $this->text($this->margin, $y, $address, 8, false, $this->mutedColor); $y -= 11; }
        if ($gstin)   { $this->text($this->margin, $y, 'GSTIN: ' . $gstin, 8, false, $this->mutedColor); $y -= 11; }
        if ($phone)   { $this->text($this->margin, $y, 'Phone: ' . $phone, 8, false, $this->mutedColor); $y -= 11; }
        $this->y = $y - 8;
    }

    public function addPartyBox($label, $name, $address, $city, $state, $phone, $gstin = '') {
        $y = $this->y;
        $this->text($this->margin, $y, strtoupper($label), 8, true, $this->mutedColor);
        $y -= 13;
        $this->text($this->margin, $y, $name, 11, true);
        $y -= 13;
        if ($address) { $this->text($this->margin, $y, $address, 9, false, $this->mutedColor); $y -= 11; }
        if ($city || $state) { $this->text($this->margin, $y, trim("$city $state"), 9, false, $this->mutedColor); $y -= 11; }
        if ($phone) { $this->text($this->margin, $y, 'Phone: ' . $phone, 9, false, $this->mutedColor); $y -= 11; }
        if ($gstin) { $this->text($this->margin, $y, 'GSTIN: ' . $gstin, 9, false, $this->mutedColor); $y -= 11; }
        $this->y = $y - 6;
        $this->line($this->margin, $this->y, $this->pageWidth - $this->margin, $this->y, 0.5);
        $this->y -= 10;
    }

    public function addSectionTitle($title) {
        $this->checkPageBreak(20);
        $this->y -= 4;
        $this->text($this->margin, $this->y, $title, 10, true, $this->primaryColor);
        $this->y -= 4;
        $this->line($this->margin, $this->y, $this->pageWidth - $this->margin, $this->y, 1, $this->primaryColor);
        $this->y -= 12;
    }

    public function addTableHeader($cols) {
        // cols = [['label'=>'', 'width'=>80, 'align'=>'L'], ...]
        $this->checkPageBreak(25);
        $rowH = 18;
        $this->rect($this->margin, $this->y - $rowH + 4, $this->pageWidth - 2*$this->margin, $rowH, $this->primaryColor);
        $x = $this->margin + 4;
        foreach ($cols as $col) {
            $lx = $col['align'] === 'R' ? $x + $col['width'] - 4 : $x;
            $this->text($lx, $this->y - 11, $col['label'], 8, true, [255,255,255]);
            $x += $col['width'];
        }
        $this->y -= $rowH + 2;
        return $cols;
    }

    private $tableRowOdd = true;
    
    public function addTableRow($cols, $values, $bold = false) {
        $this->checkPageBreak(18);
        $rowH = 16;
        if ($this->tableRowOdd) {
            $this->rect($this->margin, $this->y - $rowH + 4, $this->pageWidth - 2*$this->margin, $rowH, [251,250,246]);
        }
        $this->tableRowOdd = !$this->tableRowOdd;
        $x = $this->margin + 4;
        foreach ($cols as $i => $col) {
            $val = $values[$i] ?? '';
            $lx  = $col['align'] === 'R' ? $x + $col['width'] - 4 : $x;
            $this->text($lx, $this->y - 10, (string)$val, 8, $bold);
            $x += $col['width'];
        }
        $this->line($this->margin, $this->y - $rowH + 3, $this->pageWidth - $this->margin, $this->y - $rowH + 3, 0.3);
        $this->y -= $rowH;
    }

    public function addTotalsBox($rows) {
        // rows = [['label'=>'', 'value'=>'', 'bold'=>false], ...]
        $this->checkPageBreak(count($rows) * 16 + 10);
        $this->y -= 6;
        $boxW = 200;
        $boxX = $this->pageWidth - $this->margin - $boxW;
        foreach ($rows as $i => $row) {
            $isLast = ($i === count($rows) - 1);
            if ($isLast) {
                $this->rect($boxX - 4, $this->y - 14, $boxW + 8, 18, $this->primaryColor);
                $this->text($boxX, $this->y - 11, $row['label'], 10, true, [255,255,255]);
                $this->text($boxX + $boxW - 4, $this->y - 11, $row['value'], 10, true, [255,255,255]);
            } else {
                $this->line($boxX, $this->y - 13, $boxX + $boxW, $this->y - 13, 0.3);
                $this->text($boxX, $this->y - 10, $row['label'], 9, $row['bold'] ?? false, $this->mutedColor);
                $this->text($boxX + $boxW - 4, $this->y - 10, $row['value'], 9, $row['bold'] ?? false);
            }
            $this->y -= 16;
        }
    }

    public function addNote($label, $text) {
        $this->checkPageBreak(20);
        $this->y -= 8;
        $this->text($this->margin, $this->y, $label . ': ', 9, true);
        $this->text($this->margin + 70, $this->y, $text, 9, false, $this->mutedColor);
        $this->y -= 12;
    }

    public function addFooter($text) {
        $y = $this->margin + 10;
        $this->line($this->margin, $y + 12, $this->pageWidth - $this->margin, $y + 12, 0.5);
        $this->text($this->margin, $y, $text, 8, false, $this->mutedColor);
    }

    // ── Generate PDF binary ───────────────────────────────────
    public function output($filename = 'document.pdf', $dest = 'D') {
        $pdf = $this->buildPDF();
        if ($dest === 'D') {
            header('Content-Type: application/pdf');
            header('Content-Disposition: attachment; filename="' . $filename . '"');
            header('Content-Length: ' . strlen($pdf));
            echo $pdf;
            exit;
        } elseif ($dest === 'S') {
            return $pdf;
        } elseif ($dest === 'F') {
            file_put_contents($filename, $pdf);
            return $filename;
        }
    }

    private function buildPDF() {
        $objects = [];
        $offsets = [];
        $n = 0;

        // Object 1: Catalog
        $objects[++$n] = "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
        // Object 2: Pages (placeholder, updated after)
        $pageObjNums = [];

        // Object 3: Font Helvetica
        $objects[++$n] = "3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n";
        $n++;
        // Object 4: Font Helvetica-Bold
        $objects[$n]   = "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>\nendobj\n";

        // Add page content streams
        foreach ($this->pages as $pi => $pageContent) {
            $stream = $pageContent;
            $streamLen = strlen($stream);

            $contentObjN = ++$n;
            $objects[$contentObjN] = "$contentObjN 0 obj\n<< /Length $streamLen >>\nstream\n$stream\nendstream\nendobj\n";

            $pageObjN = ++$n;
            $pageObjNums[] = $pageObjN;
            $objects[$pageObjN] = "$pageObjN 0 obj\n<< /Type /Page /Parent 2 0 R "
                . "/MediaBox [0 0 {$this->pageWidth} {$this->pageHeight}] "
                . "/Contents $contentObjN 0 R "
                . "/Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> >>\nendobj\n";
        }

        // Object 2: Pages dictionary
        $kids = implode(' ', array_map(function($n) { return "$n 0 R"; }, $pageObjNums));
        $count = count($pageObjNums);
        $objects[2] = "2 0 obj\n<< /Type /Pages /Kids [$kids] /Count $count >>\nendobj\n";

        // Build PDF
        $pdf  = "%PDF-1.4\n";
        foreach ($objects as $num => $obj) {
            $offsets[$num] = strlen($pdf);
            $pdf .= $obj;
        }

        // Cross-reference table
        $xrefOffset = strlen($pdf);
        $objCount = max(array_keys($objects)) + 1;
        $pdf .= "xref\n0 $objCount\n";
        $pdf .= "0000000000 65535 f \n";
        for ($i = 1; $i < $objCount; $i++) {
            if (isset($offsets[$i])) {
                $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
            } else {
                $pdf .= "0000000000 65535 f \n";
            }
        }
        $pdf .= "trailer\n<< /Size $objCount /Root 1 0 R >>\n";
        $pdf .= "startxref\n$xrefOffset\n%%EOF\n";

        return $pdf;
    }
}