// 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
// Note that passed data must match interface multiLineChartDatatype
// 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';

interface multiLineChartDatatype {
    key: any;
    entries: {
        key: any;
        value: any;
    }[];
}

export class MultiLineChart extends Chart {
    private xScale: any;
    private yScale: any;
    private xAxis: any;
    private yAxis: any;
    private colorScale: any;
    private lineGenerator: any;
    private tooltip: any;

    private xAxisLabel: string;
    private yAxisLabel: string;

    constructor(_config: ChartConfiguration, _xAxisLabel: string, _yAxisLabel: string, _data: multiLineChartDatatype[]) {
        super(_config, _data);
        this.xAxisLabel = _xAxisLabel;
        this.yAxisLabel = _yAxisLabel;
        this.initVis();
    }

    protected getDefaultMargins() {
        return { top: 20, right: 150, bottom: 50, left: 70 };
    }

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

    protected initVis() {
        const vis = this;

        vis.xScale = d3.scaleLinear()
            .range([0, vis.width]);
        vis.xAxis = vis.chart.append('g')
            .attr('transform', `translate(0,${vis.height})`)
            .call(d3.axisBottom(vis.xScale));
        vis.chart.append("text")
            .attr("class", "axis-label")
            .attr("text-anchor", "middle")
            .attr("x", vis.width / 2)
            .attr("y", vis.height + 40)
            .style('font-weight', 'bold')
            .text(this.xAxisLabel);

        vis.yScale = d3.scaleLinear()
            .nice()
            .range([vis.height, 0]);
        vis.yAxis = vis.chart.append('g')
            .call(d3.axisLeft(vis.yScale));
        vis.chart.append("text")
            .attr("class", "axis-label")
            .attr("text-anchor", "middle")
            .attr("transform", "rotate(-90)")
            .attr("y", -40)
            .attr("x", -vis.height / 2)
            .style('font-weight', 'bold')
            .text(this.yAxisLabel);

        vis.colorScale = d3.scaleOrdinal(d3.schemeCategory10);

        vis.lineGenerator = d3.line()
            .x((d: any) => vis.xScale(d.key))
            .y((d: any) => vis.yScale(d.value));

        // Add tooltip
        this.tooltip = d3.select('body').append('div')
            .attr('id', 'multi-line-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')
            .html(
                `<span id="id-title"></span><span id="id-placeholder"></span><br>
                <span id="key-title"></span><span id="key-placeholder"></span><br>
                <span id="value-title"></span><span id="value-placeholder"></span>`);
    }

    public updateVis() {
        const vis = this;

        // Update scales
        vis.xScale.domain(d3.extent(vis.data.flatMap(d => d.entries), (d: any) => d.key));
        vis.yScale.domain([0, d3.max(vis.data.flatMap(d => d.entries), (d: any) => d.value)]).nice();
        vis.colorScale.domain(this.data.map(d => d.id));

        // Update Axis
        vis.xAxis.transition().duration(1000).call(d3.axisBottom(vis.xScale));
        vis.yAxis.transition().duration(1000).call(d3.axisLeft(vis.yScale));

        vis.renderVis();
    }

    protected renderVis() {
        const vis = this;

        const lines = vis.chart.selectAll('.line').data(vis.data, (d: any) => d.id);

        // Enter selection for new lines
        const lineEnter = lines.enter()
            .append('path')
            .attr('class', 'line')
            .attr('fill', 'none')
            .attr('stroke', (d: any) => vis.colorScale(d.id))
            .attr('stroke-width', 4)
            .attr('d', (d: any) => vis.lineGenerator(d.entries))
            .style("opacity", 0);

        // Merge and update existing lines with transition
        lineEnter.merge(lines)
            .transition()
            .duration(500)
            .attr('d', (d: any) => vis.lineGenerator(d.entries))
            .style("opacity", 1); // Fade in

        // Exit selection for removed lines
        lines.exit()
            .transition()
            .duration(500)
            .style("opacity", 0)
            .remove();

        // Line hover event
        vis.chart.selectAll('.line')
            .on("mouseover", (event: any) => {
                d3.selectAll(".line").style("opacity", 0.2);
                d3.select(event.currentTarget).style("opacity", 1).attr("stroke-width", 6);
            })
            .on("mouseout", function () {
                d3.selectAll(".line").style("opacity", 1).attr("stroke-width", 4);
                vis.tooltip.style("opacity", 0);
            })
            .on("mousemove", function (event: any, d: any) {
                const mouseX = d3.pointer(event)[0];
                const key = vis.xScale.invert(mouseX);

                // Find the closest data point to the mouse position
                const closestDataPoint = d.entries.reduce((prev: any, curr: any) => {
                    return (Math.abs(curr.key - key) < Math.abs(prev.key - key) ? curr : prev);
                });

                // Update tooltip content and position
                d3.select('#multi-line-chart-tooltip')
                    .select('#id-placeholder')
                    .text(d.id);
                d3.select('#multi-line-chart-tooltip')
                    .select('#key-placeholder')
                    .text(closestDataPoint.key);
                d3.select('#multi-line-chart-tooltip')
                    .select('#value-placeholder')
                    .text(closestDataPoint.value);
                vis.tooltip
                    .style("left", (event.pageX - 20) + "px")
                    .style("top", (event.pageY - 85) + "px")
                    .style("opacity", 1)
                    .style('display', 'block');
            });

        // Render legend //

        vis.chart.append("text")
            .attr("x", vis.width + 10)
            .attr("y", 0)
            .text("Filter by:")
            .style("font-weight", "bold")
            .style("font-size", "14px")
            .style("user-select", "none");

        // Bind the data to the legend items
        const legend = vis.chart.selectAll('.legend').data(vis.data, (d: any) => d.id);

        // Enter selection for new legend items
        const legendEnter = legend.enter()
            .append("g")
            .attr("class", "legend")
            .attr("transform", (_: any, i: number) => `translate(${vis.width + 10},${i * 20 + 20})`)
            .on("mouseover", (event: any, d: any) => {
                d3.selectAll(".line").style("opacity", 0.2);
                d3.select(`.line[stroke='${vis.colorScale(d.id)}']`).style("opacity", 1).attr("stroke-width", 6);
                d3.selectAll(".legend").select("rect").style("opacity", 0.2);
                d3.selectAll(".legend").select("text").style("opacity", 0.2);
                d3.select(event.currentTarget).select("rect").style("opacity", 1);
                d3.select(event.currentTarget).select("text").style("opacity", 1);
            })
            .on("mouseout", function () {
                d3.selectAll(".legend").select("rect").style("opacity", 1);
                d3.selectAll(".legend").select("text").style("opacity", 1);
                d3.selectAll(".line").style("opacity", 1).attr("stroke-width", 4);
            });

        // Append rectangles and text for the legend
        legendEnter.append("rect")
            .attr("width", 10)
            .attr("height", 10)
            .attr("fill", (d: any) => vis.colorScale(d.id));

        legendEnter.append("text")
            .attr("x", 15)
            .attr("y", 10)
            .text((d: any) => d.id)
            .style("font-size", "12px")
            .style("user-select", "none");

        // Update selection for existing legend items
        const legendUpdate = legend.merge(legendEnter);
        legendUpdate.attr("transform", (_: any, i: number) => `translate(${vis.width + 10},${i * 20 + 20})`);

        // Exit selection for removed legend items
        legend.exit().remove();
    }
}