import * as d3 from 'd3';
import { Chart } from '../chart-base/chart';
import { ChartConfiguration } from '../chart-base/chart-configuration';

interface pieChartDatatype {
    key: string;
    value: number;
}

export class PieChart extends Chart {
    private pie: any;
    private arc: any;
    private color: any;
    tooltip: d3.Selection<HTMLDivElement, unknown, HTMLElement, any>;

    constructor(_config: ChartConfiguration, _data?: pieChartDatatype[]) {
        super(_config, _data);
        this.initVis();
    }

    protected getDefaultMargins() {
        return { top: 20, right: 80, bottom: 20, left: 20 };
    }

    protected getDefaultContainerSize() {
        return { width: 400, height: 400 };
    }

    protected initVis() {
        const vis = this;

        // Define the pie layout
        vis.pie = d3.pie<any>()
            .value(d => d.value)
            .sort(null);

        // Define the arc
        vis.arc = d3.arc()
            .innerRadius(0)
            .outerRadius(Math.min(vis.width, vis.height) / 2 - 1);

        // Define color scale for gender
        // Can use scaleOrdinal10 for generalization and usage of keys
        // instead of hard coded keys
        vis.color = d3.scaleOrdinal()
            .domain(['Men', 'Women'])
            .range(['#01a7c5', '#f284a5']);

        // Add tooltip
        this.tooltip = d3.select('body').append('div')
            .attr('id', 'pie-chart-tooltip')
            .style('opacity', 0)
            .style('position', 'absolute')
            .style('display', 'none')
            .style('background', 'white')
            .style('border', '1px solid black')
            .style('padding', '5px')
            .style('pointer-events', 'none');
    }

    public updateVis() {
        const vis = this;
        vis.renderVis();
    }

    protected renderVis() {
        const vis = this;

        // Clear previous
        vis.chart.selectAll("*").remove();

        // chart gropu
        const g = vis.chart.append("g")
            .attr("transform", `translate(${vis.width / 2}, ${vis.height / 2})`);

        // Create args
        const pieData = vis.pie(this.data);

        // Total omong all values
        const total = d3.sum(vis.data, (data: any) => data.value);

        // Bind data to arcs
        const arcs = g.selectAll(".arc")
            .data(pieData, (d: any) => d.data.key);

        // Enter section (new arcs)
        const arcsEnter = arcs.enter().append("g")
            .attr("class", "arc");

        arcsEnter.append("path")
            .attr("d", vis.arc)
            .attr("fill", (d: any) => vis.color(d.data.key))
            .attr("stroke", "#fff")
            .attr("stroke-width", 0)
            .attr("opacity", 0) // To be changed in transition
            .transition()
            .duration(500)
            .attr("opacity", 1);

        // Add percentage text to each arc
        arcsEnter.append("text")
            .attr("transform", (d: any) => `translate(${vis.arc.centroid(d)})`) // Position text at the centroid of the arc
            .attr("dy", ".35em") // Adjust vertical alignment
            .attr("text-anchor", "middle") // Center the text
            .text((d: any) => {
                const percentage = ((d.data.value / total) * 100).toFixed(2);
                return `${percentage}%`; // Display percentage
            })
            .style("fill", "#fff")
            .style("user-select", 'none')
            .style('font-weight', 'bold');

        // Add mouse event handlers for arcs
        arcsEnter.select("path")
            .on("mouseover", function (event: any, d: any) {
                // Highlight the hovered arc
                d3.select(event.currentTarget).attr("opacity", 1); // Set hovered arc to full opacity
                // minimum opacity of other arcs
                arcsEnter.selectAll("path").filter(function (arcData: any) {
                    return arcData.data.key !== d.data.key;
                }).attr("opacity", 0.4);

                // high stroke to distinguish between args when select
                arcsEnter.selectAll("path").filter(function (arcData: any) {
                    return arcData.data.key === d.data.key;
                }).attr("stroke-width", 6);

                vis.tooltip.style('opacity', .9).style('display', 'block');
                vis.tooltip.html(`${d.data.key}<br>Total Medals: ${d.data.value}`)
                    .style('left', (event.pageX - 20) + 'px')
                    .style('top', (event.pageY - 65) + 'px');
            })
            .on("mousemove", function (event: any) {
                vis.tooltip.style('left', (event.pageX - 20) + 'px')
                    .style('top', (event.pageY - 65) + 'px');
            })
            .on("mouseout", function () {
                // Reset opacity of all arcs
                arcsEnter.selectAll("path").attr("opacity", 1).attr('stroke-width', 0); // Reset all arcs to full opacity
                vis.tooltip.style('opacity', '0');
            });

        // Update section
        arcs.select("path")
            .transition() // smooth updates
            .duration(500)
            .attr("d", vis.arc)
            .attr("fill", (d: any) => vis.color(d.data.key))
            .attr("stroke", "#fff")
            .attr("stroke-width", 2)

        // Update text for existing arcs
        arcs.select("text")
            .transition() // Transition for text change
            .duration(500)
            .attr("transform", (d: any) => `translate(${vis.arc.centroid(d)})`) // Update position
            .text((d: any) => {
                const percentage = ((d.data.value / total) * 100).toFixed(2);
                return `${percentage}%`; // Update percentage text
            });

        // Exit
        arcs.exit().select("path")
            .transition()
            .duration(500)
            .attr("opacity", 0) // Fade out effects
            .remove();

        // Legend
        // this is in render for cases when keys changes when data changes (general case)
        const legend = vis.chart.append("g")
            .attr("transform", `translate(${vis.width}, ${vis.height - 60})`);
        // Find all keys
        const legendData = this.data.map(d => d.key);
        // Bind data to legend items
        const legendItems = legend.selectAll(".legend-item")
            .data(legendData);

        // Enter: create new legend items
        const legendEnter = legendItems.enter().append("g")
            .attr("class", "legend-item")
            .attr("transform", (_: any, i: any) => `translate(0, ${i * 20})`);

        legendEnter.append("rect")
            .attr("x", 0)
            .attr("width", 18)
            .attr("height", 18)
            .attr("fill", (d: any) => vis.color(d))
            .attr("stroke", "#fff");

        legendEnter.append("text")
            .attr("x", 25)
            .attr("y", 9)
            .attr("dy", ".35em")
            .text((d: any) => d)
            .style("user-select", "none");

        // Update
        const legendUpdate = legendItems.merge(legendEnter);
        legendUpdate.select("rect")
            .transition() // Transition for color change
            .duration(500)
            .attr("fill", (d: any) => vis.color(d));

        legendUpdate.select("text")
            .transition() // Transition for text change
            .duration(500)
            .text((d: any) => d);

        // Events handlers
        legendUpdate.on("mouseover", function (_: any, d: any) {
            // Highlight related arc
            arcsEnter.selectAll("path").filter(function (arcData: any) {
                return arcData.data.key === d;
            }).attr("opacity", 1).attr("stroke-width", 6);

            // Reduce opacity of other arcs
            arcsEnter.selectAll("path").filter(function (arcData: any) {
                return arcData.data.key !== d;
            }).attr("opacity", 0.4);
        })
            .on("mouseout", function () {
                // Reset opacity of all arcs
                arcsEnter.selectAll("path").attr("opacity", 1).attr('stroke-width', 0); // Reset all arcs to full opacity
            });

        // Exit
        legendItems.exit().transition()
            .duration(500)
            .attr("opacity", 0) // Fade out
            .remove(); // Remove after fade out
    }
}