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=[];
        // Store unsubscribe functions for cleanup when rebinding
        this._syncUnsubscribes = [];
        // Debounce timestamp to prevent infinite loop in chart synchronization
        this._lastSyncTime = 0;
        // Flag to prevent recursive sync
        this._isSyncing = false;
        // 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 (v5 API)
        this.candleSeries = this.chart_1.addSeries(LightweightCharts.CandlestickSeries);

        // 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 initialized with ${this.price_history.length} candles`);
            console.log('First candle:', this.price_history[0]);
            console.log('Last candle:', this.price_history[this.price_history.length - 1]);
        } else {
            console.error('Price history is not available or is empty. Received:', this.price_history);
        }
        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 (v5 API)
        if (this.chart_1) {
            let barSpacing = this.chart_1.timeScale().options().barSpacing;
            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){
        // v5 API: watermarks are now created via createTextWatermark plugin
        LightweightCharts.createTextWatermark(chart.panes()[0], {
            horzAlign: 'center',
            vertAlign: 'center',
            lines: [
                {
                    text: text,
                    color: '#DBC29E',
                    fontSize: 30,
                    fontFamily: 'Roboto',
                    fontStyle: 'bold',
                }
            ]
        });
    }


    bind_charts(chart){
        // Add chart to list if not already present
        if (!this.bound_charts.includes(chart)) {
            this.bound_charts.push(chart);
        }

        // Only need to sync when we have 2+ charts
        if (this.bound_charts.length >= 2) {
            this._rebindAllCharts();
        }
    }

    /**
     * Unsubscribe all existing sync handlers and set up fresh ones.
     * This prevents cumulative handlers from conflicting.
     */
    _rebindAllCharts() {
        // Unsubscribe all existing handlers using v5 API
        for (const {chart, handler} of this._syncUnsubscribes) {
            try {
                chart.timeScale().unsubscribeVisibleTimeRangeChange(handler);
            } catch (e) {
                // Ignore errors during cleanup
            }
        }
        this._syncUnsubscribes = [];

        // Create a single sync handler that works with all current charts
        const chartCount = this.bound_charts.length;

        for (let sourceIndex = 0; sourceIndex < chartCount; sourceIndex++) {
            const sourceChart = this.bound_charts[sourceIndex];

            const syncHandler = () => {
                // Prevent recursive sync (when we apply options, it triggers another event)
                if (this._isSyncing) return;

                const now = Date.now();
                if (now - this._lastSyncTime < 50) return;

                this._isSyncing = true;
                this._lastSyncTime = now;

                try {
                    const barSpacing = sourceChart.timeScale().options().barSpacing;
                    const scrollPosition = sourceChart.timeScale().scrollPosition();

                    // Apply to all other charts
                    for (let i = 0; i < this.bound_charts.length; i++) {
                        if (i !== sourceIndex) {
                            this.bound_charts[i].timeScale().applyOptions({
                                rightOffset: scrollPosition,
                                barSpacing: barSpacing
                            });
                        }
                    }
                } finally {
                    // Use setTimeout to reset flag after current event loop
                    setTimeout(() => {
                        this._isSyncing = false;
                    }, 0);
                }
            };

            // Subscribe and store chart+handler for later unsubscribe
            sourceChart.timeScale().subscribeVisibleTimeRangeChange(syncHandler);
            this._syncUnsubscribes.push({chart: sourceChart, handler: syncHandler});
        }
    }

    // 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`);

        // v5 API: use createSeriesMarkers instead of series.setMarkers
        // Clear existing markers primitive if it exists
        if (this.markersPrimitive) {
            this.markersPrimitive.setMarkers([]);
        }
        // Create new markers primitive
        this.markersPrimitive = LightweightCharts.createSeriesMarkers(this.candleSeries, markers);
    }

    // Clear all trade markers from chart
    clearTradeMarkers() {
        // v5 API: clear markers via the markers primitive
        if (this.markersPrimitive) {
            this.markersPrimitive.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;
        }
    }


}
