<?php

namespace App\Controllers\View;

use App\Controllers\BaseController;
use CodeIgniter\HTTP\ResponseInterface;
use App\Models\UserModel;
use App\Models\LateSlipLogModel;
use App\Models\ConfigurationModel;
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;


// ESC/POS
use Mike42\Escpos\Printer;
use Mike42\Escpos\EscposImage;
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
use Mike42\Escpos\PrintConnectors\UsbPrintConnector;
use Dompdf\Dompdf;
use Dompdf\Options;

class SlipPrinterController extends BaseController
{
    protected $configModel;
    protected $semester;
    protected $schoolYear;

    public function __construct()
    {
        helper(['form', 'url']);
        $this->configModel = new ConfigurationModel();
        $this->semester = $this->configModel->getConfig('semester');
        $this->schoolYear = $this->configModel->getConfig('school_year');
    }
    /**
     * POST /slips/print
     * Expected fields (POST): school_year, student_name, date, time_in, grade, reason, admin_name
     * You can wire this to your own form/data source; all fields are strings.
     */
    public function print(): ResponseInterface
    {
        $req = $this->request;

        $data = [
            'school_year' => trim((string) $req->getVar('school_year') ?: ''),
            'student_name' => trim((string) $req->getVar('student_name') ?: ''),
            'date'        => trim((string) $req->getVar('date') ?: date('m/d/Y')),
            'time_in'     => trim((string) $req->getVar('time_in') ?: date('h:i A')),
            'grade'       => trim((string) $req->getVar('grade') ?: ''),
            'reason'      => trim((string) $req->getVar('reason') ?: ''),
            'admin_name'  => trim((string) $req->getVar('admin_name') ?: ''),
        ];

        // Always prefer current logged-in user's full name from DB
        $adminFromDb = $this->currentAdminName();
        if ($adminFromDb !== '') {
            $data['admin_name'] = $adminFromDb;
        }

        // Validate minimal fields
        if ($data['student_name'] === '') {
            return $this->response->setStatusCode(422)->setJSON(['error' => 'Student name is required.']);
        }

        try {
            // Persist a log (best-effort) before generating PDF
            try {
                $printedBy = (int) (session()->get('user_id') ?? 0) ?: null;
                $cfg = new ConfigurationModel();
                $semester = (string)($cfg->getConfig('semester') ?? '');
                $logData = [
                    'school_year'  => $data['school_year'],
                    'semester'     => $semester,
                    'student_name' => $data['student_name'],
                    'slip_date'    => $this->toDbDate($data['date']),
                    'time_in'      => $this->toDbTime($data['time_in']),
                    'grade'        => $data['grade'],
                    'reason'       => $data['reason'],
                    'admin_name'   => $data['admin_name'],
                ];
                (new LateSlipLogModel())->logSlip($logData, $printedBy);
            } catch (\Throwable $e) {
                log_message('error', 'Late slip log insert failed: ' . $e->getMessage());
            }

            // Generate and stream a PDF instead of direct printer output
            return $this->renderSlipPdfResponse($data);
        } catch (\Throwable $e) {
            log_message('error', 'Late slip PDF generation failed: ' . $e->getMessage());
            return $this->response->setStatusCode(500)->setJSON([
                'error'   => 'Printing failed.',
                'details' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Show or print preview of late slips.
     */
    public function preview(): ResponseInterface
    {
        $req = $this->request;

        try {
            // ---- Step 1: Resolve current school year ----
            $currentYear = trim((string) ($this->schoolYear ?? ''));
            if ($currentYear === '') {
                $latest = (new LateSlipLogModel())
                    ->select('school_year')
                    ->orderBy('id', 'DESC')
                    ->first();
                $currentYear = $latest['school_year'] ?? (date('Y') . '-' . (date('Y') + 1));
            }

            $selectedYear = trim((string) $req->getVar('school_year') ?: $currentYear);
            $selectedSemester = strtolower(trim((string) $req->getVar('semester') ?: ''));

            $data = [
                'school_year'  => $selectedYear,
                'student_name' => trim((string) $req->getVar('student_name') ?: ''),
                'date'         => trim((string) $req->getVar('date') ?: date('m/d/Y')),
                'time_in'      => trim((string) $req->getVar('time_in') ?: date('h:i A')),
                'grade'        => trim((string) $req->getVar('grade') ?: ''),
                'reason'       => trim((string) $req->getVar('reason') ?: ''),
                'admin_name'   => trim((string) $req->getVar('admin_name') ?: ''),
            ];

            // Prefer logged-in admin name
            $adminFromDb = $this->currentAdminName();
            if ($adminFromDb !== '') {
                $data['admin_name'] = $adminFromDb;
            }

            // ---- Handle GET (HTML preview) ----
            if (strtolower($req->getMethod()) === 'get') {
                $rows = [];
                $logModel = new LateSlipLogModel();
                $builder = $logModel;

                // Filter by school year if not empty
                if ($selectedYear !== '') {
                    $builder->where('school_year', $selectedYear);
                }

                // Normalize and apply semester filter
                $hasFilter = false;
                if ($selectedSemester !== '') {
                    $hasFilter = true;
                    if ($selectedSemester === 'fall') {
                        $builder->groupStart()
                            ->where('semester', 'fall')
                            ->orWhere('semester', '1')
                            ->orWhere('semester', 1)
                            ->groupEnd();
                    } elseif ($selectedSemester === 'spring') {
                        $builder->groupStart()
                            ->where('semester', 'spring')
                            ->orWhere('semester', '2')
                            ->orWhere('semester', 2)
                            ->groupEnd();
                    }
                }

                // ---- Execute query ----
                $logs = $builder->orderBy('id', 'DESC')->findAll(50);

                // 🟢 Only use fallback when *no filters are selected*
                if (empty($logs) && !$hasFilter && $selectedYear === '') {
                    $logs = (new LateSlipLogModel())->orderBy('id', 'DESC')->findAll(50);
                }

                // ---- Format rows ----
                foreach ($logs as $r) {
                    $dispDate = '';
                    $slipDateRaw = trim((string)($r['slip_date'] ?? ''));
                    if ($slipDateRaw !== '' && $slipDateRaw !== '0000-00-00') {
                        $ts = strtotime($slipDateRaw);
                        $dispDate = $ts ? date('m/d/Y', $ts) : '';
                    }
                    if ($dispDate === '') {
                        $printedAt = trim((string)($r['printed_at'] ?? ''));
                        if ($printedAt !== '') {
                            try {
                                $dispDate = local_date($printedAt, 'm/d/Y');
                            } catch (\Throwable $e) {
                                $ts = strtotime($printedAt);
                                $dispDate = $ts ? date('m/d/Y', $ts) : '';
                            }
                        }
                    }

                    $dispTime = '';
                    if (!empty($r['time_in'])) {
                        $t = strtotime($r['time_in']) ?: strtotime('today ' . $r['time_in']);
                        $dispTime = $t ? date('h:i A', $t) : '';
                    }

                    $rows[] = [
                        'school_year'  => (string)($r['school_year'] ?? ''),
                        'semester'     => (string)($r['semester'] ?? ''),
                        'student_name' => (string)($r['student_name'] ?? ''),
                        'date'         => $dispDate,
                        'time_in'      => $dispTime,
                        'grade'        => (string)($r['grade'] ?? ''),
                        'reason'       => (string)($r['reason'] ?? ''),
                        'admin_name'   => (string)($r['admin_name'] ?? ''),
                    ];
                }

                // ---- Render the view ----
                $html = view('slips/preview_list', [
                    'rows' => $rows,
                    'school_year' => $selectedYear,
                    'semester' => $selectedSemester,
                ]);

                return $this->response
                    ->setStatusCode(200)
                    ->setHeader('Content-Type', 'text/html; charset=UTF-8')
                    ->setBody($html);
            }

            // ---- POST mode (JSON preview for printer) ----
            $cfg = $this->printerConfig();
            $lineWidth = (int) ($cfg['chars_per_line'] ?? 48);
            $feedLines = (int) ($cfg['feed_lines'] ?? 3);

            $header = "Al Rahma School at ISGL\nSchool Year: {$data['school_year']}\n\n";
            $body   = implode("\n", $this->buildSlipLines($data, $lineWidth));
            $footer = str_repeat("\n", $feedLines);
            $full   = $header . $body . $footer;

            return $this->response->setJSON([
                'ok'     => true,
                'text'   => $full,
                'width'  => $lineWidth,
            ]);
        } catch (\Throwable $e) {
            log_message('error', 'Late slip preview failed: ' . $e->getMessage());
            return $this->response->setStatusCode(500)->setJSON([
                'ok'      => false,
                'error'   => 'Preview failed.',
                'details' => $e->getMessage(),
            ]);
        }
    }

    /**
     * POST /administrator/late_slips/reprint/{id}
     * Reprint a late slip from a saved log row, then redirect back with status.
     */
    public function reprint($id = null): ResponseInterface
    {
        $id = (int) ($id ?? 0);
        if ($id <= 0) {
            return redirect()->back()->with('error', 'Invalid slip id.');
        }

        $row = null;
        try {
            $row = (new LateSlipLogModel())->find($id);
        } catch (\Throwable $e) {
            $row = null;
        }
        if (!$row) {
            return redirect()->back()->with('error', 'Slip not found.');
        }

        // Map DB row to print payload
        $dateDisplay = '';
        if (!empty($row['slip_date'])) {
            $ts = strtotime($row['slip_date']);
            $dateDisplay = $ts ? date('m/d/Y', $ts) : '';
        }
        if ($dateDisplay === '') $dateDisplay = date('m/d/Y');

        $timeDisplay = '';
        if (!empty($row['time_in'])) {
            $ts = strtotime($row['time_in']);
            if ($ts === false) {
                // Try combining with today
                $ts = strtotime('today ' . $row['time_in']);
            }
            $timeDisplay = $ts ? date('h:i A', $ts) : '';
        }
        if ($timeDisplay === '') $timeDisplay = date('h:i A');

        $data = [
            'school_year' => (string) ($row['school_year'] ?? ''),
            'student_name' => (string) ($row['student_name'] ?? ''),
            'date'        => $dateDisplay,
            'time_in'     => $timeDisplay,
            'grade'       => (string) ($row['grade'] ?? ''),
            'reason'      => (string) ($row['reason'] ?? ''),
            'admin_name'  => (string) ($row['admin_name'] ?? ''),
        ];

        // Always prefer current logged-in user's full name from DB
        $adminFromDb = $this->currentAdminName();
        if ($adminFromDb !== '') {
            $data['admin_name'] = $adminFromDb;
        }

        try {
            // Format lines and print
            $lines = $this->buildSlipLines($data);
            $printer = $this->makePrinter();

            $printer->setJustification(Printer::JUSTIFY_CENTER);
            $printer->setEmphasis(true);
            $printer->text("Al Rahma School at ISGL\n");
            $printer->setEmphasis(false);
            $printer->text("School Year: {$data['school_year']}\n");
            $printer->feed();

            $printer->setJustification(Printer::JUSTIFY_LEFT);
            foreach ($lines as $ln) {
                $printer->text($ln . "\n");
            }

            $printer->feed((int) ($this->printerConfig()['feed_lines'] ?? 3));
            $printer->cut();
            $printer->close();

            // Log again (best-effort) for audit trail
            try {
                $printedBy = (int) (session()->get('user_id') ?? 0) ?: null;
                $cfg = new ConfigurationModel();
                $semester = (string)($cfg->getConfig('semester') ?? '');
                $logData = [
                    'school_year'  => $data['school_year'],
                    'semester'     => $semester,
                    'student_name' => $data['student_name'],
                    'slip_date'    => $this->toDbDate($data['date']),
                    'time_in'      => $this->toDbTime($data['time_in']),
                    'grade'        => $data['grade'],
                    'reason'       => $data['reason'],
                    'admin_name'   => $data['admin_name'],
                ];
                (new LateSlipLogModel())->logSlip($logData, $printedBy);
            } catch (\Throwable $e) {
                log_message('error', 'Late slip reprint log insert failed: ' . $e->getMessage());
            }

            return redirect()->back()->with('success', 'Late slip sent to printer.');
        } catch (\Throwable $e) {
            return redirect()->back()->with('error', 'Reprint failed: ' . $e->getMessage());
        }
    }

    /**
     * Build monospaced slip lines for an 80mm (typically 48 chars) receipt.
     *
     * Layout required:
     * Al Rahma School at ISGL "SchoolYear"
     * Student Name ___________________________
     * Date ______________ Time In ______________
     * Grade _________________________________
     * Reason _________________________________
     * _______________________________________
     * Admin Name ____________________________
     */
    private function buildSlipLines(array $data, ?int $lineWidth = null): array
    {
        // Choose a sane default for 58mm rolls (~24–32 chars). Fall back to env config.
        $cfgWidth = (int) ($this->printerConfig()['chars_per_line'] ?? 0);
        $w = $lineWidth ?? ($cfgWidth > 0 ? $cfgWidth : 32);

        $lines = [];
        $lines[] = $this->fitLine('Student Name: ' . (string)($data['student_name'] ?? ''), $w);
        $lines[] = $this->fitLine('Date: ' . (string)($data['date'] ?? '') . '  ' . 'Time In: ' . (string)($data['time_in'] ?? ''), $w);
        $lines[] = $this->fitLine('Grade: ' . (string)($data['grade'] ?? ''), $w);
        $lines[] = $this->fitLine('Reason: ' . (string)($data['reason'] ?? ''), $w);
        $lines[] = $this->fitLine('Admin: ' . (string)($data['admin_name'] ?? ''), $w);

        return $lines;
    }

    /**
     * Create and return a configured Mike42\Escpos\Printer instance.
     */
    private function makePrinter(): Printer
    {
        $cfg  = $this->printerConfig();
        $mode = strtolower((string) ($cfg['mode'] ?? 'network'));

        if ($mode === 'windows') {
            if (stripos(PHP_OS_FAMILY, 'Windows') !== false) {
                $name = (string) ($cfg['windows_name'] ?? 'POS-80');
                $connector = new \Mike42\Escpos\PrintConnectors\WindowsPrintConnector($name);
                return new Printer($connector);
            }
            log_message('warning', 'PRINTER_MODE=windows on non-Windows OS; falling back to network.');
            $mode = 'network';
        }

        if ($mode === 'usb') {
            if (stripos(PHP_OS_FAMILY, 'Windows') !== false) {
                $name = (string) ($cfg['windows_name'] ?? 'POS-80');
                $connector = new \Mike42\Escpos\PrintConnectors\WindowsPrintConnector($name);
                return new Printer($connector);
            }
            $vidStr = (string) ($cfg['usb_vid'] ?? '');
            $pidStr = (string) ($cfg['usb_pid'] ?? '');
            if ($vidStr === '' || $pidStr === '') {
                throw new \RuntimeException('USB mode requires PRINTER_USB_VID and PRINTER_USB_PID');
            }
            $vid = $this->hexToInt($vidStr);
            $pid = $this->hexToInt($pidStr);
            $connector = new \Mike42\Escpos\PrintConnectors\UsbPrintConnector($vid, $pid);
            return new Printer($connector);
        }

        // Default: network
        $host = (string) ($cfg['host'] ?? '192.168.1.100');
        $port = (int) ($cfg['port'] ?? 9100);
        $connector = new \Mike42\Escpos\PrintConnectors\NetworkPrintConnector($host, $port);
        return new Printer($connector);
    }


    /**
     * Build a "Label: ________VALUE" style line, using underscores to fill remaining space.
     * If $value is empty, just render a long blank line after the label.
     */
    private function labelWithLine(string $label, string $value, int $width, ?int $fillCount = null): string
    {
        $label = rtrim($label, ':') . ': ';
        $maxFill = $fillCount ?? max(0, $width - strlen($label));
        $filled = $value !== ''
            ? $this->fixedField($value, $maxFill, '_')
            : str_repeat('_', $maxFill);

        $line = $label . $filled;

        // Ensure total width, pad or trim
        return $this->fitLine($line, $width);
    }

    /**
     * Create a single field like "Date: ________" (or with value).
     */
    private function fieldWithBlanks(string $label, string $value, int $fieldWidth): string
    {
        $label = rtrim($label, ':') . ': ';
        $fill = max(0, $fieldWidth - strlen($label));
        $content = $value !== '' ? $this->fixedField($value, $fill, '_') : str_repeat('_', $fill);
        return $this->fitLine($label . $content, $fieldWidth);
    }

    /**
     * Join two fixed-width fields into one line (e.g., Date field + Time In field).
     */
    private function joinTwoFields(string $left, string $right, int $totalWidth): string
    {
        $gap = '  ';
        $line = $left . $gap . $right;
        return $this->fitLine($line, $totalWidth);
    }

    /**
     * Pad/truncate a value to an exact width using a fill character (for underlines).
     */
    private function fixedField(string $val, int $width, string $fillChar = '_'): string
    {
        $val = (string) $val;
        if (strlen($val) > $width) {
            return substr($val, 0, $width);
        }
        return $val . str_repeat($fillChar, max(0, $width - strlen($val)));
    }

    /**
     * Ensure any line is exactly $width characters (trim or pad spaces).
     */
    private function fitLine(string $line, int $width): string
    {
        $line = (string) $line;
        if (strlen($line) > $width) {
            return substr($line, 0, $width);
        }
        return $line . str_repeat(' ', $width - strlen($line));
    }

    /**
     * "0x1a86" -> 0x1a86 (int). Accepts plain decimal too.
     */
    private function hexToInt(string $hexOrDec): int
    {
        $hexOrDec = trim($hexOrDec);
        if ($hexOrDec === '') return 0;
        if (stripos($hexOrDec, '0x') === 0) {
            return intval($hexOrDec, 16);
        }
        return (int) $hexOrDec;
    }

    /**
     * Consolidated printer configuration. Supports both dotted keys (printer.*)
     * and uppercase PRINTER_* keys from .env.
     */
    private function printerConfig(): array
    {
        $get = function ($keys, $default = null) {
            $keys = is_array($keys) ? $keys : [$keys];
            foreach ($keys as $k) {
                $v = env($k);
                if ($v !== null && $v !== '') return $v;
            }
            return $default;
        };

        return [
            'mode'            => strtolower((string) $get(['printer.mode', 'PRINTER_MODE'], 'network')),
            'host'            => (string) $get(['printer.host', 'PRINTER_HOST'], '192.168.1.100'),
            'port'            => (int)    $get(['printer.port', 'PRINTER_PORT'], 9100),
            'usb_vid'         => (string) $get(['printer.usb.vid', 'PRINTER_USB_VID'], ''),
            'usb_pid'         => (string) $get(['printer.usb.pid', 'PRINTER_USB_PID'], ''),
            'windows_name'    => (string) $get(['printer.windows.name', 'PRINTER_WINDOWS_NAME'], 'POS-80'),
            'chars_per_line'  => (int)    $get(['printer.chars_per_line', 'PRINTER_CHARS_PER_LINE'], 48),
            'feed_lines'      => (int)    $get(['printer.feed_lines', 'PRINTER_FEED_LINES'], 3),
        ];
    }

    /**
     * Convert user-provided date/time strings into DB formats.
     */
    private function toDbDate(?string $s): ?string
    {
        $s = trim((string)$s);
        if ($s === '') return null;
        // Explicitly parse common US formats to avoid day/month swaps.
        if (preg_match('/^\\d{4}-\\d{2}-\\d{2}$/', $s)) {
            return $s;
        }
        if (preg_match('/^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/', $s)) {
            $dt = \DateTime::createFromFormat('!m/d/Y', $s);
            return $dt ? $dt->format('Y-m-d') : null;
        }
        if (preg_match('/^\\d{1,2}-\\d{1,2}-\\d{4}$/', $s)) {
            $dt = \DateTime::createFromFormat('!m-d-Y', $s);
            return $dt ? $dt->format('Y-m-d') : null;
        }
        $ts = strtotime($s);
        return $ts ? date('Y-m-d', $ts) : null;
    }

    private function toDbTime(?string $s): ?string
    {
        $s = trim((string)$s);
        if ($s === '') return null;
        $ts = strtotime($s);
        return $ts ? date('H:i:s', $ts) : null;
    }

    /**
     * Compute current admin full name using session user_id and users table.
     * Falls back to session firstname/lastname, then user_name, else ''.
     */
    private function currentAdminName(): string
    {
        try {
            $uid = (int) (session()->get('user_id') ?? 0);
            if ($uid > 0) {
                $user = (new UserModel())
                    ->select('firstname, lastname')
                    ->find($uid);
                if ($user) {
                    $full = trim((string)($user['firstname'] ?? '') . ' ' . (string)($user['lastname'] ?? ''));
                    if ($full !== '') return $full;
                }
            }
        } catch (\Throwable $e) {
            // ignore and fall back
        }

        $first = trim((string)(session()->get('firstname') ?? ''));
        $last  = trim((string)(session()->get('lastname') ?? ''));
        $full  = trim($first . ' ' . $last);
        if ($full !== '') return $full;
        return (string)(session()->get('user_name') ?? '');
    }

    /**
     * Render the slip as a PDF and stream it inline to the browser.
     */
    private function renderSlipPdfResponse(array $data): ResponseInterface
    {
        // Build HTML from a dedicated view
        // Determine paper size (mm)
        $paper = strtolower((string) ($this->request->getVar('paper') ?: env('SLIP_PAPER', 'card')));
        // Estimate dynamic height based on the PDF view font and line-height
        // Title + seven content lines = 8 rows total
        // Defaults match the current view: 13pt font, 2.0 line-height
        $fontPt = (float) ($this->request->getVar('font_pt') ?? env('SLIP_FONT_PT', 13.0));
        $lineH  = (float) ($this->request->getVar('line_h')  ?? env('SLIP_LINE_H', 2.0));
        $linesCount = (int) ($this->request->getVar('rows') ?? 8);
        if ($linesCount < 1) {
            $linesCount = 8;
        }
        $ptPerLine = $fontPt * $lineH;
        $mmPerPt   = 25.4 / 72.0;                    // 1pt in mm
        $contentMm = $ptPerLine * $linesCount * $mmPerPt; // content only
        $pageMarginsMm = 3.0 * 2;                    // top+bottom from CSS
        // Allow caller to increase safety buffer if their viewer/driver adds spacing
        // Slightly bigger safety buffer to avoid second-page spillovers from drivers
        $fudgeMm = (float) ($this->request->getVar('fudge_mm') ?? env('SLIP_FUDGE_MM', 12.0));
        $dynHeightMm = $contentMm + $pageMarginsMm + $fudgeMm;

        switch ($paper) {
            case 'cardp':
            case 'card-portrait':
                $wMm = 53.98;
                $hMm = max($dynHeightMm, 40.0); // portrait width, dynamic height
                $wChars = 30;
                break;
            case 'receipt80':
                $wMm = 72.0;
                $hMm = 90.0;  // 80mm roll (printable ~72mm) — taller to fit larger font
                $wChars = 30;
                break;
            case 'receipt58':
                $wMm = 58.0;
                $hMm = 80.0;  // 58mm roll — taller to fit larger font
                $wChars = 24;
                break;
            case 'card':
            default:
                $wMm = 85.60;
                $hMm = max($dynHeightMm, 40.0); // credit card width, dynamic height
                $wChars = 40;
        }

        // Optional manual override via query: height_mm (or h)
        $hOverride = $this->request->getVar('height_mm') ?? $this->request->getVar('h');
        if (is_numeric($hOverride)) {
            $hMm = max(30.0, min(200.0, (float) $hOverride));
        }

        // Build exactly six lines in monospace formatting
        $w = $wChars; // characters per line for layout
        $lines = [];
        $lines[] = $this->fitLine('Al Rahma School at ISGL ' . ($data['school_year'] ?? ''), $w);
        // Helper to compose "Label: value_____" without truncating value first
        $compose = function (string $label, string $value, int $padUnderscore = 0) use ($w) {
            $prefix = rtrim($label, ':') . ': ';
            $line = $prefix . $value;
            $len = strlen($line);
            if ($padUnderscore > 0 && $len < $w) {
                $line .= str_repeat('_', min($padUnderscore, $w - $len));
            }
            return $this->fitLine($line, $w);
        };
        // Student Name (no trailing underscores)
        $lines[] = $compose('Student Name', (string)($data['student_name'] ?? ''), 0);
        // Date and Time In on separate lines
        $lines[] = $this->fitLine('Date: ' . (string)($data['date'] ?? ''), $w);
        $lines[] = $this->fitLine('Time In: ' . (string)($data['time_in'] ?? ''), $w);
        // Grade and Reason (no trailing underscores)
        $lines[] = $compose('Grade', (string)($data['grade'] ?? ''), 0);
        $lines[] = $compose('Reason', (string)($data['reason'] ?? ''), 0);
        // Admin: do not underline, just value
        $lines[] = $this->fitLine('Admin: ' . (string)($data['admin_name'] ?? ''), $w);

        $html = view('slips/slip_pdf', ['entry' => $data, 'lines' => $lines, 'page' => ['w_mm' => $wMm, 'h_mm' => $hMm]]);

        $options = new Options();
        $options->set('isRemoteEnabled', true);
        $options->set('defaultFont', 'Courier');

        $dompdf = new Dompdf($options);
        $dompdf->loadHtml($html, 'UTF-8');
        // Ensure minimum height is enough for current content calculation
        if ($hMm < $dynHeightMm) {
            $hMm = $dynHeightMm;
        }

        // Set paper size from selected mm values -> points
        $mmToPt = 72 / 25.4;
        $wPt = $wMm * $mmToPt;
        $hPt = $hMm * $mmToPt;
        $dompdf->setPaper([0, 0, $wPt, $hPt]);
        $dompdf->render();

        $filename = 'LateSlip_' . preg_replace('/[^A-Za-z0-9]+/', '_', (string)($data['student_name'] ?? 'slip')) . '_' . date('Ymd_His') . '.pdf';

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"')
            ->setBody($dompdf->output());
    }
}
