<?php
/**
 * thermal_print.php
 * ─────────────────────────────────────────────────────────────────────────────
 * Epson TM-T88V / TM-T20 series thermal printer endpoint.
 *
 * Print path (tried in order):
 *   1. Epson ePOS HTTP (port 80)  — cleanest, gives success/fail response
 *   2. Raw ESC/POS socket (port 9100) — fallback if ePOS not enabled
 *   3. Returns { ok:false, method:"browser" } — JS opens browser print popup
 *
 * CONFIGURE YOUR PRINTERS BELOW — that is the only block you need to edit.
 *
 * POST: JSON body { "tickets": [ { "type": "receipt|split|kitchen", ... } ] }
 * Returns JSON: { "ok": bool, "method": "epos|raw|browser", "results": [...] }
 */

// ─── CONFIGURE YOUR PRINTERS HERE ────────────────────────────────────────────
const THERMAL_PRINTERS = [
    'reception' => [
        'ip'      => '10.15.18.201',   // Cashier / receipt printer IP
        'epos'    => true,               // true = try ePOS HTTP first
        'port'    => 9100,               // raw ESC/POS fallback port
        'timeout' => 4,
    ],
    'kitchen' => [
        'ip'      => '192.168.1.101',   // Kitchen printer IP
        'epos'    => true,
        'port'    => 9100,
        'timeout' => 4,
    ],
    'bar' => [
        'ip'      => '10.15.18.22',   // Bar printer IP
        'epos'    => true,
        'port'    => 9100,
        'timeout' => 4,
    ],
];

const THERMAL_PAPER_WIDTH  = 48;                // 80mm = 48 chars, 58mm = 32 chars
const THERMAL_PROPERTY_NAME = 'THE PROPERTY';   // Printed on every receipt header
const THERMAL_EPOS_DEVID   = 'local_printer';   // ePOS device ID (default works for most Epson)
// ─────────────────────────────────────────────────────────────────────────────

require_once __DIR__ . '/../../includes/bootstrap.php';
require_login();

header('Content-Type: application/json');

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
    echo json_encode(['ok' => false, 'error' => 'POST required']);
    exit;
}

$body = file_get_contents('php://input');
$data = $body ? json_decode($body, true) : null;
if (!$data) $data = $_POST;

if (empty($data['tickets']) || !is_array($data['tickets'])) {
    echo json_encode(['ok' => false, 'error' => 'No tickets provided']);
    exit;
}

// ── Text helpers ──────────────────────────────────────────────────────────────
function th_pad(string $left, string $right, int $width = THERMAL_PAPER_WIDTH): string {
    $left  = mb_substr($left, 0, $width - mb_strlen($right) - 1);
    $space = max(1, $width - mb_strlen($left) - mb_strlen($right));
    return $left . str_repeat(' ', $space) . $right . "\n";
}
function th_center(string $text, int $width = THERMAL_PAPER_WIDTH): string {
    $pad = max(0, intdiv($width - mb_strlen($text), 2));
    return str_repeat(' ', $pad) . $text . "\n";
}
function th_divider(string $char = '-', int $width = THERMAL_PAPER_WIDTH): string {
    return str_repeat($char, $width) . "\n";
}
function th_money(float $v): string { return number_format($v, 2); }

// ── ESC/POS byte helpers (for raw port 9100 fallback) ────────────────────────
function esc_init():                   string { return "\x1B\x40"; }
function esc_cut():                    string { return "\x1D\x56\x42\x00"; }
function esc_feed(int $n = 3):         string { return str_repeat("\x0A", $n); }
function esc_bold(bool $on):           string { return $on ? "\x1B\x45\x01" : "\x1B\x45\x00"; }
function esc_align(string $a):         string {
    return "\x1B\x61" . (['left'=>"\x00",'center'=>"\x01",'right'=>"\x02"][$a] ?? "\x00");
}
function esc_double_height(bool $on):  string { return $on ? "\x1B\x21\x10" : "\x1B\x21\x00"; }
function esc_invert(bool $on):         string { return $on ? "\x1D\x42\x01" : "\x1D\x42\x00"; }

// ── Build ESC/POS bytes for raw printing ─────────────────────────────────────
function build_raw_receipt(array $t): string {
    $w   = THERMAL_PAPER_WIDTH;
    $out = esc_init();
    $out .= esc_align('center') . esc_double_height(true) . esc_bold(true);
    $out .= mb_strtoupper(THERMAL_PROPERTY_NAME) . "\n";
    $out .= esc_double_height(false) . esc_bold(false);
    $out .= th_divider('=', $w);
    $out .= esc_align('left');
    if (!empty($t['order_no'])) $out .= esc_bold(true) . 'Order:   ' . esc_bold(false) . $t['order_no'] . "\n";
    if (!empty($t['table']))    $out .= 'Table:   ' . $t['table']   . "\n";
    if (!empty($t['guest']))    $out .= 'Guest:   ' . $t['guest']   . "\n";
    if (!empty($t['cashier']))  $out .= 'Cashier: ' . $t['cashier'] . "\n";
    $out .= 'Date:    ' . (!empty($t['date']) ? $t['date'] : date('Y-m-d H:i')) . "\n";
    $out .= th_divider('-', $w);
    $subtotal = 0.0;
    foreach ((array)($t['items'] ?? []) as $item) {
        if (($item['status'] ?? 'active') !== 'active') continue;
        $qty    = (float)($item['qty']    ?? 1);
        $amount = (float)($item['amount'] ?? 0);
        $subtotal += $amount;
        $label  = ($qty != 1 ? ($qty == intval($qty) ? (int)$qty : $qty) . ' x ' : '') . ($item['name'] ?? 'Item');
        $out   .= th_pad(mb_substr($label, 0, $w - 10), th_money($amount), $w);
        if (!empty($item['notes'])) $out .= '  * ' . mb_substr($item['notes'], 0, $w - 4) . "\n";
    }
    $out .= th_divider('-', $w);
    if (!empty($t['tax']) && (float)$t['tax'] > 0) {
        $out .= th_pad('Subtotal', th_money((float)($t['subtotal'] ?? $subtotal)), $w);
        $out .= th_pad('Tax',      th_money((float)$t['tax']), $w);
    }
    $out .= esc_bold(true) . th_pad('TOTAL', th_money((float)($t['total'] ?? $subtotal)), $w) . esc_bold(false);
    if (!empty($t['payments'])) {
        $out .= th_divider('-', $w);
        foreach ((array)$t['payments'] as $pay) {
            $out .= th_pad(mb_strtoupper((string)($pay['method'] ?? '')), th_money((float)($pay['amount'] ?? 0)), $w);
            if (!empty($pay['reference'])) $out .= '  Ref: ' . mb_substr((string)$pay['reference'], 0, $w - 6) . "\n";
        }
    }
    if (!empty($t['surcharge']) && (float)$t['surcharge'] > 0)
        $out .= '  * Incl. 3% card surcharge ' . th_money((float)$t['surcharge']) . "\n";
    $out .= th_divider('=', $w);
    $out .= esc_align('center') . "Thank you for your visit!\n" . esc_align('left');
    $out .= esc_feed(4) . esc_cut();
    return $out;
}

function build_raw_split(array $t): string {
    $w   = THERMAL_PAPER_WIDTH;
    $out = esc_init();
    $out .= esc_align('center') . esc_bold(true) . mb_strtoupper(THERMAL_PROPERTY_NAME) . "\n" . esc_bold(false);
    $out .= "-- SPLIT BILL --\n" . th_divider('=', $w);
    $out .= esc_align('left');
    if (!empty($t['order_no'])) $out .= 'Order:  ' . $t['order_no'] . "\n";
    if (!empty($t['payer']))    $out .= esc_bold(true) . 'Payer:  ' . esc_bold(false) . $t['payer']  . "\n";
    if (!empty($t['method']))   $out .= 'Method: ' . $t['method']   . "\n";
    $out .= 'Date:   ' . (!empty($t['date']) ? $t['date'] : date('Y-m-d H:i')) . "\n";
    $out .= th_divider('-', $w);
    $subtotal = 0.0;
    foreach ((array)($t['items'] ?? []) as $item) {
        $qty    = (float)($item['qty'] ?? 1);
        $amount = (float)($item['amount'] ?? 0);
        $subtotal += $amount;
        $label  = ($qty != 1 ? (int)$qty . ' x ' : '') . ($item['name'] ?? 'Item');
        $out   .= th_pad(mb_substr($label, 0, $w - 10), th_money($amount), $w);
    }
    $out .= th_divider('-', $w);
    $out .= esc_bold(true) . th_pad('AMOUNT DUE', th_money((float)($t['total'] ?? $subtotal)), $w) . esc_bold(false);
    if (!empty($t['surcharge']) && (float)$t['surcharge'] > 0)
        $out .= th_pad('  incl. 3% surcharge', th_money((float)$t['surcharge']), $w);
    $out .= th_divider('=', $w);
    $out .= esc_align('center') . "Thank you!\n" . esc_align('left');
    $out .= esc_feed(4) . esc_cut();
    return $out;
}

function build_raw_kitchen(array $t): string {
    $w    = THERMAL_PAPER_WIDTH;
    $role = mb_strtoupper((string)($t['station'] ?? 'KITCHEN'));
    $out  = esc_init();
    $out .= esc_align('center') . esc_invert(true) . esc_bold(true) . " $role TICKET \n" . esc_bold(false) . esc_invert(false);
    $out .= th_divider('=', $w) . esc_align('left');
    if (!empty($t['order_no'])) $out .= esc_bold(true) . 'ORDER: ' . $t['order_no'] . esc_bold(false) . "\n";
    if (!empty($t['table']))    $out .= 'Table:  ' . $t['table']  . "\n";
    if (!empty($t['covers']))   $out .= 'Covers: ' . $t['covers'] . "\n";
    $out .= 'Time:   ' . (!empty($t['date']) ? $t['date'] : date('H:i')) . "\n";
    $out .= th_divider('=', $w);
    foreach ((array)($t['items'] ?? []) as $item) {
        $qty  = (float)($item['qty'] ?? 1);
        $name = mb_strtoupper((string)($item['name'] ?? 'Item'));
        $out .= esc_bold(true) . ' ' . ($qty == intval($qty) ? (int)$qty : $qty) . ' x ' . $name . "\n" . esc_bold(false);
        if (!empty($item['seat']))  $out .= '   Seat:  ' . $item['seat']  . "\n";
        if (!empty($item['notes'])) $out .= '   ** '    . mb_substr($item['notes'], 0, $w - 6) . "\n";
    }
    $out .= th_divider('=', $w) . esc_feed(4) . esc_cut();
    return $out;
}

// ── Build ePOS XML for HTTP printing ─────────────────────────────────────────
function xe(string $text): string { return htmlspecialchars($text, ENT_XML1, 'UTF-8'); }

function build_epos_receipt(array $t): string {
    $w        = THERMAL_PAPER_WIDTH;
    $subtotal = 0.0;
    $lines    = '';

    $lines .= '<text align="center" width="2" height="2" b="true">' . xe(mb_strtoupper(THERMAL_PROPERTY_NAME)) . '&#10;</text>';
    $lines .= '<text align="left" width="1" height="1" b="false">' . xe(str_repeat('=', $w)) . '&#10;</text>';

    if (!empty($t['order_no'])) $lines .= '<text>Order:   ' . xe($t['order_no']) . '&#10;</text>';
    if (!empty($t['table']))    $lines .= '<text>Table:   ' . xe($t['table'])    . '&#10;</text>';
    if (!empty($t['guest']))    $lines .= '<text>Guest:   ' . xe($t['guest'])    . '&#10;</text>';
    if (!empty($t['cashier']))  $lines .= '<text>Cashier: ' . xe($t['cashier'])  . '&#10;</text>';
    $lines .= '<text>Date:    ' . xe(!empty($t['date']) ? $t['date'] : date('Y-m-d H:i')) . '&#10;</text>';
    $lines .= '<text>' . xe(str_repeat('-', $w)) . '&#10;</text>';

    foreach ((array)($t['items'] ?? []) as $item) {
        if (($item['status'] ?? 'active') !== 'active') continue;
        $qty    = (float)($item['qty']    ?? 1);
        $amount = (float)($item['amount'] ?? 0);
        $subtotal += $amount;
        $label  = ($qty != 1 ? ($qty == intval($qty) ? (int)$qty : $qty) . ' x ' : '') . ($item['name'] ?? 'Item');
        $right  = th_money($amount);
        $left   = mb_substr($label, 0, $w - mb_strlen($right) - 1);
        $space  = max(1, $w - mb_strlen($left) - mb_strlen($right));
        $lines .= '<text>' . xe($left . str_repeat(' ', $space) . $right) . '&#10;</text>';
        if (!empty($item['notes'])) $lines .= '<text>  * ' . xe(mb_substr($item['notes'], 0, $w - 4)) . '&#10;</text>';
    }

    $lines .= '<text>' . xe(str_repeat('-', $w)) . '&#10;</text>';
    if (!empty($t['tax']) && (float)$t['tax'] > 0) {
        $lines .= '<text>' . xe(th_pad('Subtotal', th_money((float)($t['subtotal'] ?? $subtotal)), $w)) . '</text>';
        $lines .= '<text>' . xe(th_pad('Tax',      th_money((float)$t['tax']), $w)) . '</text>';
    }
    $lines .= '<text b="true">' . xe(th_pad('TOTAL', th_money((float)($t['total'] ?? $subtotal)), $w)) . '</text>';

    if (!empty($t['payments'])) {
        $lines .= '<text>' . xe(str_repeat('-', $w)) . '&#10;</text>';
        foreach ((array)$t['payments'] as $pay) {
            $lines .= '<text>' . xe(th_pad(mb_strtoupper((string)($pay['method'] ?? '')), th_money((float)($pay['amount'] ?? 0)), $w)) . '</text>';
            if (!empty($pay['reference'])) $lines .= '<text>  Ref: ' . xe(mb_substr((string)$pay['reference'], 0, $w - 6)) . '&#10;</text>';
        }
    }
    if (!empty($t['surcharge']) && (float)$t['surcharge'] > 0)
        $lines .= '<text>  * Incl. 3% card surcharge ' . xe(th_money((float)$t['surcharge'])) . '&#10;</text>';

    $lines .= '<text>' . xe(str_repeat('=', $w)) . '&#10;</text>';
    $lines .= '<text align="center">Thank you for your visit!&#10;</text>';
    $lines .= '<feed line="4"/>';
    $lines .= '<cut type="feed"/>';
    return $lines;
}

function build_epos_split(array $t): string {
    $w        = THERMAL_PAPER_WIDTH;
    $subtotal = 0.0;
    $lines    = '';
    $lines .= '<text align="center" b="true">' . xe(mb_strtoupper(THERMAL_PROPERTY_NAME)) . '&#10;</text>';
    $lines .= '<text align="center">-- SPLIT BILL --&#10;</text>';
    $lines .= '<text align="left">' . xe(str_repeat('=', $w)) . '&#10;</text>';
    if (!empty($t['order_no'])) $lines .= '<text>Order:  ' . xe($t['order_no']) . '&#10;</text>';
    if (!empty($t['payer']))    $lines .= '<text b="true">Payer:  ' . xe($t['payer'])  . '&#10;</text>';
    if (!empty($t['method']))   $lines .= '<text>Method: ' . xe($t['method'])   . '&#10;</text>';
    $lines .= '<text>Date:   ' . xe(!empty($t['date']) ? $t['date'] : date('Y-m-d H:i')) . '&#10;</text>';
    $lines .= '<text>' . xe(str_repeat('-', $w)) . '&#10;</text>';
    foreach ((array)($t['items'] ?? []) as $item) {
        $qty    = (float)($item['qty'] ?? 1);
        $amount = (float)($item['amount'] ?? 0);
        $subtotal += $amount;
        $label  = ($qty != 1 ? (int)$qty . ' x ' : '') . ($item['name'] ?? 'Item');
        $right  = th_money($amount);
        $left   = mb_substr($label, 0, $w - mb_strlen($right) - 1);
        $space  = max(1, $w - mb_strlen($left) - mb_strlen($right));
        $lines .= '<text>' . xe($left . str_repeat(' ', $space) . $right) . '&#10;</text>';
    }
    $lines .= '<text>' . xe(str_repeat('-', $w)) . '&#10;</text>';
    $lines .= '<text b="true">' . xe(th_pad('AMOUNT DUE', th_money((float)($t['total'] ?? $subtotal)), $w)) . '</text>';
    if (!empty($t['surcharge']) && (float)$t['surcharge'] > 0)
        $lines .= '<text>' . xe(th_pad('  incl. 3% surcharge', th_money((float)$t['surcharge']), $w)) . '</text>';
    $lines .= '<text>' . xe(str_repeat('=', $w)) . '&#10;</text>';
    $lines .= '<text align="center">Thank you!&#10;</text>';
    $lines .= '<feed line="4"/><cut type="feed"/>';
    return $lines;
}

function build_epos_kitchen(array $t): string {
    $w     = THERMAL_PAPER_WIDTH;
    $role  = mb_strtoupper((string)($t['station'] ?? 'KITCHEN'));
    $lines = '';
    $lines .= '<text align="center" b="true" reverse="true"> ' . xe($role) . ' TICKET &#10;</text>';
    $lines .= '<text align="left" reverse="false">' . xe(str_repeat('=', $w)) . '&#10;</text>';
    if (!empty($t['order_no'])) $lines .= '<text b="true">ORDER: ' . xe($t['order_no']) . '&#10;</text>';
    if (!empty($t['table']))    $lines .= '<text b="false">Table:  ' . xe($t['table'])  . '&#10;</text>';
    if (!empty($t['covers']))   $lines .= '<text>Covers: ' . xe($t['covers']) . '&#10;</text>';
    $lines .= '<text>Time:   ' . xe(!empty($t['date']) ? $t['date'] : date('H:i')) . '&#10;</text>';
    $lines .= '<text>' . xe(str_repeat('=', $w)) . '&#10;</text>';
    foreach ((array)($t['items'] ?? []) as $item) {
        $qty  = (float)($item['qty'] ?? 1);
        $name = mb_strtoupper((string)($item['name'] ?? 'Item'));
        $lines .= '<text b="true"> ' . xe(($qty == intval($qty) ? (int)$qty : $qty) . ' x ' . $name) . '&#10;</text>';
        if (!empty($item['seat']))  $lines .= '<text b="false">   Seat:  ' . xe($item['seat'])  . '&#10;</text>';
        if (!empty($item['notes'])) $lines .= '<text>   ** '    . xe(mb_substr($item['notes'], 0, $w - 6)) . '&#10;</text>';
    }
    $lines .= '<text>' . xe(str_repeat('=', $w)) . '&#10;</text>';
    $lines .= '<feed line="4"/><cut type="feed"/>';
    return $lines;
}

// ── Wrap ePOS XML in SOAP envelope ───────────────────────────────────────────
function wrap_epos_soap(string $innerXml): string {
    $devId = THERMAL_EPOS_DEVID;
    return '<?xml version="1.0" encoding="utf-8"?>'
        . '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">'
        . '<s:Body>'
        . '<epos-print xmlns="http://www.epson-pos.com/schemas/2011/03/epos-print">'
        . $innerXml
        . '</epos-print>'
        . '</s:Body>'
        . '</s:Envelope>';
}

// ── Send via Epson ePOS HTTP ──────────────────────────────────────────────────
function send_epos(string $ip, string $innerXml, int $timeout = 4): array {
    if (!function_exists('curl_init')) {
        return ['ok' => false, 'error' => 'cURL not available for ePOS'];
    }

    $url  = 'http://' . $ip . '/cgi-bin/epos/service.cgi?devid=' . THERMAL_EPOS_DEVID . '&timeout=10000';
    $soap = wrap_epos_soap($innerXml);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $soap,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: text/xml; charset=utf-8',
            'SOAPAction: ""',
            'If-Modified-Since: Thu, 01 Jan 1970 00:00:00 GMT',
        ],
    ]);

    $response = curl_exec($ch);
    $curlErr  = curl_error($ch);
    $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($curlErr) {
        return ['ok' => false, 'method' => 'epos', 'error' => 'ePOS cURL: ' . $curlErr];
    }
    if ($httpCode !== 200) {
        return ['ok' => false, 'method' => 'epos', 'error' => 'ePOS HTTP ' . $httpCode];
    }

    // Check for success in ePOS response XML
    if ($response && strpos($response, 'success="true"') !== false) {
        return ['ok' => true, 'method' => 'epos'];
    }
    if ($response && strpos($response, 'success="false"') !== false) {
        // Extract error code if present
        preg_match('/code="([^"]+)"/', $response ?? '', $m);
        $code = $m[1] ?? 'unknown';
        return ['ok' => false, 'method' => 'epos', 'error' => 'ePOS error code: ' . $code];
    }

    // No explicit success/false — treat a 200 with any body as success
    if ($response) {
        return ['ok' => true, 'method' => 'epos'];
    }

    return ['ok' => false, 'method' => 'epos', 'error' => 'Empty ePOS response'];
}

// ── Send via raw ESC/POS socket (port 9100 fallback) ─────────────────────────
function send_raw(string $ip, string $bytes, int $port = 9100, int $timeout = 4): array {
    $fp = @fsockopen($ip, $port, $errno, $errstr, $timeout);
    if (!$fp) {
        return ['ok' => false, 'method' => 'raw', 'error' => "Raw socket $ip:$port — $errstr ($errno)"];
    }
    fwrite($fp, $bytes);
    fclose($fp);
    return ['ok' => true, 'method' => 'raw'];
}

// ── Main: process each ticket ─────────────────────────────────────────────────
$results = [];
$allOk   = true;

foreach ($data['tickets'] as $ticket) {
    $type   = strtolower((string)($ticket['type']    ?? 'receipt'));
    $station = strtolower((string)($ticket['station'] ?? ''));
    $role   = $station ?: ($type === 'kitchen' ? 'kitchen' : ($type === 'bar' ? 'bar' : 'receipt'));

    $config  = THERMAL_PRINTERS[$role] ?? THERMAL_PRINTERS['receipt'];
    $ip      = (string)$config['ip'];
    $port    = (int)($config['port']    ?? 9100);
    $timeout = (int)($config['timeout'] ?? 4);
    $useEpos = (bool)($config['epos']   ?? true);

    $ticket['date'] = $ticket['date'] ?? date('Y-m-d H:i');

    // Build both ePOS XML and raw ESC/POS bytes up front
    if ($type === 'kitchen' || $type === 'bar') {
        $eposXml  = build_epos_kitchen($ticket);
        $rawBytes = build_raw_kitchen($ticket);
    } elseif ($type === 'split') {
        $eposXml  = build_epos_split($ticket);
        $rawBytes = build_raw_split($ticket);
    } else {
        $eposXml  = build_epos_receipt($ticket);
        $rawBytes = build_raw_receipt($ticket);
    }

    $res = ['role' => $role, 'type' => $type, 'ip' => $ip];

    // Try ePOS HTTP first
    if ($useEpos) {
        $eposResult = send_epos($ip, $eposXml, $timeout);
        if ($eposResult['ok']) {
            $res = array_merge($res, $eposResult);
            $results[] = $res;
            continue;
        }
        $res['epos_error'] = $eposResult['error'] ?? 'ePOS failed';
    }

    // Fallback: raw ESC/POS port 9100
    $rawResult = send_raw($ip, $rawBytes, $port, $timeout);
    $res = array_merge($res, $rawResult);

    if (!$rawResult['ok']) {
        $allOk = false;
        // Return base64 raw bytes so browser can show print popup
        $res['bytes_b64'] = base64_encode($rawBytes);
    }

    $results[] = $res;
}

$method = 'network';
foreach ($results as $r) {
    if (!($r['ok'] ?? false)) { $method = 'browser'; break; }
    if (($r['method'] ?? '') === 'epos') $method = 'epos';
    elseif (($r['method'] ?? '') === 'raw' && $method !== 'epos') $method = 'raw';
}

echo json_encode([
    'ok'      => $allOk,
    'method'  => $method,
    'results' => $results,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
