// No dataset-related code here, just pass prepared data correctly using helper
// and you'll get vis with stacks-based filtering and tooltip printing id value

// Modify some hard-coded margins, paddings values to enhance vis appearance

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

export class StackedBarChart extends Chart {
    public stacks: any[];
    private activeKeys: string[];
    private xAxisTitle: string;
    private yAxisTitle: string;
    private legend: d3.Selection<SVGGElement, unknown, HTMLElement, any>;
    private tooltip: d3.Selection<HTMLDivElement, unknown, HTMLElement, any>;

    constructor(_config: ChartConfiguration, _stacks: any[], xAxisTitle: string, yAxisTitle: string, _data?: any[]) {
        super(_config, _data);
        this.stacks = _stacks;
        this.activeKeys = this.stacks.map(d => d.stackName);
        this.xAxisTitle = xAxisTitle;
        this.yAxisTitle = yAxisTitle;
        this.initVis();
    }

    private xScale: d3.ScaleBand<string>;
    private yScale: d3.ScaleLinear<number, number, never>;
    private colorScale: d3.ScaleOrdinal<string, unknown, never>;
    private stackGenerator: d3.Stack<any, { [key: string]: number; }, string>;

    protected getDefaultMargins() { return { top: 5, right: 100, bottom: 120, left: 100 }; }
    protected getDefaultContainerSize() { return { width: 800, height: 400 }; }

    protected initVis() {
        const vis = this;
        // keys are staks in stacked bar chart
        const keys = this.stacks.map(d => d.stackName);

        // Axes, scales
        vis.xScale = d3.scaleBand().padding(0.3);
        vis.xScale.range([0, vis.width]);
        vis.chart.append('text')
            .attr('class', 'axis-title')
            .attr('x', vis.width + 40)
            .attr('y', vis.height + 6)
            .attr('text-anchor', 'middle')
            .attr('fill', 'black')
            .style('font-weight', 'bold')
            .text(this.xAxisTitle);
        vis.chart.append('g').attr('id', 'stacked-bar-chart-x-axis');

        vis.yScale = d3.scaleLinear();
        vis.yScale.range([vis.height, 0]);
        vis.chart.append('text')
            .attr('class', 'axis-title')
            .attr('transform', 'rotate(-90)')
            .attr('x', -vis.height / 2)
            .attr('y', -50)
            .attr('text-anchor', 'middle')
            .style('font-weight', 'bold')
            .text(this.yAxisTitle);
        vis.chart.append('g').attr('id', 'stacked-bar-chart-y-axis');

        vis.colorScale = d3.scaleOrdinal()
            .domain(keys)
            .range(this.stacks.map(d => d.stackColor));

        vis.stackGenerator = d3.stack()
            .keys(keys)
            .order(d3.stackOrderNone)  // keep original stack order
            .offset(d3.stackOffsetNone);

        // legend (passed stacks names)
        vis.legend = vis.chart.append('g')
            .attr('id', 'stacked-bar-chart-legend')
            .attr('transform', `translate(0,${vis.height + 80})`);  // Position legend below chart
        vis.legend.append('text')
            .attr('x', 25)
            .attr('y', 15)
            .attr('text-anchor', 'middle')
            .style('font-weight', 'bold')
            .style('user-select', 'none')
            .text('Filter by:');

        keys.forEach((key, i) => {
            const legendItem = vis.legend.append('g')
                .attr('class', 'legend-item')
                .attr('transform', `translate(${(i + 1) * 80},0)`)
                .style('cursor', 'pointer')
                .on('click', _ => vis.handleKeyFiltering(key));

            legendItem.append('rect')
                .attr('width', 20).attr('height', 20)
                .attr('fill', String(vis.colorScale(key)));

            legendItem.append('text')
                .attr('x', 25).attr('y', 15)
                .style('user-select', 'none')
                .text(key);
        });

        // Tooltip
        this.tooltip = d3.select('body').append('div')
            .attr('id', 'stacked-bar-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');
    }

    // Selecting key from legend menu handler
    private handleKeyFiltering(medalType: string) {
        const keys = this.stacks.map(d => d.stackName);
        const index = this.activeKeys.indexOf(medalType);
        if (index != -1)
            this.activeKeys.splice(index, 1);
        else
            this.activeKeys.push(medalType);

        if (this.activeKeys.length === 0) this.activeKeys = keys;  // Reset if none selected
        this.updateVis();
    }

    protected renderVis() {
        const vis = this;

        const keys = this.stacks.map(d => d.stackName);
        const orderedKeys = keys.filter(k => vis.activeKeys.includes(k));
        vis.stackGenerator.keys(orderedKeys);

        const stackedData = vis.stackGenerator(vis.data);

        // Enter-Update(Merge)-Exit for layers(each key stack) and rects
        const layers = vis.chart.selectAll('.key-layer')
            .data(stackedData, (d: any) => d.key);

        layers.exit()
            .transition().duration(1000)
            .style('opacity', 0)
            .remove();

        const enterLayers = layers.enter()
            .append('g')
            .attr('class', 'key-layer')
            .attr('fill', (d: any) => vis.colorScale(d.key));

        const allLayers = enterLayers.merge(layers);

        allLayers.each((layerData: any, index: number, groups: any) => {
            const layer = d3.select(groups[index]);
            const rects = layer.selectAll<SVGRectElement, any>('rect').data(layerData, (d: any) => d.data.id);

            rects.exit().
                transition().duration(1000)
                .attr('y', vis.height).attr('height', 0)
                .remove();

            const enterRects = rects.enter()
                .append('rect')
                .attr('x', (d: any) => vis.xScale(d.data.id)!)
                .attr('width', vis.xScale.bandwidth())
                .attr('y', vis.height)
                .attr('height', 0)
                .on('mouseover', function (event: any, d: any) {
                    vis.tooltip.style('opacity', .9).style('display', 'block');
                    vis.tooltip.html(`${d.data.id}<br>${d[1] - d[0]}`)
                        .style('left', (event.pageX - 20) + 'px')
                        .style('top', (event.pageY - 60) + 'px');

                    // Highlight the hovered stack and less opacity for the others
                    vis.chart.selectAll('rect').filter((di: any) => di !== d).style('opacity', 0.3);
                    vis.legend.selectAll('rect')
                        .style('opacity', (_, index) => vis.getLegendItemOpacity(index));
                })
                .on('mousemove', function (event: any) {
                    vis.tooltip.style('left', (event.pageX - 20) + 'px')
                        .style('top', (event.pageY - 60) + 'px');
                })
                .on('mouseout', function () {
                    vis.tooltip.style('opacity', 0);
                    vis.chart.selectAll('rect').style('opacity', 1);
                });

            enterRects.merge(rects)
                .transition().duration(1000)
                .attr('y', (d: any) => vis.yScale(d[1]))
                .attr('height', (d: any) => vis.yScale(d[0]) - vis.yScale(d[1]));
        });

        vis.legend.selectAll('.legend-item')
            .style('opacity', (_, index) => this.getLegendItemOpacity(index));

        vis.updateAxes();
    }

    // Selected or not
    private getLegendItemOpacity(index: any) {
        const vis = this;
        const orderedKeys = this.stacks.map(d => d.stackName);
        return vis.activeKeys.includes(orderedKeys[index]) ? 1 : 0.3;  // Fade inactive items
    }

    private updateAxes() {
        const vis = this;

        // Ensure axis changes are correct after changing data
        vis.chart.select('#stacked-bar-chart-x-axis')
            .attr('transform', `translate(0,${vis.height})`)
            .call(d3.axisBottom(vis.xScale))
            .selectAll('text')
            .attr('transform', 'rotate(-30)')
            .style('text-anchor', 'end')
            .style('font-size', '13px')
            .style('fill', 'black');

        vis.chart.select('#stacked-bar-chart-y-axis')
            .call(d3.axisLeft(vis.yScale).ticks(5))
            .selectAll('text')
            .style('font-size', '13px')
            .style('fill', 'black');
    }

    public updateVis() {
        const vis = this;
        // Update domains and render
        const keys = this.stacks.map(d => d.stackName);
        const orderedKeys = keys.filter(k => vis.activeKeys.includes(k));
        vis.xScale.domain(vis.data.map((d: any) => d.id));

        vis.yScale.domain([0, d3.max(vis.data, (d: any) =>
            orderedKeys.reduce((sum, key) => sum + d[key], 0)
        )!]);
        vis.renderVis();
    }
}