/**
 * Hardware scale adapter (Web Serial API when available).
 */

const DEFAULT_STABLE_MS = 450;
const DEFAULT_STABLE_DELTA_KG = 0.002;

/**
 * Parse a line from common scale formats → kg.
 * @param {string} line
 * @returns {number|null}
 */
export function parseScaleLineToKg(line) {
    if (!line || typeof line !== 'string') return null;
    const s = line.replace(/\u0000/g, '').trim();
    if (!s) return null;
    const m = s.match(/([-+]?\d+(?:[.,]\d+)?)\s*(kg|g|KG|G)\b/i);
    if (m) {
        const n = parseFloat(m[1].replace(',', '.'));
        if (Number.isNaN(n)) return null;
        const u = m[2].toLowerCase();
        return u === 'g' ? n / 1000 : n;
    }
    const m2 = s.match(/([-+]?\d+(?:[.,]\d+)?)/);
    if (!m2) return null;
    const n2 = parseFloat(m2[1].replace(',', '.'));
    if (Number.isNaN(n2)) return null;
    if (n2 > 50) return n2 / 1000;
    return n2;
}

export class ScaleAdapter {
    constructor() {
        this._port = null;
        this._reader = null;
        this._buf = '';
        this._lastKg = null;
        this._stableCandidate = null;
        this._stableSince = 0;
        this.deviceId = null;
    }

    isSupported() {
        return typeof navigator !== 'undefined' && !!navigator.serial;
    }

    async connect(baudRate = 9600) {
        if (!this.isSupported()) throw new Error('serial_unsupported');
        const port = await navigator.serial.requestPort();
        await port.open({ baudRate });
        this._port = port;
        const info = port.getInfo && port.getInfo();
        this.deviceId = info && info.usbVendorId
            ? `usb:${info.usbVendorId}:${info.usbProductId}`
            : 'serial';
        return this.deviceId;
    }

    async disconnect() {
        try {
            if (this._reader) {
                await this._reader.cancel().catch(() => {});
                this._reader = null;
            }
        } catch (_) { /* noop */ }
        try {
            if (this._port) await this._port.close();
        } catch (_) { /* noop */ }
        this._port = null;
        this.deviceId = null;
        this._buf = '';
    }

    /**
     * @param {(r: { weightKg: number, raw: string, stable: boolean }) => void} onReading
     * @param {{ stableMs?: number, stableDeltaKg?: number }} [opts]
     */
    startStream(onReading, opts = {}) {
        if (!this._port || !this._port.readable) throw new Error('port_not_open');
        const stableMs = opts.stableMs != null ? opts.stableMs : DEFAULT_STABLE_MS;
        const stableDeltaKg = opts.stableDeltaKg != null ? opts.stableDeltaKg : DEFAULT_STABLE_DELTA_KG;
        const dec = new TextDecoderStream();
        this._port.readable.pipeTo(dec.writable).catch(() => {});
        this._reader = dec.readable.getReader();
        const tickStable = (kg, raw) => {
            const now = Date.now();
            if (this._stableCandidate == null || Math.abs(kg - this._stableCandidate) > stableDeltaKg) {
                this._stableCandidate = kg;
                this._stableSince = now;
            }
            const stable = now - this._stableSince >= stableMs;
            onReading({ weightKg: kg, raw, stable });
        };
        (async () => {
            try {
                for (;;) {
                    const { value, done } = await this._reader.read();
                    if (done) break;
                    if (value) {
                        this._buf += value;
                        const parts = this._buf.split(/\r?\n/);
                        this._buf = parts.pop() || '';
                        for (const line of parts) {
                            const kg = parseScaleLineToKg(line);
                            if (kg == null) continue;
                            this._lastKg = kg;
                            tickStable(kg, line);
                        }
                    }
                }
            } catch (_) { /* stream ended */ }
        })();
    }

    getLastWeightKg() {
        return this._lastKg;
    }
}
