### ----------------------------------------------------
### -- JavaScript::D3
### -- Licenses: Artistic-2.0
### -- Authors: Anton Antonov
### -- File: lib/JavaScript/D3/CodeSnippets.rakumod
### ----------------------------------------------------
### -- Chunk 3 of 3
]]];
const scaleRanges = $SCALE_RANGES;

const colorScheme = $COLOR_SCHEME; //"Tableau10";
const colorScale = createColorScale(colorScheme, scaleRanges.length);

function isSequentialScheme(schemeName) {
    const sequentialSchemes = [
        "Blues", "Greens", "Greys", "Oranges", "Purples", "Reds",
        "BuGn", "BuPu", "GnBu", "OrRd", "PuBuGn", "PuBu", "PuRd", "RdPu", "YlGnBu", "YlGn", "YlOrBr", "YlOrRd",
        "BrBG", "PRGn", "PiYG", "PuOr", "RdBu", "RdGy", "RdYlBu", "RdYlGn", "Spectral",
        "Cividis", "Viridis", "Inferno", "Magma", "Plasma", "Warm", "Cool", "CubehelixDefault", "Turbo",
        "Rainbow", "Sinebow"
    ];
    return sequentialSchemes.includes(schemeName);
}

function isCategoricalScheme(schemeName) {
    const categoricalSchemes = [
        "Observable10", "Category10", "Accent", "Dark2", "Paired", "Pastel1", "Pastel2", "Set1", "Set2", "Set3", "Tableau10"
    ];
    return categoricalSchemes.includes(schemeName);
}

function createColorScale(scheme, numCategories, start = $COLOR_SCHEME_INTERPOLATION_START, end = $COLOR_SCHEME_INTERPOLATION_END) {
    if (isSequentialScheme(scheme)) {
        //return d3.scaleSequential(d3[`interpolate${scheme}`]).domain([0, numCategories - 1]);
        const scale = d3.scaleSequential(d3[`interpolate${scheme}`]).domain([0, 1]);
        return function(i) { return scale(start + (end - start) * i / (numCategories - 1)) };
    } else if (isCategoricalScheme(scheme)) {
        return d3.scaleOrdinal(d3[`scheme${scheme}`]);
    } else {
        throw new Error("Unknown color scheme.");
    }
}

function isString(obj) {
  return typeof obj === 'string' || obj instanceof String;
}

function isListOfTwoStrings(obj) {
  return Array.isArray(obj) &&
         obj.length === 2 &&
         obj.every(item => isString(item));
}

function generateRandomIdentifier() {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let identifier = '';
  for (let i = 0; i < 6; i++) {
    identifier += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return identifier;
}

var gaugeLabels = $GAUGE_LABELS;

function drawClock(hour, minute, second, gaugeLabels) {
    svg.selectAll("*").remove();

    svg.append("circle")
        .attr("r", radius)
        .attr("fill", $FILL_COLOR)
        .attr("stroke", $STROKE_COLOR)
        .attr("stroke-width", $STROKE_WIDTH);

    if (scaleRanges.length) {
        const arc = d3.arc()
            .innerRadius(d => radius - radius * Math.min(...d[1]))
            .outerRadius(d => radius - radius * Math.max(...d[1]))
            .startAngle(d => (d[0][0] * 2 * Math.PI) / 60)
            .endAngle(d => (d[0][1] * 2 * Math.PI) / 60);

        var uniqueId = generateRandomIdentifier();

        svg.selectAll("path")
            .data(scaleRanges)
            .enter()
            .append("path")
            .attr("d", arc)
            .each(function(d, i) {
                // Create a radial gradient for each arc
                var gradient = svg.append("defs")
                    .append("radialGradient")
                    .attr("id", "gradient_" + uniqueId + '_' + i)
                    .attr("gradientUnits", "userSpaceOnUse")
                    .attr("cx", 0)
                    .attr("cy", 0)
                    .attr("r", radius);

                // Define the gradient stops along the radius
                if (isString(d[2]) && (isSequentialScheme(d[2]) || isCategoricalScheme(d[2])) ) {
                    var cf = createColorScale(d[2], 100);
                    const minRad = Math.min(...d[1]);
                    const maxRad = Math.max(...d[1]);
                    for (var j = 0; j <= 100; j++) {
                        const j2 = Math.round((minRad + j / 100 * (maxRad - minRad)) * 100);
                        if (d[1][0] < d[1][1]) {
                            gradient.append("stop")
                                .attr("offset", j + "%")
                                .attr("stop-color", cf(100-j2))
                                .attr("stop-opacity", 1);
                        } else {
                            gradient.append("stop")
                                .attr("offset", j + "%")
                                .attr("stop-color", cf(j2))
                                .attr("stop-opacity", 1);
                        }
                    }

                    // Apply the gradient to the current arc
                    d3.select(this)
                        .style("fill", "url(#gradient_" + uniqueId + '_' + i + ")");

                } else if (isListOfTwoStrings(d[2])) {
                    gradient.append("stop")
                        .attr("offset",  Math.round((1-d[1][1]) * 100) + "%")
                        .attr("stop-color", d[2][1])
                        .attr("stop-opacity", 1);

                    gradient.append("stop")
                        .attr("offset", Math.round((1-d[1][0]) * 100) + "%")
                        .attr("stop-color", d[2][0])
                        .attr("stop-opacity", 1);

                    // Apply the gradient to the current arc
                    d3.select(this)
                        .style("fill", "url(#gradient_" + uniqueId + '_' + i + ")");
                } else if (isString(d[2])) {
                    d3.select(this).attr("fill", d[2]);
                } else {
                    d3.select(this).attr("fill", colorScale(i));
                }
            });
    }

    // Draw ticks
    const ticks = svg.append("g").selectAll("line")
        .data(d3.range(0, 60)).enter()
        .append("line")
        .attr("x1", 0)
        .attr("y1", d => d % 5 === 0 ? -radius + 15 : -radius + 10)
        .attr("x2", 0)
        .attr("y2", -radius + 5)
        .attr("stroke", $STROKE_COLOR)
        .attr("stroke-width", d => d % 5 === 0 ? 2 : 1)
        .attr("transform", d => `rotate(${d * 6})`);

    // Draw numbers
    const numbers = svg.append("g").selectAll("text")
        .data(d3.range(1, 13)).enter()
        .append("text")
        .attr("x", d => Math.sin(d * Math.PI / 6) * (radius - 30))
        .attr("y", d => -Math.cos(d * Math.PI / 6) * (radius - 30) + 5)
        .attr("text-anchor", "middle")
        .attr("font-size", $TICK_LABELS_FONT_SIZE)
        .attr("fill", $TICK_LABELS_COLOR)
        .attr("font-family", $TICK_LABELS_FONT_FAMILY)
        .text(d => d);

    // Draw gauge labels (before the clock hands)
    const labelElements = svg.append("g").selectAll("text")
        .data(Object.entries(gaugeLabels)).enter()
        .append("text")
        .attr("x", d => typeof d[1] === 'object' ? d[1][0] * 2 * radius - radius : Math.sin(d[1] * Math.PI / 6) * (radius - 40))
        .attr("y", d => typeof d[1] === 'object' ? - d[1][1] * 2 * radius + radius : -Math.cos(d[1] * Math.PI / 6) * (radius - 40))
        .attr("text-anchor", "middle")
        .attr("font-size", $GAUGE_LABELS_FONT_SIZE)
        .attr("fill", $GAUGE_LABELS_COLOR)
        .attr("font-family", $GAUGE_LABELS_FONT_FAMILY)
        .text(d => d[0] === 'Value' ? `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}` : d[0]);

    // Draw hour hand
    svg.append("line")
        .attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", 0)
        .attr("y2", -radius + 8 / 15 * radius)
        .attr("stroke", $HOUR_HAND_COLOR)
        .attr("stroke-width", 4)
        .attr("transform", `rotate(${(hour % 12) * 30 + minute / 2})`);

    // Draw minute hand
    svg.append("line")
        .attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", 0)
        .attr("y2", -radius + 4 / 15 * radius)
        .attr("stroke", $MINUTE_HAND_COLOR)
        .attr("stroke-width", 2)
        .attr("transform", `rotate(${minute * 6 + second / 10})`);

    // Draw second hand
    svg.append("line")
        .attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", 0)
        .attr("y2", -radius + 2 / 15 * radius)
        .attr("stroke", $SECOND_HAND_COLOR)
        .attr("stroke-width", 1)
        .attr("transform", `rotate(${second * 6})`);
}

function updateClock() {
    //const now = new Date();
    //drawClock(now.getHours(), now.getMinutes(), now.getSeconds());
    drawClock($HOUR, $MINUTE, $SECOND, gaugeLabels);
}

drawClock(hour, minute, second, gaugeLabels);
setInterval(updateClock, updateInterval);
GAUGE-CLOCK

#============================================================
# Clock Gauge code snippet accessor
#============================================================

our sub GetClockGauge() {
    return $jsClockGauge;
}


