<?php

namespace App\Libraries;

class QuotationPdf
{
    private const PAGE_WIDTH = 595;
    private const PAGE_HEIGHT = 842;
    private const LEFT = 46;
    private const RIGHT = 549;

    private $pages = [];
    private $content = "";
    private $y = 0;
    private $documentType = "quotation";

    public function generate(
        array $quotation,
        array $items,
        string $documentType = "quotation"
    ): string {
        $this->documentType =
            $documentType === "invoice" ? "invoice" : "quotation";
        $this->pages = [];
        $this->newPage($quotation);

        $clientName = $quotation["first_name"] . " " . $quotation["last_name"];
        $this->label(
            $this->documentType === "invoice" ? "BILL TO" : "PREPARED FOR",
            self::LEFT,
            $this->y,
            "#ff6846",
        );
        $this->text($clientName, self::LEFT, $this->y - 23, 18, true);
        $this->text($quotation["email"], self::LEFT, $this->y - 42, 9);
        $this->text($quotation["mobile"], self::LEFT, $this->y - 57, 9);
        $this->wrappedText(
            $quotation["address"],
            self::LEFT,
            $this->y - 74,
            270,
            9,
            13,
        );

        $metaX = 380;
        $this->label(
            $this->documentType === "invoice"
                ? "INVOICE DATE"
                : "QUOTATION DATE",
            $metaX,
            $this->y,
            "#6e7a76",
        );
        $this->text(
            date("d M Y", strtotime($quotation["quotation_date"])),
            $metaX,
            $this->y - 18,
            10,
            true,
        );
        $this->label(
            $this->documentType === "invoice" ? "DUE DATE" : "VALID UNTIL",
            $metaX,
            $this->y - 46,
            "#6e7a76",
        );
        $this->text(
            $quotation["valid_until"]
                ? date("d M Y", strtotime($quotation["valid_until"]))
                : ($this->documentType === "invoice"
                    ? "Due on receipt"
                    : "No expiry"),
            $metaX,
            $this->y - 64,
            10,
            true,
        );
        $this->y -= 128;

        $this->itemsHeader();
        foreach ($items as $item) {
            $this->itemRow($quotation, $item);
        }

        $this->ensureSpace(96, $quotation);
        $this->y -= 20;
        $this->fillRect(345, $this->y - 63, 204, 63, "#142522");
        $this->label(
            $this->documentType === "invoice" ? "TOTAL DUE" : "TOTAL COST",
            362,
            $this->y - 24,
            "#9caba6",
        );
        $this->text(
            "INR " . number_format((float) $quotation["total"], 2),
            362,
            $this->y - 48,
            18,
            true,
            "#caff62",
        );
        $this->y -= 90;

        if (!empty($quotation["notes"])) {
            $noteLines = $this->wrap($quotation["notes"], 88);
            $firstNoteChunk = true;
            while ($noteLines) {
                $availableLines = (int) floor(($this->y - 105) / 13);
                if ($availableLines < 2) {
                    $this->newPage($quotation);
                    $availableLines = (int) floor(($this->y - 105) / 13);
                }
                $chunk = array_splice($noteLines, 0, max(2, $availableLines));
                $height = 45 + count($chunk) * 13;
                $this->fillRect(
                    self::LEFT,
                    $this->y - $height,
                    self::RIGHT - self::LEFT,
                    $height,
                    "#f4f1e9",
                );
                $this->label(
                    $firstNoteChunk
                        ? "NOTES & TERMS"
                        : "NOTES & TERMS - CONTINUED",
                    self::LEFT + 16,
                    $this->y - 22,
                    "#ff6846",
                );
                $lineY = $this->y - 42;
                foreach ($chunk as $line) {
                    $this->text(
                        $line,
                        self::LEFT + 16,
                        $lineY,
                        9,
                        false,
                        "#53615d",
                    );
                    $lineY -= 13;
                }
                $this->y -= $height + 12;
                $firstNoteChunk = false;
                if ($noteLines) {
                    $this->newPage($quotation);
                }
            }
        }

        $this->finishPage();
        return $this->buildPdf();
    }

    private function newPage(array $quotation): void
    {
        if ($this->content !== "") {
            $this->finishPage();
        }

        $this->content = "";
        $this->fillRect(0, 752, self::PAGE_WIDTH, 90, "#142522");
        $this->text("Brand", self::LEFT, 791, 22, true, "#ffffff");
        $this->text("Square.", 109, 791, 22, true, "#caff62");
        $this->label(strtoupper($this->documentType), 445, 804, "#ff6846");
        $this->text(
            $quotation["quotation_number"],
            395,
            782,
            12,
            true,
            "#ffffff",
        );
        $this->line(self::LEFT, 738, self::RIGHT, 738, "#d9ddd5");
        $this->y = 710;
    }

    private function finishPage(): void
    {
        if ($this->content !== "") {
            $this->pages[] = $this->content;
            $this->content = "";
        }
    }

    private function itemsHeader(): void
    {
        $this->fillRect(
            self::LEFT,
            $this->y - 28,
            self::RIGHT - self::LEFT,
            28,
            "#142522",
        );
        $this->label("ITEM", 57, $this->y - 18, "#ffffff");
        $this->label("DESCRIPTION", 190, $this->y - 18, "#ffffff");
        $this->label("QTY", 384, $this->y - 18, "#ffffff");
        $this->label("PRICE", 431, $this->y - 18, "#ffffff");
        $this->label("AMOUNT", 495, $this->y - 18, "#ffffff");
        $this->y -= 28;
    }

    private function itemRow(array $quotation, array $item): void
    {
        $nameLines = $this->wrap($item["item_name"], 22);
        $descriptionLines = $this->wrap($item["description"] ?: "-", 36);
        $lineCount = max(count($nameLines), count($descriptionLines), 1);
        $height = max(45, 23 + $lineCount * 12);
        $this->ensureSpace($height + 35, $quotation, true);

        $rowTop = $this->y;
        $textY = $rowTop - 19;
        foreach ($nameLines as $line) {
            $this->text($line, 57, $textY, 9, true);
            $textY -= 12;
        }
        $textY = $rowTop - 19;
        foreach ($descriptionLines as $line) {
            $this->text($line, 190, $textY, 8, false, "#596762");
            $textY -= 12;
        }
        $this->text(
            number_format((float) $item["quantity"], 2),
            384,
            $rowTop - 19,
            8,
        );
        $this->rightText(
            number_format((float) $item["price"], 2),
            482,
            $rowTop - 19,
            8,
        );
        $this->rightText(
            number_format((float) $item["line_total"], 2),
            548,
            $rowTop - 19,
            8,
            true,
        );
        $this->line(
            self::LEFT,
            $rowTop - $height,
            self::RIGHT,
            $rowTop - $height,
            "#e1e5df",
        );
        $this->y -= $height;
    }

    private function ensureSpace(
        int $height,
        array $quotation,
        bool $withItemsHeader = false
    ): void {
        if ($this->y - $height >= 58) {
            return;
        }

        $this->newPage($quotation);
        if ($withItemsHeader) {
            $this->itemsHeader();
        }
    }

    private function wrappedText(
        string $text,
        int $x,
        int $y,
        int $width,
        int $size,
        int $lineHeight
    ): void {
        $maxChars = max(10, (int) floor($width / ($size * 0.52)));
        foreach ($this->wrap($text, $maxChars) as $line) {
            $this->text($line, $x, $y, $size, false, "#53615d");
            $y -= $lineHeight;
        }
    }

    private function wrap(string $text, int $maxChars): array
    {
        $text = preg_replace("/\s+/", " ", trim($this->ascii($text)));
        if ($text === "") {
            return [""];
        }

        return explode("\n", wordwrap($text, $maxChars, "\n", true));
    }

    private function text(
        string $text,
        int $x,
        int $y,
        int $size = 10,
        bool $bold = false,
        string $color = "#142522"
    ): void {
        [$r, $g, $b] = $this->rgb($color);
        $font = $bold ? "F2" : "F1";
        $escaped = $this->escape($this->ascii($text));
        $this->content .= sprintf(
            "%.3F %.3F %.3F rg BT /%s %d Tf 1 0 0 1 %d %d Tm (%s) Tj ET\n",
            $r,
            $g,
            $b,
            $font,
            $size,
            $x,
            $y,
            $escaped,
        );
    }

    private function rightText(
        string $text,
        int $right,
        int $y,
        int $size,
        bool $bold = false
    ): void {
        $width = (int) round(strlen($text) * $size * 0.51);
        $this->text($text, $right - $width, $y, $size, $bold);
    }

    private function label(string $text, int $x, int $y, string $color): void
    {
        $this->text(strtoupper($text), $x, $y, 7, true, $color);
    }

    private function line(
        int $x1,
        int $y1,
        int $x2,
        int $y2,
        string $color
    ): void {
        [$r, $g, $b] = $this->rgb($color);
        $this->content .= sprintf(
            "%.3F %.3F %.3F RG %.2F w %d %d m %d %d l S\n",
            $r,
            $g,
            $b,
            0.7,
            $x1,
            $y1,
            $x2,
            $y2,
        );
    }

    private function fillRect(
        int $x,
        int $y,
        int $width,
        int $height,
        string $color
    ): void {
        [$r, $g, $b] = $this->rgb($color);
        $this->content .= sprintf(
            "%.3F %.3F %.3F rg %d %d %d %d re f\n",
            $r,
            $g,
            $b,
            $x,
            $y,
            $width,
            $height,
        );
    }

    private function rgb(string $hex): array
    {
        $hex = ltrim($hex, "#");
        return [
            hexdec(substr($hex, 0, 2)) / 255,
            hexdec(substr($hex, 2, 2)) / 255,
            hexdec(substr($hex, 4, 2)) / 255,
        ];
    }

    private function ascii(string $text): string
    {
        $converted = iconv("UTF-8", "Windows-1252//TRANSLIT//IGNORE", $text);
        return $converted === false
            ? preg_replace("/[^\x20-\x7E]/", "", $text)
            : $converted;
    }

    private function escape(string $text): string
    {
        return str_replace(
            ["\\", "(", ")", "\r", "\n"],
            ["\\\\", "\\(", "\\)", "", " "],
            $text,
        );
    }

    private function buildPdf(): string
    {
        $pageCount = count($this->pages);
        $objects = [
            1 => "<< /Type /Catalog /Pages 2 0 R >>",
            3 => "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
            4 => "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>",
        ];
        $kids = [];

        foreach ($this->pages as $index => $pageContent) {
            $pageNumber = $index + 1;
            $pageContent .= sprintf(
                "0.431 0.478 0.463 rg BT /F1 7 Tf 1 0 0 1 46 28 Tm (BrandSquare - %s) Tj ET\n",
                ucfirst($this->documentType),
            );
            $pageLabel = "Page " . $pageNumber . " of " . $pageCount;
            $pageContent .= sprintf(
                "0.431 0.478 0.463 rg BT /F1 7 Tf 1 0 0 1 497 28 Tm (%s) Tj ET\n",
                $this->escape($pageLabel),
            );
            $pageObject = 5 + $index * 2;
            $contentObject = $pageObject + 1;
            $kids[] = $pageObject . " 0 R";
            $objects[$pageObject] =
                "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 " .
                self::PAGE_WIDTH .
                " " .
                self::PAGE_HEIGHT .
                "] /Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> /Contents " .
                $contentObject .
                " 0 R >>";
            $objects[$contentObject] =
                "<< /Length " .
                strlen($pageContent) .
                " >>\nstream\n" .
                $pageContent .
                "endstream";
        }

        $objects[2] =
            "<< /Type /Pages /Kids [" .
            implode(" ", $kids) .
            "] /Count " .
            $pageCount .
            " >>";
        ksort($objects);

        $pdf = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
        $offsets = [0];
        foreach ($objects as $number => $object) {
            $offsets[$number] = strlen($pdf);
            $pdf .= $number . " 0 obj\n" . $object . "\nendobj\n";
        }
        $xref = strlen($pdf);
        $maxObject = max(array_keys($objects));
        $pdf .= "xref\n0 " . ($maxObject + 1) . "\n";
        $pdf .= "0000000000 65535 f \n";
        for ($i = 1; $i <= $maxObject; $i++) {
            $pdf .= sprintf("%010d 00000 n \n", $offsets[$i] ?? 0);
        }
        $pdf .=
            "trailer\n<< /Size " .
            ($maxObject + 1) .
            " /Root 1 0 R >>\nstartxref\n" .
            $xref .
            "\n%%EOF";

        return $pdf;
    }
}
