<?php

declare(strict_types=1);

namespace PpPhp\Core;

/**
 * Server-side PDF generation (headless — no browser).
 *
 * Renders an HTML string to a real PDF using the bundled dompdf library, so PDFs
 * can be produced from anywhere the browser can't reach: emailed attachments,
 * scheduled/cron jobs, queue workers, or an API endpoint.
 *
 * This complements the client-side {@see window.PPpdf} (html2pdf) used for
 * interactive, in-page downloads. Both are available; use whichever fits:
 *   - PPpdf (JS)   → user clicks, downloads in the browser.
 *   - PdfService   → the server builds the PDF (email it, save it, stream it).
 *
 * dompdf and its dependencies are bundled under vendor/ (copied into generated
 * apps by the build). If they are absent, {@see PdfService::available()} returns
 * false and callers can degrade gracefully.
 */
final class PdfService
{
    /** Load the bundled dompdf autoloader once, if it isn't already available. */
    private static function boot(): bool
    {
        if (class_exists(\Dompdf\Dompdf::class)) {
            return true;
        }
        $autoload = constant('BASE_PATH') . '/vendor/autoload.php';
        if (is_file($autoload)) {
            require_once $autoload;
        }
        return class_exists(\Dompdf\Dompdf::class);
    }

    /** True when server-side PDF generation is possible in this app. */
    public static function available(): bool
    {
        return self::boot();
    }

    /**
     * Render an HTML document to PDF bytes.
     *
     * @param string $html Full or partial HTML. Inline styles work best.
     * @param array{orientation?:string,paper?:string,margin?:string,base_path?:string} $opts
     *   orientation: 'portrait'|'landscape'  paper: 'A4'|'letter'|…
     *   margin: any CSS margin value applied via @page (e.g. '15mm' or '20mm 15mm')
     *   base_path: filesystem dir dompdf resolves relative asset paths against
     * @return string PDF binary
     * @throws \RuntimeException when dompdf is unavailable
     */
    public static function fromHtml(string $html, array $opts = []): string
    {
        if (!self::boot()) {
            throw new \RuntimeException('PDF library is not installed (vendor/dompdf missing).');
        }

        $orientation = ($opts['orientation'] ?? 'portrait') === 'landscape' ? 'landscape' : 'portrait';
        $paper       = (string) ($opts['paper'] ?? 'A4');
        $margin      = trim((string) ($opts['margin'] ?? '15mm'));

        // Page margins are set through an injected @page rule (dompdf honours it).
        if ($margin !== '' && stripos($html, '@page') === false) {
            $html = "<style>@page{margin:{$margin};}</style>" . $html;
        }

        $options = new \Dompdf\Options();
        $options->set('isRemoteEnabled', true);     // allow the brand logo / images via URL
        $options->set('isHtml5ParserEnabled', true);
        $options->set('defaultFont', 'DejaVu Sans'); // broad Unicode coverage
        if (!empty($opts['base_path']) && is_dir((string) $opts['base_path'])) {
            $options->set('chroot', (string) $opts['base_path']);
        }

        $dompdf = new \Dompdf\Dompdf($options);
        $dompdf->setPaper($paper, $orientation);
        $dompdf->loadHtml($html, 'UTF-8');
        $dompdf->render();
        return (string) $dompdf->output();
    }

    /**
     * Stream a PDF to the browser as a download (or inline preview).
     * Ends the request.
     */
    public static function stream(string $html, string $filename = 'document', array $opts = []): void
    {
        $pdf = self::fromHtml($html, $opts);
        $name = preg_replace('/[^A-Za-z0-9._-]+/', '-', $filename);
        if (!str_ends_with(strtolower($name), '.pdf')) {
            $name .= '.pdf';
        }
        $disposition = !empty($opts['inline']) ? 'inline' : 'attachment';
        if (!headers_sent()) {
            header('Content-Type: application/pdf');
            header('Content-Disposition: ' . $disposition . '; filename="' . $name . '"');
            header('Content-Length: ' . strlen($pdf));
            header('Cache-Control: private, max-age=0, must-revalidate');
        }
        echo $pdf;
    }

    /**
     * Render a PDF and write it to a file (e.g. to attach to an email or archive).
     *
     * @return string The path written.
     */
    public static function save(string $html, string $path, array $opts = []): string
    {
        $pdf = self::fromHtml($html, $opts);
        $dir = dirname($path);
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }
        file_put_contents($path, $pdf);
        return $path;
    }
}
