function create_gauge_enhanced(container, modifier, configin) {
    var config = {
        'size': $('#' + container).width() * 1.25,
        'min': 0,
        'max': 100,
        'fontsize': 12,
        'minorTicks': 5,
    }

    for (var attrname in configin) {
        config[attrname] = configin[attrname];
    }

    var gauge = new HighchartsGauge(container, config, modifier);
    return gauge;
}

function HighchartsGauge(placeholderName, configuration, modifier) {
    this.placeholderName = placeholderName;
    this.modifier = modifier;
    this.config = configuration;
    this.chart = null;
    this.lastValue = configuration && typeof configuration.current === 'number'
        ? configuration.current : 0;
    this._resizeBound = false;
    this._ro = null;
    this._onWinResize = null;

    var self = this;

    this.render = function () {
        var $containerEl = $('#' + this.placeholderName);
        if ($containerEl.length === 0) { return; }

        // Bind a resize observer once so browser resizes also re-render the gauge
        if (!this._resizeBound) {
            if (window.ResizeObserver) {
                this._ro = new ResizeObserver(function () { self.render(); });
                this._ro.observe($containerEl[0]);
            } else {
                this._onWinResize = function () { self.render(); };
                window.addEventListener('resize', this._onWinResize);
            }
            this._resizeBound = true;
        }

        var containerWidth = $containerEl.width() || $containerEl.parent().width() || 260;
        var containerHeight = $containerEl.height() || $containerEl.parent().height() || containerWidth;

        // Scale factor based on a baseline size so text and strokes scale with the chart
        var baseSize = 260;
        var minSide = Math.max(10, Math.min(containerWidth, containerHeight)); // prevent zero
        var s = Math.max(0.6, Math.min(2.5, minSide / baseSize));
        var tickLen = Math.round(10 * s);
        var minorTickLen = Math.round(5 * s);
        var tickW = Math.max(1, Math.round(1 * s));
        var minorTickW = Math.max(1, Math.round(1 * s));
        var labelFontPx = Math.max(10, Math.round(12 * s));
        var titleFontPx = Math.max(12, Math.round(14 * s));
        var dataLabelFontPx = Math.max(12, Math.round(16 * s));
        var uomFontPx = Math.max(10, Math.round(12 * s));
        var labelDistance = Math.round(-20 * s);
        var bandThickness = Math.max(2, Math.round(3 * s));
        var dialBaseWidth = Math.max(3, Math.round(4 * s));
        var pivotRadius = Math.max(3, Math.round(4 * s));
        var tickPxInterval = Math.max(24, Math.round(30 * s));

        // Chart host
        var chartId = this.placeholderName + '_hc';
        var $chartHost = $('#' + chartId);
        if ($chartHost.length === 0) {
            $containerEl.empty().append('<div id="' + chartId + '"></div>');
            $chartHost = $('#' + chartId);
        }
        var chartSide = minSide;
        $chartHost.css({ width: chartSide + 'px', height: chartSide + 'px' });

        // Heuristic: decide when to prefer compact axis labels (e.g., 6000 -> 6k)
        function fmtCompact(n) {
            var abs = Math.abs(n);
            if (abs >= 1e9) return (n / 1e9).toFixed(abs >= 1e10 ? 0 : 1) + 'G';
            if (abs >= 1e6) return (n / 1e6).toFixed(abs >= 1e7 ? 0 : 1) + 'M';
            if (abs >= 1000) return (n / 1000).toFixed(abs >= 10000 ? 0 : 1) + 'k';
            return Highcharts.numberFormat(n, 0);
        }

        // Estimate how many labels can fit on the 300° arc
        var radius = chartSide * 0.5 * 0.86;
        var arcLen = radius * Math.PI * (300 / 180);
        var approxTickCount = Math.max(3, Math.floor(arcLen / tickPxInterval));
        var digitsMax = Math.max(
            ('' + Math.round(Math.abs(this.config.max || 100))).length,
            ('' + Math.round(Math.abs(this.config.min || 0))).length
        );
        var approxLabelWidthPx = Math.max(16, Math.round(labelFontPx * (digitsMax * 0.6 + 1.2)));

        // Bias: higher => switch to 6k earlier (default 0.80; try 0.85 to be more aggressive)
        var COMPACT_BIAS = 0.80;
        var slotPx = arcLen / Math.max(1, approxTickCount);
        var preferCompact = approxLabelWidthPx > (slotPx * COMPACT_BIAS);

        // ---- DYNAMIC LABEL DENSITY (prevents overlap) ----
        // Approximate arc length available for labels: 300° of a circle
        var radius = chartSide * 0.5 * 0.86;                      // a bit inside the pane
        var arcLength = radius * Math.PI * (300 / 180);
        // Estimate label width (digits + a bit of padding)
        var maxAbs = Math.max(Math.abs(this.config.max || 100), Math.abs(this.config.min || 0));
        var maxLabelChars = ('' + Math.round(maxAbs)).length;
        var approxLabelWidth = Math.max(16, Math.round(labelFontPx * (maxLabelChars <= 3 ? 0.7 : 0.6) + 8));
        var maxLabels = Math.floor(arcLength / approxLabelWidth);
        var tickAmount = Math.max(3, Math.min(11, maxLabels));    // clamp between 3 and 11

        // Rebuild plot bands
        var plotBands = [];
        if (this.config.greenZones) {
            for (var i = 0; i < this.config.greenZones.length; i++) {
                plotBands.push({
                    color: 'var(--color-okay)',
                    from: this.config.greenZones[i].from,
                    thickness: bandThickness,
                    to: this.config.greenZones[i].to
                });
            }
        }
        if (this.config.yellowZones) {
            for (var j = 0; j < this.config.yellowZones.length; j++) {
                plotBands.push({
                    color: 'var(--color-warning)',
                    from: this.config.yellowZones[j].from,
                    thickness: bandThickness,
                    to: this.config.yellowZones[j].to
                });
            }
        }
        if (this.config.redZones) {
            for (var k = 0; k < this.config.redZones.length; k++) {
                plotBands.push({
                    color: 'var(--color-critical)',
                    from: this.config.redZones[k].from,
                    thickness: bandThickness,
                    to: this.config.redZones[k].to
                });
            }
        }

        const formatUpTo = (n, max = 3) =>
            new Intl.NumberFormat(undefined, {
                useGrouping: true,
                minimumFractionDigits: 0,
                maximumFractionDigits: max
            }).format(n);

        // Destroy any existing chart before creating a new one (important for geometry recalculation)
        if (this.chart) {
            try { this.chart.destroy(); } catch (e) {}
            this.chart = null;
        }

        // --- Digit-aware tick density helpers -------------------------
        function countIntegerDigits(n) {
            n = Math.floor(Math.abs(n || 0));
            return n === 0 ? 1 : Math.floor(Math.log10(n)) + 1;
        }

        // “How wide is a label?” (rough estimate, px) – assumes ~0.62em/char.
        function estimateLabelWidthPx(chars, fontPx) {
            // include thousands separators (e.g., 10,000 => 6 chars)
            var separators = Math.max(0, Math.floor((chars - 1) / 3));
            var totalChars = chars + separators;
            return Math.ceil(totalChars * fontPx * 0.62);
        }

        // Longest label (in digits) we expect on the axis
        var minVal = this.config.min || 0;
        var maxVal = this.config.max || 100;
        var maxDigits = Math.max(countIntegerDigits(minVal), countIntegerDigits(maxVal));
        var estLabelW = estimateLabelWidthPx(maxDigits, labelFontPx);

        // Effective label radius (negative distance brings labels inward)
        var radiusPx = Math.max(40 * s, (chartSide / 2) + labelDistance);

        // Arc length for the 300° gauge sweep
        var arcLenPx = radiusPx * (Math.PI * 5 / 3);

        // Max labels that can fit without overlap, with a small safety gap
        var maxLabelsNoOverlap = Math.max(3, Math.floor(arcLenPx / (estLabelW * 1.10)));

        // Bound the tick count to a sensible range for gauges
        var smartTickAmount = Math.max(3, Math.min(11, maxLabelsNoOverlap));

        // Make the pixel interval scale up with digits as a secondary guard
        tickPxInterval = Math.max(
            tickPxInterval,
            Math.round(estLabelW * 1.10) // ensure ticks are at least one label wide
        );

        var chartConfig = {
            chart: {
                backgroundColor: 'transparent',
                height: chartSide,
                reflow: true,
                width: chartSide,
                spacing: [0, 0, 0, 0],
                margin: [0, 0, 0, 0],
                plotBackgroundColor: null,
                plotBorderWidth: 0,
                plotShadow: false,
                type: 'gauge'
            },
            title: {
                style: {
                    color: 'var(--gauge-enhanced-foreground-color)',
                    fontWeight: 'bold',
                    fontSize: titleFontPx + 'px'
                },
                text: this.config.label || '',
                floating: true,
                align: 'center',
                y: Math.round(80 * s),
                verticalAlign: 'top',
                margin: 0
            },
            pane: {
                startAngle: -150,
                endAngle: 150,
                size: '100%',
                background: {
                    backgroundColor: 'var(--gauge-enhanced-foreground-color)',
                    shape: 'arc',
                    outerRadius: '100%',
                    innerRadius: '100%'
                }
            },
            yAxis: {
                lineWidth: 0,
                max: this.config.max || 100,
                min: this.config.min || 0,
                minorTickColor: 'var(--gauge-enhanced-foreground-color)',
                minorTicks: this.config.minorTicks,
                minorTickInterval: 'auto',
                minorTickLength: minorTickLen,
                minorTickPosition: 'inside',
                minorTickWidth: minorTickW,
                plotBands: plotBands,
                tickColor: 'var(--gauge-enhanced-foreground-color)',
                tickLength: tickLen,
                // Digit-aware spacing:
                tickAmount: smartTickAmount,
                tickPixelInterval: tickPxInterval,
                tickPosition: 'inside',
                tickWidth: tickW,

                labels: {
                    distance: labelDistance,
                    rotation: 'auto',
                    // Optional: normalize axis label decimals for large ranges
                    formatter: function () {
                        var span = (maxVal - minVal);
                        var decimals = span < 10 ? 1 : 0; // 0 decimals for big spans
                        return Highcharts.numberFormat(this.value, decimals);
                    },
                    style: {
                        color: 'var(--gauge-enhanced-foreground-color)',
                        fontSize: labelFontPx + 'px'
                    }
                }
            },
            tooltip: {
                useHTML: true,
                valueDecimals: 3,
                formatter: function () {
                    const v = this.y;
                    const bands = this.series.yAxis?.options?.plotBands || [];
                    let color;
                    for (let i = 0; i < bands.length; i++) {
                        if (v >= bands[i].from && v <= bands[i].to) { color = bands[i].color; break; }
                    }
                    const max = this.series.tooltipOptions?.valueDecimals ?? 3;
                    const suffix = this.series.tooltipOptions?.valueSuffix || '';
                    const marker = color
                        ? `<span style="
                            background: ${color};
                            border: 1px solid color-mix(in oklch, ${color} 90%, gray 70%);
                            border-radius: 50%;
                            display: inline-block;
                            height: 1rem;
                            margin-right: 0.5rem;
                            width: 1rem;
                        "></span>` : '';
                    return `${marker}${formatUpTo(v, max)}${suffix}`;
                }
            },
            series: [{
                name: this.config.label || 'Value',
                data: [this.config.current || 0],
                tooltip: {
                    valueDecimals: 3,
                    valueSuffix: this.config.uom ? ` ${this.config.uom}` : ''
                },
                dataLabels: {
                    useHTML: true,
                    y: Math.round(40 * s),
                    formatter: function () {
                        const max = this.series.tooltipOptions?.valueDecimals ?? 3;
                        const uom = (this.series.tooltipOptions?.valueSuffix || '').trim();
                        return `
                            <div style="color: var(--gauge-enhanced-foreground-color);">
                                <span style="font-size: ${dataLabelFontPx}px;">${formatUpTo(this.y, max)}</span>
                                ${uom ? `<span style="
                                    color: color-mix(in oklch, var(--gauge-enhanced-foreground-color) 90%, gray 70%);
                                    font-size: ${uomFontPx}px;
                                    margin-left: 4px;
                                ">${uom}</span>` : ''}
                            </div>
                        `;
                    }
                },
                dial: {
                    radius: '75%',
                    backgroundColor: 'var(--gauge-enhanced-foreground-color)',
                    baseWidth: dialBaseWidth,
                    baseLength: '0%',
                    rearLength: '8%'
                },
                pivot: {
                    backgroundColor: 'var(--gauge-enhanced-foreground-color)',
                    radius: pivotRadius
                }
            }],
            credits: { enabled: false },
            exporting: { enabled: false }
        };

        this.chart = Highcharts.chart(chartId, chartConfig);

        // Post-render overlap check: if labels still overlap, relax tickAmount
        var self = this;
        function tickLabelsOverlap(chart) {
            var axis = chart.yAxis && chart.yAxis[0];
            if (!axis) return false;
            var boxes = [];
            Highcharts.objectEach(axis.ticks || {}, function (t) {
                if (t && t.label && t.label.element) {
                    boxes.push(t.label.element.getBBox());
                }
            });
            if (boxes.length < 2) return false;
            boxes.sort(function (a, b) { return a.x - b.x; });
            for (var i = 1; i < boxes.length; i++) {
                if (boxes[i - 1].x + boxes[i - 1].width > boxes[i].x - 2) return true;
            }
            return false;
        }

        setTimeout(function () {
            if (!self.chart) return;
            var axis = self.chart.yAxis[0];
            var amount = axis && axis.options && axis.options.tickAmount || smartTickAmount;

            var guard = 0;
            while (guard++ < 5 && amount > 3 && tickLabelsOverlap(self.chart)) {
                amount = Math.max(3, amount - 1);
                axis.update({ tickAmount: amount }, false);
                self.chart.redraw();
            }
        }, 0);

        // If we've already shown a value, bring the needle to it immediately
        try {
            if (typeof this.lastValue === 'number') {
                this.chart.series[0].points[0].update(this.lastValue, false, false);
                this.chart.redraw();
            }
        } catch (e) {}

        return this.chart;
    };

    this.redraw = function (value, transitionDuration) {
        if (!this.chart) {
            this.render();
        }

        var actualValue = value;
        if (this.modifier !== undefined) {
            actualValue = this.modifier * value;
        }
        this.lastValue = actualValue;

        // Update series color to match the active band so default tooltip marker also matches
        if (this.chart && this.chart.yAxis && this.chart.yAxis[0]) {
            var bands = (this.chart.yAxis[0].options && this.chart.yAxis[0].options.plotBands) || [];
            var zoneColor = 'var(--gauge-enhanced-foreground-color)';
            for (var i = 0; i < bands.length; i++) {
                if (actualValue >= bands[i].from && actualValue <= bands[i].to) { zoneColor = bands[i].color; break; }
            }
            try {
                this.chart.series[0].update({ color: zoneColor }, false);
            } catch (e) { }
        }

        // Update the gauge value with animation
        try {
            var point = this.chart.series[0].points[0];
            point.update(actualValue, true, true);
        } catch (e) {}
    };
}

function load_gauge_hosts_enhanced(currentHost = '') {
    var url = base_url + "includes/dashlets/gauges_enhanced/getdata.php";
    $('.host-loader').show();

    // Get all services with data
    $.ajax({
        "url": url,
        data: { 'cmd': 'noperfdata' },
        "success": function (result) {

            $('.host-loader').hide();

            hosts = result;

            var hostslist = "";
            if (currentHost) {
                hostslist += "<option value='" + currentHost + "' selected>" + currentHost + "</option>";
            } else {
                hostslist += "<option selected></option>";
            }

            $(hosts).each(function (k, v) {
                if (v !== currentHost) {
                    hostslist += "<option value='" + v + "'>" + v + "</option>";
                }
            });
            $('#gauges_enhanced_form_name').html(hostslist);
        }
    });
}

function getGaugesEnhancedJson(currentHost = '', currentService = '') {
    var url = base_url + "includes/dashlets/gauges_enhanced/getdata.php";
    var host = $('#gauges_enhanced_form_name').val();
    if (currentHost) {
        host = currentHost;
    }

    if (host == '') {
        $('#gauges_enhanced_form_services').html("<option selected></option>");
        $('#gauges_enhanced_form_services').prop('disabled', true);
        $('#empty-services').hide();
        $('#gauges_enhanced_form_services').show();
        return;
    }

    // Set loading...
    $('.service-loader').show();
    $('#gauges_enhanced_form_ds').prop('disabled', true);

    // Get all services with data
    $.ajax({
        "url": url,
        data: { 'host': host, 'cmd': 'noperfdata' },
        "success": function (result) {

            $('.service-loader').hide();

            // If services are empty
            if (result.length == 0) {
                $('#gauges_enhanced_form_services').hide();
                $('#empty-services').show();
                return;
            } else {
                $('#gauges_enhanced_form_services').show();
                $('#empty-services').hide();
            }

            services = result;

            var servicelist = "";
            if (currentService) {
                servicelist += "<option value='" + currentService + "' selected>" + currentService + "</option>";
            } else {
                servicelist = "<option selected></option>";
            }

            $(services).each(function (k, v) {
                if (v !== currentService) {
                    servicelist += "<option value='" + v + "'>" + v + "</option>";
                }
            });
            $('#gauges_enhanced_form_services').html(servicelist);

            // Remove disabled
            $('#gauges_enhanced_form_services').prop('disabled', false);
        }
    });
}

function getGaugesEnhancedServices(currentHost = '', currentService = '', currentDatasource = '') {
    var url = base_url + "includes/dashlets/gauges_enhanced/getdata.php";
    var host = $('#gauges_enhanced_form_name').val();
    var service = $("#gauges_enhanced_form_services").val();

    if (service == '') {
        $('#gauges_enhanced_form_ds').html("<option selected></option>");
        $('#gauges_enhanced_form_ds').prop('disabled', true);
        $('#empty-ds').hide();
        $('#gauges_enhanced_form_ds').show();
        return;
    }

    // Set loading...
    $('.ds-loader').show();

    // Get all services with data
    $.ajax({
        "url": url,
        data: { 'host': host, 'service': service },
        dataType: "json",
        "success": function (result) {

            $('.ds-loader').hide();

            // If services are empty
            if (!result || Object.keys(result).length == 0) {
                $('#gauges_enhanced_form_ds').hide();
                $('#empty-ds').show();
                return;
            } else {
                $('#gauges_enhanced_form_ds').show();
                $('#empty-ds').hide();
            }

            if (currentHost) {
                host = currentHost;
            }
            if (currentService) {
                service = currentService;
            }
            datasources = result[host][service];

            var dslist = "";
            if (currentDatasource) {
                dslist += "<option value='" + currentDatasource + "' selected>" + currentDatasource + "</option>";
            } else {
                dslist = "<option selected></option>";
            }

            for (ds in datasources) {
                if (ds !== currentDatasource) {
                    dslist += "<option value='" + ds + "'>" + ds + "</option>";
                }
            }

            $('#gauges_enhanced_form_ds').html(dslist);

            // Remove disabled
            $('#gauges_enhanced_form_ds').prop('disabled', false);
        },
        "error": function (xhr, status, error) {
            $('.ds-loader').hide();
            console.error('Failed to load datasources:', error);
            $('#gauges_enhanced_form_ds').hide();
            $('#empty-ds').show();
        }
    });
}
