/*!
 * subnet-bargraph.js — barre de remplissage d'un subnet (inspiré Avolites Titan).
 * 100 % client, 3 couches pures : métier → layout → rendu.
 * ES5 / IIFE, aligné sur le style de subnet.php. Aucune dépendance.
 */
window.SubnetBargraph = (function () {
    'use strict';

    // ── Helpers IP (entier non signé ↔ pointé) ──
    function intToIp(n) {
        n = n >>> 0;
        return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join('.');
    }

    // Nombre de bits d'hôte pour couvrir `count` adresses (arrondi sup. puissance de 2).
    function hostBitsFor(count) {
        var bits = 0, size = 1;
        while (size < count) { size *= 2; bits++; }
        return bits;
    }
    // Masque (pointé) du plus petit bloc CIDR contenant [startInt, endInt].
    function maskForCount(startInt, endInt) {
        var bits = hostBitsFor(endInt - startInt + 1);
        var mask = bits >= 32 ? 0 : (0xFFFFFFFF << bits) >>> 0;
        return intToIp(mask);
    }
    // Plus grand CIDR aligné entièrement contenu dans [startInt, endInt].
    // On teste les tailles décroissantes ; pour chaque taille, la 1re base alignée ≥ start
    // dont la fin ≤ end convient. Retourne "base/prefix".
    function largestAlignedCidr(startInt, endInt) {
        var span = endInt - startInt + 1;
        var bits = hostBitsFor(span); // borne haute de recherche
        for (; bits >= 0; bits--) {
            var size = Math.pow(2, bits);
            var mask = bits >= 32 ? 0 : (0xFFFFFFFF << bits) >>> 0;
            // 1re base alignée ≥ startInt
            var base = (startInt & mask) >>> 0;
            if (base < startInt) base = (base + size) >>> 0;
            if (base >= startInt && (base + size - 1) <= endInt) {
                return intToIp(base) + '/' + (32 - bits);
            }
        }
        // fallback (ne devrait pas arriver) : /32 sur start
        return intToIp(startInt) + '/32';
    }

    // Fusionne les réservations contiguës de MÊME type (toutes "reservation" ici),
    // puis injecte les pools comme blocs propres. Retourne la liste triée par startInt.
    function computeBlocks(rows, subnet) {
        var resv = [];
        for (var i = 0; i < rows.length; i++) {
            var r = rows[i];
            var ipInt = r.ip_int >>> 0;
            if (!ipInt) continue; // sans IP fixe → vit dans le pool
            if (ipInt < subnet.firstUsable || ipInt > subnet.lastUsable) {
                if (window.console) console.warn('bargraph: réservation hors CIDR ignorée', r);
                continue;
            }
            resv.push(r);
        }
        resv.sort(function (a, b) { return (a.ip_int >>> 0) - (b.ip_int >>> 0); });

        var blocks = [];
        var cur = null;
        for (var j = 0; j < resv.length; j++) {
            var row = resv[j];
            var ip = row.ip_int >>> 0;
            if (cur && ip === cur.endInt + 1) {
                cur.endInt = ip;
                cur.count++;
                cur.hostIds.push(row.host_id);
                accGroup(cur.groups, row.group_name);
                if (row.short_lease) cur.shortLeaseCount++;
            } else {
                cur = {
                    type: 'reservation',
                    startInt: ip, endInt: ip, count: 1,
                    groups: {}, shortLeaseCount: row.short_lease ? 1 : 0,
                    hostIds: [row.host_id]
                };
                accGroup(cur.groups, row.group_name);
                blocks.push(cur);
            }
        }

        // Pools
        if (subnet.dynStartInt != null && subnet.dynEndInt != null) {
            blocks.push(poolBlock('dynamic', subnet.dynStartInt, subnet.dynEndInt));
        }
        if (subnet.shortStartInt != null && subnet.shortEndInt != null) {
            blocks.push(poolBlock('short-pool', subnet.shortStartInt, subnet.shortEndInt));
        }

        blocks.sort(function (a, b) { return a.startInt - b.startInt; });
        return blocks;
    }
    function accGroup(groups, name) {
        var k = name || '';
        groups[k] = (groups[k] || 0) + 1;
    }
    function poolBlock(type, startInt, endInt) {
        return {
            type: type, startInt: startInt >>> 0, endInt: endInt >>> 0,
            count: (endInt >>> 0) - (startInt >>> 0) + 1,
            groups: {}, shortLeaseCount: 0, hostIds: []
        };
    }

    // Trous = intervalles [firstUsable, lastUsable] non couverts par les blocs (déjà triés).
    function computeHoles(blocks, subnet) {
        var holes = [];
        var cursor = subnet.firstUsable;
        for (var i = 0; i < blocks.length; i++) {
            var b = blocks[i];
            if (b.startInt > cursor) {
                holes.push(makeHole(cursor, b.startInt - 1));
            }
            if (b.endInt + 1 > cursor) cursor = b.endInt + 1;
        }
        if (cursor <= subnet.lastUsable) {
            holes.push(makeHole(cursor, subnet.lastUsable));
        }
        return holes;
    }
    function makeHole(startInt, endInt) {
        return {
            startInt: startInt, endInt: endInt,
            count: endInt - startInt + 1,
            largestCidr: largestAlignedCidr(startInt, endInt)
        };
    }

    // Projette blocs + trous en segments {kind, ref, leftPct, widthPct}.
    // Plancher minPx sur les BLOCS occupés uniquement, puis renormalisation globale (Σ=100).
    function layoutSegments(blocks, holes, subnet, opts) {
        opts = opts || {};
        var barWidthPx = opts.barWidthPx || 0;
        var minPx = opts.minPx != null ? opts.minPx : 5;
        var minPct = barWidthPx > 0 ? (minPx / barWidthPx * 100) : 0;

        // 1) Séquence ordonnée blocs+trous
        var items = [];
        blocks.forEach(function (b) { items.push({ kind: 'block', ref: b, count: b.count }); });
        holes.forEach(function (h) { items.push({ kind: 'hole', ref: h, count: h.count }); });
        items.sort(function (a, b) { return a.ref.startInt - b.ref.startInt; });

        var total = subnet.total || 1;

        // 2) Largeur idéale
        items.forEach(function (it) { it.widthPct = it.count / total * 100; });

        // 3) Plancher sur les blocs
        if (minPct > 0) {
            items.forEach(function (it) {
                if (it.kind === 'block' && it.widthPct < minPct) {
                    it.floored = true;
                    it.widthPct = minPct;
                }
            });
            // 4) Renormalisation globale : retirer le surplus aux segments non planchés,
            //    proportionnellement, en itérant si un segment repasserait sous son plancher.
            renormalize(items, minPct);
        }

        // 5) Positions cumulées
        var left = 0, out = [];
        items.forEach(function (it) {
            out.push({ kind: it.kind, ref: it.ref, leftPct: left, widthPct: it.widthPct });
            left += it.widthPct;
        });
        return out;
    }
    // Ramène Σ widthPct à 100 en réduisant proportionnellement les segments "réductibles"
    // (au-dessus de leur plancher). Un bloc ne descend jamais sous minPct.
    function renormalize(items, minPct) {
        for (var pass = 0; pass < 20; pass++) {
            var sum = 0; items.forEach(function (it){ sum += it.widthPct; });
            var excess = sum - 100;
            if (Math.abs(excess) < 1e-6) return;
            var reducible = 0;
            items.forEach(function (it) {
                var floor = (it.kind === 'block') ? minPct : 0;
                if (it.widthPct > floor) reducible += (it.widthPct - floor);
            });
            if (reducible <= 1e-9) return; // rien à réduire (barre saturée de planchers)
            var ratio = excess / reducible;
            items.forEach(function (it) {
                var floor = (it.kind === 'block') ? minPct : 0;
                if (it.widthPct > floor) {
                    var room = it.widthPct - floor;
                    it.widthPct -= room * ratio;
                    if (it.widthPct < floor) it.widthPct = floor;
                }
            });
        }
    }

    return {
        intToIp: intToIp,
        maskForCount: maskForCount,
        largestAlignedCidr: largestAlignedCidr,
        computeBlocks: computeBlocks,
        computeHoles: computeHoles,
        layoutSegments: layoutSegments
    };
}());
