class Charts {


    constructor(idata) {
        // Unpack the initialization data.
        this.chart1_id = idata.chart1_id;
        this.chart2_id = idata.chart2_id;
        this.chart3_id = idata.chart3_id;
        this.chart4_id = idata.chart4_id;
        this.chart5_id = idata.chart5_id;  // Patterns chart
        this.trading_pair = idata.trading_pair;
        this.price_history = idata.price_history;
        /* A list of bound charts this is necessary for maintaining a dynamic
           number of charts with their position and zoom factors bound.*/
        this.bound_charts=[];
        // Only the main chart is created by default.
        this.create_main_chart();
    }


    create_main_chart() {
        // Pass the id of the element to create the main chart in.
        // This function returns the main chart object.
        this.chart_1 = this.create_chart(this.chart1_id);

        // Display the trading pair as a watermark overlaying the chart.
        this.addWatermark(this.chart_1, this.trading_pair);

        // - Create the candle stick series for our chart
        this.candleSeries = this.chart_1.addCandlestickSeries();

        // Initialize the candlestick series if price_history is available
        if (this.price_history && this.price_history.length > 0) {
            this.candleSeries.setData(this.price_history);
            console.log('Candle series init:', this.price_history);
        } else {
            console.error('Price history is not available or is empty.');
        }
        this.bind_charts(this.chart_1);
    }

    update_main_chart(new_candle){
            // Update candlestick series
	        this.candleSeries.update(new_candle);
    }

    create_RSI_chart(){
        this.chart2 = this.create_chart(this.chart2_id, 100);
        this.set_priceScale(this.chart2, 0.3, 0.0);
        // Put the name of the chart in a watermark in the chart.
        this.addWatermark(this.chart2, 'RSI');
        // Todo: Not sure how to set this
        //this.chart2.applyOptions({ priceRange: {minValue:0,maxValue:100} });
        this.bind_charts(this.chart2);
    }


    create_MACD_chart(){
        this.chart3 = this.create_chart(this.chart3_id, 100);
        this.addWatermark(this.chart3, 'MACD');
        this.bind_charts(this.chart3);
    }

    create_PercentB_chart(){
        this.chart4 = this.create_chart(this.chart4_id, 100);
        this.set_priceScale(this.chart4, 0.3, 0.0);
        // Put the name of the chart in a watermark
        this.addWatermark(this.chart4, '%B');
        this.bind_charts(this.chart4);
    }

    create_patterns_chart(){
        // Create a chart for candlestick pattern indicators
        // Scale is fixed -100 to +100 (bearish to bullish signals)
        this.chart5 = this.create_chart(this.chart5_id, 100);
        this.set_priceScale(this.chart5, 0.1, 0.1);
        // Put the name of the chart in a watermark
        this.addWatermark(this.chart5, 'Patterns');
        this.bind_charts(this.chart5);

        // Sync time scale with main chart so timestamps align
        if (this.chart_1) {
            let barSpacing = this.chart_1.timeScale().getBarSpacing();
            let scrollPosition = this.chart_1.timeScale().scrollPosition();
            this.chart5.timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
        }
    }


    create_chart(target_id, height=500){
        // Accepts a target element to place the chart in.
        // Returns the chart object.
        let container = document.getElementById(target_id);
        //Create a lightweight chart object.
        let chart = LightweightCharts.createChart(container, {
	        width: 1000,
  	        height: height,
	        crosshair: {
		        mode: LightweightCharts.CrosshairMode.Normal,
	        },
	        rightPriceScale: {
		        borderColor: 'rgba(197, 203, 206, 0.8)',
	        },
	        timeScale: {
		        borderColor: 'rgba(197, 203, 206, 0.8)',
		        timeVisible: true,
		        secondsVisible: false,
		        barSpacing: 6
	        },
	        handleScroll: true,
	        localization: {
	            // Display times in UTC to match server data
	            timeFormatter: (timestamp) => {
	                const date = new Date(timestamp * 1000);
	                const day = date.getUTCDate().toString().padStart(2, '0');
	                const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
	                const month = months[date.getUTCMonth()];
	                const year = date.getUTCFullYear().toString().slice(-2);
	                const hours = date.getUTCHours().toString().padStart(2, '0');
	                const minutes = date.getUTCMinutes().toString().padStart(2, '0');
	                return `${day}/${month}/${year} ${hours}:${minutes}`;
	            }
	        }
        });

        return chart;
    }


    set_priceScale(chart, top, bottom){
        chart.priceScale('right').applyOptions({
            scaleMargins: {
                top: top,
                bottom: bottom,
            },
        });
    }


    addWatermark(chart,text){
        chart.applyOptions({
            watermark: {visible: true,
                color: '#DBC29E',
                text: text,
			    fontSize: 30,
			    fontFamily: 'Roboto',
			    fontStyle: 'bold',
			    vertAlign: 'center'
            }
        });
    }


    bind_charts(chart){
        // keep a list of charts and bind all their position and spacing.
        // Add (arg1) to bound_charts
        this.add_to_list(chart);
        // Get the number of objects in bound_charts
        let bcl = Object.keys(this.bound_charts).length;
        // if bound_charts has two element in it bind them
        if (bcl == 2) { this.bind2charts(); }

        // if bound_charts has three elements in it bind them
        if (bcl == 3) { this.bind3charts(); }

        // if bound_charts has four elements in it bind them
        if (bcl == 4) { this.bind4charts(); }

        // if bound_charts has five elements in it bind them
        if (bcl == 5) { this.bind5charts(); }

        return;
    }
    add_to_list(chart){
        // If the chart isn't already included in the list, add it.
        if ( !this.bound_charts.includes(chart) ){
            this.bound_charts.push(chart);
        }
    }

    bind2charts(){
        //On change in chart 1 change chart 2
        let syncHandler1 = (e) => {
            // Get the barSpacing(zoom) and position of 1st chart.
            let barSpacing1 = this.bound_charts[0].timeScale().getBarSpacing();
            let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
            // Apply barSpacing(zoom) and position to 2nd chart.
            this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
        }
        this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler1);

        //On change in chart 2 change chart 1
        let syncHandler2 = (e) => {
            // Get the barSpacing(zoom) and position of chart 2
            let barSpacing2 = this.bound_charts[1].timeScale().getBarSpacing();
            let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
            // Apply barSpacing(zoom) and position to chart 1
            this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
        }
        this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
    }
    bind3charts(){

        //On change to chart 1 change chart 2 and 3
        let syncHandler = (e) => {
            // Get the barSpacing(zoom) and position of chart 1
            let barSpacing1 = this.bound_charts[0].timeScale().getBarSpacing();
            let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
            // Apply barSpacing(zoom) and position to new chart
            this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
            this.bound_charts[2].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
        }
        this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler);

        //On change to chart 2 change chart 1 and 3
        let syncHandler2 = (e) => {
            // Get the barSpacing(zoom) and position of chart 2
            let barSpacing2 = this.bound_charts[1].timeScale().getBarSpacing();
            let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
            // Apply barSpacing(zoom) and position to chart 1 and 3
            this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
            this.bound_charts[2].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
        }
        this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);

        //On change to chart 3 change chart 1 and 2
        let syncHandler3 = (e) => {
            // Get the barSpacing(zoom) and position of new chart
            let barSpacing2 = this.bound_charts[2].timeScale().getBarSpacing();
            let scrollPosition2 = this.bound_charts[2].timeScale().scrollPosition();
            // Apply barSpacing(zoom) and position to parent chart
            this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
            this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
        }
        this.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncHandler3);
    }

    bind4charts(){
        // Sync all 4 charts together
        let syncFromChart = (sourceIndex) => {
            return (e) => {
                let barSpacing = this.bound_charts[sourceIndex].timeScale().getBarSpacing();
                let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
                for (let i = 0; i < 4; i++) {
                    if (i !== sourceIndex) {
                        this.bound_charts[i].timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
                    }
                }
            }
        }
        this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(0));
        this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(1));
        this.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(2));
        this.bound_charts[3].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(3));
    }

    bind5charts(){
        // Sync all 5 charts together (main + RSI + MACD + %B + Patterns)
        let syncFromChart = (sourceIndex) => {
            return (e) => {
                let barSpacing = this.bound_charts[sourceIndex].timeScale().getBarSpacing();
                let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
                for (let i = 0; i < 5; i++) {
                    if (i !== sourceIndex) {
                        this.bound_charts[i].timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
                    }
                }
            }
        }
        this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(0));
        this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(1));
        this.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(2));
        this.bound_charts[3].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(3));
        this.bound_charts[4].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(4));
    }

    // Set trade markers on chart for all trades in backtest results
    setTradeMarkers(trades) {
        if (!this.candleSeries) {
            console.warn('Candlestick series not available');
            return;
        }

        if (!trades || trades.length === 0) {
            console.log('No trades to display as markers');
            return;
        }

        const candleData = this.price_history || [];
        if (candleData.length === 0) {
            console.warn('No candle data available for markers');
            return;
        }

        // Get the time range of loaded candle data
        const minCandleTime = candleData[0].time;
        const maxCandleTime = candleData[candleData.length - 1].time;
        console.log(`Chart data range: ${minCandleTime} to ${maxCandleTime}`);

        // Build markers array from all trades
        const markers = [];

        trades.forEach(trade => {
            const openTime = this.dateStringToUnixTimestamp(trade.open_datetime);
            const closeTime = trade.close_datetime ? this.dateStringToUnixTimestamp(trade.close_datetime) : null;

            // Add entry marker if within chart data range
            if (openTime && openTime >= minCandleTime && openTime <= maxCandleTime) {
                const matchedOpenTime = this.findNearestCandleTime(openTime, candleData);
                markers.push({
                    time: matchedOpenTime,
                    position: 'belowBar',
                    color: '#26a69a',
                    shape: 'arrowUp',
                    text: 'BUY @ ' + (trade.open_price ? trade.open_price.toFixed(2) : '')
                });
            }

            // Add exit marker if within chart data range
            if (closeTime && closeTime >= minCandleTime && closeTime <= maxCandleTime) {
                const matchedCloseTime = this.findNearestCandleTime(closeTime, candleData);
                markers.push({
                    time: matchedCloseTime,
                    position: 'aboveBar',
                    color: '#ef5350',
                    shape: 'arrowDown',
                    text: 'SELL @ ' + (trade.close_price ? trade.close_price.toFixed(2) : '')
                });
            }
        });

        if (markers.length === 0) {
            console.log('No trades fall within the loaded chart data timespan');
            return;
        }

        // Sort markers by time (required by lightweight-charts)
        markers.sort((a, b) => a.time - b.time);

        console.log(`Setting ${markers.length} trade markers on chart`);

        // Set markers on the candlestick series
        this.candleSeries.setMarkers(markers);
    }

    // Clear all trade markers from chart
    clearTradeMarkers() {
        if (this.candleSeries) {
            this.candleSeries.setMarkers([]);
            console.log('Trade markers cleared');
        }
    }

    // Find the nearest candle time in the data
    findNearestCandleTime(targetTime, candleData) {
        if (!candleData || candleData.length === 0) {
            return targetTime;
        }

        let nearestTime = candleData[0].time;
        let minDiff = Math.abs(targetTime - nearestTime);

        for (const candle of candleData) {
            const diff = Math.abs(targetTime - candle.time);
            if (diff < minDiff) {
                minDiff = diff;
                nearestTime = candle.time;
            }
            // Early exit if exact match
            if (diff === 0) break;
        }

        return nearestTime;
    }

    // Convert datetime string to Unix timestamp in seconds
    dateStringToUnixTimestamp(dateStr) {
        if (!dateStr) return null;
        try {
            const date = new Date(dateStr);
            return Math.floor(date.getTime() / 1000);
        } catch (e) {
            console.warn('Failed to parse date:', dateStr, e);
            return null;
        }
    }


}
