// src/mqtt/context.js
function createMqttContext({ db, config, emitter, sendPushToUser }) {
    // --------------------
    // Helpers
    // --------------------
    const toNum = (v) => {
        if (v === null || v === undefined || v === '') return 0;
        const n = typeof v === 'number' ? v : parseFloat(v);
        return Number.isFinite(n) ? n : 0;
    };

    const roundN = (v, n) => {
        const num = toNum(v);
        const m = Math.pow(10, n);
        return Math.round(num * m) / m;
    };

    // ✅ mantém round1 por compatibilidade (legacy usa)
    const round1 = (v) => roundN(v, 1);

    // ✅ 2 casas (para DB/telemetria)
    const round2 = (v) => roundN(v, 2);

    // W -> kW com 2 casas
    const wToKw2 = (w) => round2(toNum(w) / 1000);

    // ✅ arrays com 2 casas (evita lixo e mantém consistência)
    const toArr3 = (v) => {
        if (Array.isArray(v)) return [round2(v[0]), round2(v[1]), round2(v[2])];
        if (v && typeof v === 'object') return [round2(v.l1), round2(v.l2), round2(v.l3)];
        return [0, 0, 0];
    };

    const toArr3Kw2 = (v) => {
        if (Array.isArray(v)) return [wToKw2(v[0]), wToKw2(v[1]), wToKw2(v[2])];
        if (v && typeof v === 'object') return [wToKw2(v.l1), wToKw2(v.l2), wToKw2(v.l3)];
        return [0, 0, 0];
    };

    function safeJsonParse(buf) {
        try {
            return JSON.parse(buf.toString());
        } catch {
            return null;
        }
    }

    function shallowEqual(a, b) {
        if (a === b) return true;
        if (!a || !b) return false;

        const ak = Object.keys(a);
        const bk = Object.keys(b);
        if (ak.length !== bk.length) return false;

        for (const k of ak) {
            const av = a[k];
            const bv = b[k];

            if (Array.isArray(av) || Array.isArray(bv)) {
                if (!Array.isArray(av) || !Array.isArray(bv)) return false;
                if (av.length !== bv.length) return false;
                for (let i = 0; i < av.length; i++) {
                    if (av[i] !== bv[i]) return false;
                }
            } else if (av !== bv) {
                return false;
            }
        }

        return true;
    }

    function inferStateCode(raw) {
        const s = String(raw || '').trim();
        if (!s) return '';
        return s.split(/\s+/)[0] || '';
    }

    function getStatusFromStateCode(code) {
        const map = {
            A: '🔌 Not Conn.',
            A1: '🔌 Not Conn.',
            B1: '🟡 Unauth.',
            B2: '🟢 Ready',
            C1: '⚡ Wait',
            C2: '⚡ Charging',
            D1: '💨 Vent (req)',
            D2: '💨 Vent',
            E: '❌ CP Error',
            F: '⚠️ Fault',
        };
        return map[code] || '❓ Unknown';
    }

    function stripUndef(obj) {
        return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
    }

    // --------------------
    // Caches
    // --------------------
    const lastDbStateByChargerId = new Map(); // chargerId -> { ...dbFields }
    const lastMetaByChargerId = new Map(); // chargerId -> { status, stateCode, rawStatus }
    const lastTotalEnergyByChargerId = new Map(); // chargerId -> totalEnergy (kWh)
    const sessionStartEnergyByChargerId = new Map(); // chargerId -> totalEnergy at session start
    const lastEnabled = {}; // chargerId -> boolean

    const chargerCache = new Map(); // mqttTopic -> { charger, fetchedAt }
    const CHARGER_CACHE_TTL_MS = config.chargerCacheTtlMs;

    // --------------------
    // DB helpers
    // --------------------
    async function getChargerByMqttTopic(mqttTopic) {
        const cached = chargerCache.get(mqttTopic);
        const now = Date.now();

        if (cached && now - cached.fetchedAt < CHARGER_CACHE_TTL_MS) {
            return cached.charger;
        }

        const charger = await db('chargers').where({ mqtt_topic: mqttTopic }).first();
        if (charger) chargerCache.set(mqttTopic, { charger, fetchedAt: now });
        return charger;
    }

    async function updateChargerDbIfChanged(chargerId, partialUpdate) {
        const prev = lastDbStateByChargerId.get(chargerId) || null;

        const cleanPartial = stripUndef(partialUpdate);
        const next = { ...(prev || {}), ...cleanPartial };

        const changed = !prev || !shallowEqual(prev, next);
        if (!changed) return false;

        await db('chargers').where({ id: chargerId }).update(cleanPartial);
        lastDbStateByChargerId.set(chargerId, next);
        return true;
    }

    return {
        db,
        config,
        emitter,
        sendPushToUser,

        // helpers
        toNum,
        round1, // ✅ não remover (legacy)
        round2,
        wToKw2,
        toArr3,
        toArr3Kw2,
        safeJsonParse,
        shallowEqual,
        inferStateCode,
        getStatusFromStateCode,
        stripUndef,

        // caches
        lastDbStateByChargerId,
        lastMetaByChargerId,
        lastTotalEnergyByChargerId,
        sessionStartEnergyByChargerId,
        lastEnabled,

        // db helpers
        getChargerByMqttTopic,
        updateChargerDbIfChanged,
    };
}

module.exports = { createMqttContext };
