/**
 * thermal_print.js
 * ─────────────────────────────────────────────────────────────────────────────
 * Browser-side Epson ePOS printing.
 * Sends XML directly from the browser to the printer IP — no server involved.
 *
 * Works when:
 *   - Browser and printer are on the same network (workstation can reach printer)
 *   - POS page is served over HTTP (not HTTPS), OR printer has HTTPS enabled
 *
 * CONFIGURE:
 *   Change PRINTER_IP below to your printer's IP address.
 *   If you have multiple printers, set KITCHEN_IP and BAR_IP too.
 */

window.ThermalPrint = (function () {
    'use strict';

    // ── CONFIG ────────────────────────────────────────────────────────────────
    const PRINTER_IP   = '10.15.18.201';   // Receipt / cashier printer
    const KITCHEN_IP   = '10.15.18.201';   // Kitchen printer (change if different)
    const BAR_IP       = '10.15.18.201';   // Bar printer (change if different)
    const EPOS_DEVID   = 'local_printer';  // ePOS device ID (default for Epson)
    const PAPER_WIDTH  = 48;               // 80mm = 48 chars
    const TIMEOUT_MS   = 6000;             // 6 seconds before fallback to browser print
    const PROP_NAME    = document.querySelector('meta[name="property-name"]')?.content
                         || 'THE PROPERTY';

    // ── ePOS XML builders ─────────────────────────────────────────────────────
    function xe(text) {
        return String(text ?? '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

    function eposWrap(innerXml) {
        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>';
    }

    function padLine(left, right, width) {
        left  = String(left  ?? '').substring(0, width - String(right ?? '').length - 1);
        right = String(right ?? '');
        const space = Math.max(1, width - left.length - right.length);
        return left + ' '.repeat(space) + right;
    }
    function money(v) { return (parseFloat(v) || 0).toFixed(2); }
    function divider(w) { return '-'.repeat(w || PAPER_WIDTH); }

    function buildReceiptXml(t) {
        const w = PAPER_WIDTH;
        let xml = '';
        xml += '<text align="center" width="2" height="2" b="true">' + xe(PROP_NAME.toUpperCase()) + '&#10;</text>';
        xml += '<text align="left" width="1" height="1" b="false">' + xe('='.repeat(w)) + '&#10;</text>';
        if (t.order_no) xml += '<text>Order:   ' + xe(t.order_no) + '&#10;</text>';
        if (t.table)    xml += '<text>Table:   ' + xe(t.table)    + '&#10;</text>';
        if (t.guest)    xml += '<text>Guest:   ' + xe(t.guest)    + '&#10;</text>';
        if (t.cashier)  xml += '<text>Cashier: ' + xe(t.cashier)  + '&#10;</text>';
        xml += '<text>Date:    ' + xe(t.date || new Date().toLocaleString()) + '&#10;</text>';
        xml += '<text>' + xe(divider(w)) + '&#10;</text>';

        let sub = 0;
        (t.items || []).forEach(item => {
            if ((item.status || 'active') !== 'active') return;
            const qty    = parseFloat(item.qty || 1) || 1;
            const amount = parseFloat(item.amount || 0);
            sub += amount;
            const label = (qty !== 1 ? (Number.isInteger(qty) ? qty : qty.toFixed(2)) + ' x ' : '') + (item.name || 'Item');
            xml += '<text>' + xe(padLine(label, money(amount), w)) + '&#10;</text>';
            if (item.notes) xml += '<text>  * ' + xe(item.notes) + '&#10;</text>';
        });

        xml += '<text>' + xe(divider(w)) + '&#10;</text>';
        if (t.tax && parseFloat(t.tax) > 0) {
            xml += '<text>' + xe(padLine('Subtotal', money(t.subtotal || sub), w)) + '&#10;</text>';
            xml += '<text>' + xe(padLine('Tax',      money(t.tax),              w)) + '&#10;</text>';
        }
        xml += '<text b="true">'  + xe(padLine('TOTAL', money(t.total || sub), w)) + '&#10;</text>';

        if (t.payments && t.payments.length) {
            xml += '<text>' + xe(divider(w)) + '&#10;</text>';
            t.payments.forEach(p => {
                xml += '<text>' + xe(padLine((p.method || '').toUpperCase(), money(p.amount || 0), w)) + '&#10;</text>';
                if (p.reference) xml += '<text>  Ref: ' + xe(p.reference) + '&#10;</text>';
            });
        }
        if (t.surcharge && parseFloat(t.surcharge) > 0)
            xml += '<text>  * Incl. 3% card surcharge ' + xe(money(t.surcharge)) + '&#10;</text>';

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

    function buildSplitXml(t) {
        const w = PAPER_WIDTH;
        let xml = '';
        xml += '<text align="center" b="true">' + xe(PROP_NAME.toUpperCase()) + '&#10;</text>';
        xml += '<text align="center">-- SPLIT BILL --&#10;</text>';
        xml += '<text align="left">'  + xe('='.repeat(w)) + '&#10;</text>';
        if (t.order_no) xml += '<text>Order:  ' + xe(t.order_no) + '&#10;</text>';
        if (t.payer)    xml += '<text b="true">Payer:  ' + xe(t.payer)  + '&#10;</text>';
        if (t.method)   xml += '<text b="false">Method: ' + xe(t.method) + '&#10;</text>';
        xml += '<text>Date:   ' + xe(t.date || new Date().toLocaleString()) + '&#10;</text>';
        xml += '<text>' + xe(divider(w)) + '&#10;</text>';
        let sub = 0;
        (t.items || []).forEach(item => {
            const qty    = parseFloat(item.qty || 1) || 1;
            const amount = parseFloat(item.amount || 0);
            sub += amount;
            const label = (qty !== 1 ? qty + ' x ' : '') + (item.name || 'Item');
            xml += '<text>' + xe(padLine(label, money(amount), w)) + '&#10;</text>';
        });
        xml += '<text>' + xe(divider(w)) + '&#10;</text>';
        xml += '<text b="true">' + xe(padLine('AMOUNT DUE', money(t.total || sub), w)) + '&#10;</text>';
        if (t.surcharge && parseFloat(t.surcharge) > 0)
            xml += '<text>' + xe(padLine('  incl. 3% surcharge', money(t.surcharge), w)) + '&#10;</text>';
        xml += '<text>' + xe('='.repeat(w)) + '&#10;</text>';
        xml += '<text align="center">Thank you!&#10;</text>';
        xml += '<feed line="4"/><cut type="feed"/>';
        return xml;
    }

    function buildKitchenXml(t) {
        const w    = PAPER_WIDTH;
        const role = (t.station || 'KITCHEN').toUpperCase();
        let xml = '';
        xml += '<text align="center" b="true" reverse="true"> ' + xe(role) + ' TICKET &#10;</text>';
        xml += '<text align="left" reverse="false">' + xe('='.repeat(w)) + '&#10;</text>';
        if (t.order_no) xml += '<text b="true">ORDER: '  + xe(t.order_no) + '&#10;</text>';
        if (t.table)    xml += '<text b="false">Table:  ' + xe(t.table)   + '&#10;</text>';
        if (t.covers)   xml += '<text>Covers: '            + xe(t.covers)  + '&#10;</text>';
        xml += '<text>Time:   ' + xe(t.date || new Date().toLocaleTimeString()) + '&#10;</text>';
        xml += '<text>' + xe('='.repeat(w)) + '&#10;</text>';
        (t.items || []).forEach(item => {
            const qty = parseFloat(item.qty || 1) || 1;
            xml += '<text b="true"> ' + xe((Number.isInteger(qty) ? qty : qty.toFixed(1)) + ' x ' + (item.name || 'Item').toUpperCase()) + '&#10;</text>';
            if (item.seat)  xml += '<text b="false">   Seat:  ' + xe(item.seat)  + '&#10;</text>';
            if (item.notes) xml += '<text>   ** '               + xe(item.notes) + '&#10;</text>';
        });
        xml += '<text>' + xe('='.repeat(w)) + '&#10;</text>';
        xml += '<feed line="4"/><cut type="feed"/>';
        return xml;
    }

    // ── Send to printer via ePOS HTTP ─────────────────────────────────────────
    async function sendEpos(ip, innerXml) {
        const url  = 'http://' + ip + '/cgi-bin/epos/service.cgi?devid=' + EPOS_DEVID + '&timeout=10000';
        const soap = eposWrap(innerXml);

        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);

        try {
            const resp = await fetch(url, {
                method:  'POST',
                headers: {
                    'Content-Type': 'text/xml; charset=utf-8',
                    'SOAPAction':   '""',
                },
                body:   soap,
                signal: controller.signal,
                mode:   'cors',
            });
            clearTimeout(timer);

            const text = await resp.text();
            if (text.includes('success="true"'))  return { ok: true };
            if (text.includes('success="false"')) {
                const m = text.match(/code="([^"]+)"/);
                return { ok: false, error: 'ePOS error: ' + (m ? m[1] : 'unknown') };
            }
            if (resp.ok) return { ok: true };
            return { ok: false, error: 'HTTP ' + resp.status };
        } catch (err) {
            clearTimeout(timer);
            if (err.name === 'AbortError') return { ok: false, error: 'Printer timeout — is ePOS enabled?' };
            return { ok: false, error: err.message };
        }
    }

    // ── Browser print fallback ────────────────────────────────────────────────
    function browserPrint(texts, title) {
        const combined = Array.isArray(texts)
            ? texts.join('\n\n' + '─'.repeat(PAPER_WIDTH) + '\n\n')
            : String(texts || '');

        const html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
<title>${title || 'Print'}</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box}
  body{font-family:'Courier New',Courier,monospace;font-size:11px;line-height:1.3;padding:4mm}
  pre{white-space:pre-wrap;word-break:break-all;font-family:inherit;font-size:inherit}
  @media print{@page{size:80mm auto;margin:2mm}body{padding:0}}
</style></head><body>
<pre>${combined.replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'})[c])}</pre>
<script>window.onload=function(){window.print();setTimeout(function(){window.close();},1200);}<\/script>
</body></html>`;

        const w = window.open('', '_blank', 'width=340,height=600,scrollbars=yes');
        if (w) { w.document.write(html); w.document.close(); }
        else   { alert('Popup blocked — allow popups to print.'); }
    }

    // ── Plain text builders (browser fallback) ────────────────────────────────
    function textReceipt(t) {
        const w = PAPER_WIDTH;
        const m = v => (parseFloat(v)||0).toFixed(2);
        const p = (l,r,w) => { l=String(l||''); r=String(r||''); return l+' '.repeat(Math.max(1,w-l.length-r.length))+r; };
        let out = [], sub = 0;
        out.push('='.repeat(w));
        out.push(PROP_NAME.toUpperCase().padStart(Math.floor((w + PROP_NAME.length) / 2)));
        out.push('='.repeat(w));
        if (t.order_no) out.push('Order:   ' + t.order_no);
        if (t.table)    out.push('Table:   ' + t.table);
        if (t.guest)    out.push('Guest:   ' + t.guest);
        if (t.cashier)  out.push('Cashier: ' + t.cashier);
        out.push('Date:    ' + (t.date || new Date().toLocaleString()));
        out.push('-'.repeat(w));
        (t.items||[]).forEach(item => {
            if ((item.status||'active') !== 'active') return;
            const qty = parseFloat(item.qty||1)||1;
            sub += parseFloat(item.amount||0);
            const label = (qty!==1?(Number.isInteger(qty)?qty:qty.toFixed(2))+' x ':'')+(item.name||'Item');
            out.push(p(label.substring(0,w-10), m(item.amount), w));
            if (item.notes) out.push('  * '+item.notes);
        });
        out.push('-'.repeat(w));
        out.push(p('TOTAL', m(t.total||sub), w));
        (t.payments||[]).forEach(pay => {
            out.push(p((pay.method||'').toUpperCase(), m(pay.amount||0), w));
            if (pay.reference) out.push('  Ref: '+pay.reference);
        });
        out.push('='.repeat(w));
        out.push('Thank you!');
        return out.join('\n');
    }
    function textKitchen(t) {
        const w = PAPER_WIDTH;
        const out = [];
        out.push('='.repeat(w));
        out.push('*** ' + (t.station||'KITCHEN').toUpperCase() + ' TICKET ***');
        out.push('='.repeat(w));
        if (t.order_no) out.push('ORDER:  ' + t.order_no);
        if (t.table)    out.push('Table:  ' + t.table);
        if (t.covers)   out.push('Covers: ' + t.covers);
        out.push('Time:   ' + (t.date || new Date().toLocaleTimeString()));
        out.push('='.repeat(w));
        (t.items||[]).forEach(item => {
            const qty = parseFloat(item.qty||1)||1;
            out.push(' '+(Number.isInteger(qty)?qty:qty.toFixed(1))+' x '+(item.name||'Item').toUpperCase());
            if (item.seat)  out.push('   Seat:  '+item.seat);
            if (item.notes) out.push('   ** '+item.notes);
        });
        out.push('='.repeat(w));
        return out.join('\n');
    }

    // ── Toast ─────────────────────────────────────────────────────────────────
    function toast(message, type) {
        type = type || 'info';
        let el = document.getElementById('thermalPrintToast');
        if (!el) {
            el = document.createElement('div');
            el.id = 'thermalPrintToast';
            el.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:99999;max-width:320px;'
                + 'padding:10px 14px;border-radius:10px;font-size:13px;font-weight:700;'
                + 'box-shadow:0 8px 24px rgba(0,0,0,.22);transition:opacity .3s;opacity:0;pointer-events:none;';
            document.body.appendChild(el);
        }
        const colors = { success:['#166534','#dcfce7'], error:['#991b1b','#fee2e2'], info:['#1e3a8a','#dbeafe'] };
        const [fg, bg] = colors[type] || colors.info;
        el.style.background = bg;
        el.style.color      = fg;
        el.textContent      = message;
        el.style.opacity    = '1';
        clearTimeout(el._t);
        el._t = setTimeout(() => { el.style.opacity = '0'; }, 3500);
    }

    // ── Main dispatcher ───────────────────────────────────────────────────────
    async function printTicket(ip, xml, fallbackText, title) {
        toast('Sending to printer…', 'info');
        const result = await sendEpos(ip, xml);
        if (result.ok) {
            toast('✓ Printed successfully', 'success');
        } else {
            console.warn('ePOS failed:', result.error);
            toast('Printer error — opening browser print…', 'error');
            browserPrint([fallbackText], title);
        }
    }

    // ── Public API ────────────────────────────────────────────────────────────
    return {

        receipt(orderData) {
            const t   = { date: new Date().toLocaleString(), ...orderData };
            const xml = buildReceiptXml(t);
            return printTicket(PRINTER_IP, xml, textReceipt(t), 'Receipt');
        },

        split(bills) {
            bills.forEach(b => {
                const t   = { date: new Date().toLocaleString(), ...b };
                const xml = buildSplitXml(t);
                printTicket(PRINTER_IP, xml, textReceipt(t), 'Split Bill');
            });
        },

        kitchen(tickets) {
            tickets.forEach(t => {
                t.date = t.date || new Date().toLocaleString();
                const ip  = (t.station || '').toLowerCase() === 'bar' ? BAR_IP : KITCHEN_IP;
                const xml = buildKitchenXml(t);
                printTicket(ip, xml, textKitchen(t), 'Kitchen Ticket');
            });
        },

        browserOnly(texts, title) {
            browserPrint(Array.isArray(texts) ? texts : [texts], title || 'Print');
        },
    };

})();