class Indicator_Output {
    constructor(name) {
        this.legend = {};
    }
    create_legend(name, chart, lineSeries) {
        // Create legend div and append it to the output element
        let target_div = document.getElementById('indicator_output');
        this.legend[name] = document.createElement('div');
        this.legend[name].className = 'legend';
        this.legend[name].style.opacity = 0.1; // Initially mostly transparent
        this.legend[name].style.transition = 'opacity 1s ease-out'; // Smooth transition for fade-out
        target_div.appendChild(this.legend[name]);
        this.legend[name].style.display = 'block';
        this.legend[name].style.left = 3 + 'px';
        this.legend[name].style.top = 3 + 'px';
        // subscribe set legend text to crosshair moves
        // v5 API: seriesPrices renamed to seriesData, returns full data item
        chart.subscribeCrosshairMove((param) => {
            const data = param.seriesData.get(lineSeries);
            // Extract value from data item (could be {value} or {close} etc)
            const priceValue = data ? (data.value !== undefined ? data.value : data.close) : undefined;
            this.set_legend_text(priceValue, name);
        });
    }

    set_legend_text(priceValue, name) {
        // Ensure the legend for the name exists
        if (!this.legend[name]) {
            console.warn(`Legend for ${name} not found, skipping legend update.`);
            return;
        }
        // Callback assigned to fire on crosshair movements.
        let val = 'n/a';
        if (priceValue !== undefined) {
            val = (Math.round(priceValue * 100) / 100).toFixed(2);
        }

        // Update legend text
        this.legend[name].innerHTML = `${name} <span style="color:rgba(4, 111, 232, 1)">${val}</span>`;

        // Make legend fully visible
        this.legend[name].style.opacity = 1;
        this.legend[name].style.display = 'block';

        // Set a timeout to fade out the legend after 3 seconds
        clearTimeout(this.legend[name].fadeTimeout); // Clear any previous timeout to prevent conflicts
        this.legend[name].fadeTimeout = setTimeout(() => {
            this.legend[name].style.opacity = 0.1; // Gradually fade out
            // Set another timeout to hide the element after the fade-out transition
            setTimeout(() => {
                this.legend[name].style.display = 'none';
            }, 1000); // Wait for the fade-out transition to complete (1s)
        }, 1000);
    }
    clear_legend(name) {
        // Remove the legend div from the DOM
        for (const key in this.legend) {
            if (key.startsWith(name)) {
                this.legend[key].remove();  // Remove the legend from the DOM
                delete this.legend[key];    // Remove the reference from the object
            }
        }
    }
}
iOutput = new Indicator_Output();

// Create a global map to store the mappings
const indicatorMap = new Map();

class Indicator {
    constructor(name) {
        // The name of the indicator.
        this.name = name;
        this.lines = [];
        this.hist = [];
        this.outputs = [];
    }

    static getIndicatorConfig() {
        return {
            args: ['name'],
            class: this
        };
    }

    init(data) {
        console.log(this.name + ': init() unimplemented.');
    }

    update(data) {
        console.log(this.name + ': update() unimplemented.');
    }

    addHist(name, chart, color = '#26a69a') {
        // v5 API: use addSeries with HistogramSeries
        this.hist[name] = chart.addSeries(LightweightCharts.HistogramSeries, {
            color: color,
            priceFormat: {
                type: 'price',
            },
            priceScaleId: 'volume_ps',
        });
        // v5: scaleMargins must be set on the price scale, not series options
        // Volume should only take up bottom 15% of the chart
        chart.priceScale('volume_ps').applyOptions({
            scaleMargins: {
                top: 0.85,
                bottom: 0,
            },
        });
    }

    addLine(name, chart, color, lineWidth) {
        // v5 API: use addSeries with LineSeries
        this.lines[name] = chart.addSeries(LightweightCharts.LineSeries, {
            color: color,
            lineWidth: lineWidth
        });
        // Initialise the crosshair legend for the charts with a unique name for each line.
        iOutput.create_legend(`${this.name}_${name}`, chart, this.lines[name]);
    }

    setLine(lineName, data, value_name) {

        let priceValue;

        // Check if the data is a multi-value object
        if (typeof data === 'object' && data !== null && value_name in data) {
            // Multi-value indicator: Extract the array for the specific key
            const processedData = data[value_name];

            // Set the data for the line
            this.lines[lineName].setData(processedData);

            // Isolate the last value provided and round to 2 decimal places
            priceValue = processedData.at(-1).value;

            // Update the display and legend for multi-value indicators
            this.updateDisplay(lineName, { [value_name]: priceValue }, 'value');
        } else {
            // Single-value indicator: Initialize the data directly
            this.lines[lineName].setData(data);

            // Isolate the last value provided and round to 2 decimal places
            priceValue = data.at(-1).value;

            // Update the display and legend for single-value indicators
            this.updateDisplay(lineName, priceValue, value_name);
        }
        iOutput.set_legend_text(priceValue, `${this.name}_${lineName}`);
    }

    updateDisplay(name, priceValue, value_name) {
        // Try the old element format first (legacy chart-based display)
        let element = document.getElementById(this.name + '_' + value_name);
        if (element) {
            if (typeof priceValue === 'object' && priceValue !== null) {
                // Handle multiple values by joining them into a single string with labels
                let currentValues = element.value ? element.value.split(', ').reduce((acc, pair) => {
                    let [key, val] = pair.split(': ');
                    if (!isNaN(parseFloat(val))) {
                        acc[key] = parseFloat(val);
                    }
                    return acc;
                }, {}) : {};

                // Update current values with the new key-value pairs
                Object.assign(currentValues, priceValue);

                // Set the updated values back to the element
                element.value = Object.entries(currentValues)
                    .filter(([key, value]) => !isNaN(value)) // Skip NaN values
                    .map(([key, value]) => `${key}: ${(Math.round(value * 100) / 100).toFixed(2)}`)
                    .join(', ');  // Use comma for formatting
            } else {
                // Handle simple values as before
                element.value = (Math.round(priceValue * 100) / 100).toFixed(2);
            }

            // Adjust the element styling dynamically for wrapping and height
            element.style.height = 'auto';  // Reset height
            element.style.height = (element.scrollHeight) + 'px';  // Adjust height based on content
        } else {
            // Try the new card-based display element
            const cardElement = document.getElementById(`indicator_card_value_${this.name}`);
            if (cardElement) {
                let displayValue = '--';
                if (typeof priceValue === 'object' && priceValue !== null) {
                    // For object values, get the first numeric value
                    const values = Object.values(priceValue).filter(v => typeof v === 'number' && !isNaN(v));
                    if (values.length > 0) {
                        displayValue = this._formatDisplayValue(values[0]);
                    }
                } else if (typeof priceValue === 'number' && !isNaN(priceValue)) {
                    displayValue = this._formatDisplayValue(priceValue);
                }
                cardElement.textContent = displayValue;
            }
            // Silently ignore if neither element exists (may be during initialization)
        }
    }

    /**
     * Formats a numeric value for display in cards
     */
    _formatDisplayValue(value) {
        if (value === null || value === undefined || isNaN(value)) return '--';
        if (Math.abs(value) >= 1000) {
            return value.toFixed(0);
        } else if (Math.abs(value) >= 100) {
            return value.toFixed(1);
        } else {
            return value.toFixed(2);
        }
    }

    setHist(name, data) {
        this.hist[name].setData(data);
    }

    updateLine(name, data, value_name) {

        // Check if the data is a multi-value object
        if (typeof data === 'object' && data !== null && value_name in data) {
            // Multi-value indicator: Extract the array for the specific key
            const processedData = data[value_name];

            // Update the line-set data in the chart
            this.lines[name].update(processedData);

            // Isolate the last value provided and round to 2 decimal places
            const priceValue = processedData.at(-1).value;

            // Update the display and legend for multi-value indicators
            this.updateDisplay(name, { [value_name]: priceValue }, 'value');
            iOutput.set_legend_text(priceValue, `${this.name}_${name}`);
    } else {
            // Single-value indicator: Initialize the data directly
            this.lines[name].update(data);

            // Isolate the last value provided and round to 2 decimal places
            const priceValue = data.at(-1).value;

            // Update the display and legend for single-value indicators
            this.updateDisplay(name, priceValue, value_name);
            iOutput.set_legend_text(priceValue, `${this.name}_${name}`);
        }
    }

    updateHist(name, data) {
        this.hist[name].update(data);
    }

    removeFromChart(chart) {
        // Ensure the chart object is passed
        if (!chart) {
            console.error("Chart object is missing.");
            return;
        }

        // Remove all line series associated with this indicator
        for (let lineName in this.lines) {
            if (this.lines[lineName]) {
                chart.removeSeries(this.lines[lineName]);
                delete this.lines[lineName];
            }
        }

        // Remove all histogram series associated with this indicator
        for (let histName in this.hist) {
            if (this.hist[histName]) {
                chart.removeSeries(this.hist[histName]);
                delete this.hist[histName];
            }
        }

        // Remove the legend from the crosshair (if any)
        if (iOutput.legend[this.name]) {
            iOutput.legend[this.name].remove();
            delete iOutput.legend[this.name];
        }
    }

}

class SMA extends Indicator {
    constructor(name, chart, color, lineWidth = 2) {
        super(name);
        this.addLine('line', chart, color, lineWidth);
        this.outputs = ['value'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'chart_1', 'color'],
            class: this
        };
    }

    init(data) {
        this.setLine('line', data, 'value');
    }

    update(data) {
        this.updateLine('line', data[0], 'value');
    }
}
// Register SMA in the map
indicatorMap.set("SMA", SMA);

class Linear_Regression extends SMA {
    // Inherits getIndicatorConfig from SMA
}
indicatorMap.set("LREG", Linear_Regression);

class EMA extends SMA {
    // Inherits getIndicatorConfig from SMA
}
indicatorMap.set("EMA", EMA);

class RSI extends Indicator {
    constructor(name, charts, color, lineWidth = 2) {
        super(name);
        if (!charts.hasOwnProperty('chart2')) {
            charts.create_RSI_chart();
        }
        let chart = charts.chart2;
        this.addLine('line', chart, color, lineWidth);
        this.outputs = ['value'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'charts', 'color'],
            class: this
        };
    }

    init(data) {
        this.setLine('line', data, 'value');
    }

    update(data) {
        this.updateLine('line', data[0], 'value');
    }
}
indicatorMap.set("RSI", RSI);

class BollingerPercentB extends Indicator {
    constructor(name, charts, color, lineWidth = 2) {
        super(name);
        if (!charts.hasOwnProperty('chart4')) {
            charts.create_PercentB_chart();
        }
        let chart = charts.chart4;
        this.addLine('line', chart, color, lineWidth);
        this.outputs = ['value'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'charts', 'color'],
            class: this
        };
    }

    init(data) {
        this.setLine('line', data, 'value');
    }

    update(data) {
        this.updateLine('line', data[0], 'value');
    }
}
indicatorMap.set("BOL%B", BollingerPercentB);

class MACD extends Indicator {
    constructor(name, charts, color_1, color_2, lineWidth = 2) {
        super(name);
        if (!charts.hasOwnProperty('chart3')) {
            charts.create_MACD_chart();
        }
        let chart = charts.chart3;

        this.addLine('line_m', chart, color_1, lineWidth);
        this.addLine('line_s', chart, color_2, lineWidth);
        this.addHist(name, chart);
        this.outputs = ['macd', 'signal', 'hist'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'charts', 'color_1', 'color_2'],
            class: this
        };
    }

    init(data) {
        // Filter out rows where macd, signal, or hist are null
        const filteredData = data.filter(row => row.macd !== null && row.signal !== null && row.hist !== null);

        if (filteredData.length > 0) {
            // Prepare the filtered data for the MACD line
            const line_m = filteredData.map(row => ({
                time: row.time,
                value: row.macd
            }));

            // Set the 'line_m' for the MACD line
            this.setLine('line_m', { macd: line_m }, 'macd');

            // Prepare the filtered data for the signal line
            const line_s = filteredData.map(row => ({
                time: row.time,
                value: row.signal
            }));

            // Set the 'line_s' for the signal line
            this.setLine('line_s', { signal: line_s }, 'signal');

            // Set the histogram data
            this.setHist(this.name, filteredData.map(row => ({
                time: row.time,
                value: row.hist
            })));
        } else {
            console.error('No valid MACD data found.');
        }
    }


    update(data) {
        // Filter out rows where macd, signal, or hist are null
        const filteredData = data.filter(row => row.macd !== null && row.signal !== null && row.hist !== null);

        if (filteredData.length > 0) {
            // Update the 'macd' line
            const line_m = filteredData.map(row => ({
                time: row.time,
                value: row.macd
            }));
            this.updateLine('line_m', { macd: line_m }, 'macd');

            // Update the 'signal' line
            const line_s = filteredData.map(row => ({
                time: row.time,
                value: row.signal
            }));
            this.updateLine('line_s', { signal: line_s }, 'signal');

            // Update the 'hist' (histogram) bar
            const hist_data = filteredData.map(row => ({
                time: row.time,
                value: row.hist
            }));
            this.updateHist('hist', hist_data);
        } else {
            console.error('No valid MACD data found for update.');
        }
    }
}
indicatorMap.set("MACD", MACD);


class ATR extends Indicator {
    // Inherits getIndicatorConfig from Indicator

    init(data) {
        this.updateDisplay(this.name, data.at(-1).value, 'value');
        this.outputs = ['value'];
    }

    update(data) {
        this.updateDisplay(this.name, data[0].value, 'value');
    }
}
indicatorMap.set("ATR", ATR);


class Volume extends Indicator {
    constructor(name, chart) {
        super(name);
        this.addHist(name, chart);
        // Note: scaleMargins are set in addHist() on the price scale (v5 API)
        this.outputs = ['value'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'chart_1'],
            class: this
        };
    }

    init(data) {
        this.setHist(this.name, data);
    }

    update(data) {
        this.updateHist(this.name, data[0]);
    }
}
indicatorMap.set("Volume", Volume);

class Bolenger extends Indicator {
    constructor(name, chart, color_1, color_2, color_3, lineWidth = 2) {
        super(name);
        this.addLine('line_u', chart, color_1, lineWidth);
        this.addLine('line_m', chart, color_2, lineWidth);
        this.addLine('line_l', chart, color_3, lineWidth);
        this.outputs = ['upper', 'middle', 'lower'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'chart_1', 'color_1', 'color_2', 'color_3'],
            class: this
        };
    }

    init(data) {
        // Filter out rows where upper, middle, or lower are null
        const filteredData = data.filter(row => row.upper !== null && row.middle !== null && row.lower !== null);

        if (filteredData.length > 0) {
            // Set the 'line_u' for the upper line
            const line_u = filteredData.map(row => ({
                time: row.time,
                value: row.upper
            }));
            this.setLine('line_u', { upper: line_u }, 'upper');

            // Set the 'line_m' for the middle line
            const line_m = filteredData.map(row => ({
                time: row.time,
                value: row.middle
            }));
            this.setLine('line_m', { middle: line_m }, 'middle');

            // Set the 'line_l' for the lower line
            const line_l = filteredData.map(row => ({
                time: row.time,
                value: row.lower
            }));
            this.setLine('line_l', { lower: line_l }, 'lower');
        } else {
            console.error('No valid data found for init.');
        }
    }

    update(data) {
        // Filter out rows where upper, middle, or lower are null
        const filteredData = data.filter(row => row.upper !== null && row.middle !== null && row.lower !== null);

        if (filteredData.length > 0) {
            // Update the 'upper' line
            const line_u = filteredData.map(row => ({
                time: row.time,
                value: row.upper
            }));
            this.updateLine('line_u', { upper: line_u }, 'upper');

            // Update the 'middle' line
            const line_m = filteredData.map(row => ({
                time: row.time,
                value: row.middle
            }));
            this.updateLine('line_m', { middle: line_m }, 'middle');

            // Update the 'lower' line
            const line_l = filteredData.map(row => ({
                time: row.time,
                value: row.lower
            }));
            this.updateLine('line_l', { lower: line_l }, 'lower');
        } else {
            console.error('No valid data found for update.');
        }
    }
}
indicatorMap.set("BOLBands", Bolenger);


// Candlestick Pattern Indicator class
// Displays pattern signals as histogram bars in a dedicated patterns chart
class CandlestickPattern extends Indicator {
    constructor(name, charts, bullish_color, bearish_color) {
        super(name);
        // Create patterns chart if it doesn't exist
        if (!charts.hasOwnProperty('chart5') || !charts.chart5) {
            charts.create_patterns_chart();
        }
        let chart = charts.chart5;

        // Store colors for pattern signals (hex format for color picker compatibility)
        this.bullish_color = bullish_color || '#00C853';
        this.bearish_color = bearish_color || '#FF5252';

        // Add histogram series for this pattern
        this.addPatternHist(name, chart);
        this.outputs = ['value'];
    }

    static getIndicatorConfig() {
        return {
            args: ['name', 'charts', 'bullish_color', 'bearish_color'],
            class: this
        };
    }

    addPatternHist(name, chart) {
        // v5 API: use addSeries with HistogramSeries
        this.hist[name] = chart.addSeries(LightweightCharts.HistogramSeries, {
            priceFormat: {
                type: 'price',
            },
            priceScaleId: 'right',
        });
        // Create legend for the pattern
        iOutput.create_legend(`${this.name}_pattern`, chart, this.hist[name]);
    }

    init(data) {
        // Transform data to add colors based on signal value
        // Keep ALL data points (including zeros) to maintain proper time scale alignment
        // Zero values will have no visible bar (height = 0) but the timestamp is needed
        const histData = data.map(d => ({
            time: d.time,
            value: d.value,
            // Use bullish/bearish colors for signals, gray for no signal (won't be visible anyway)
            color: d.value > 0 ? this.bullish_color
                 : d.value < 0 ? this.bearish_color
                 : '#808080'
        }));
        this.setHist(this.name, histData);

        // Update display with latest signal
        const latestValue = data.at(-1).value;
        let displayText = latestValue > 0 ? 'Bullish' : latestValue < 0 ? 'Bearish' : 'None';
        this.updateDisplay(this.name, displayText, 'value');
    }

    update(data) {
        // Transform data to add colors based on signal value
        // Keep ALL data points to maintain time scale alignment with other charts
        const histData = data.map(d => ({
            time: d.time,
            value: d.value,
            color: d.value > 0 ? this.bullish_color
                 : d.value < 0 ? this.bearish_color
                 : '#808080'
        }));
        this.setHist(this.name, histData);

        // Update display with latest signal
        const latestValue = data.at(-1).value;
        let displayText = latestValue > 0 ? 'Bullish' : latestValue < 0 ? 'Bearish' : 'None';
        this.updateDisplay(this.name, displayText, 'value');
    }

    updateDisplay(name, value, value_name) {
        // Override to handle text display for patterns
        let element = document.getElementById(this.name + '_' + value_name);
        if (element) {
            element.value = value;
        }
    }
}

// Register all candlestick pattern types to use the same CandlestickPattern class
// Single candle patterns
indicatorMap.set("CDL_DOJI", CandlestickPattern);
indicatorMap.set("CDL_HAMMER", CandlestickPattern);
indicatorMap.set("CDL_INVERTEDHAMMER", CandlestickPattern);
indicatorMap.set("CDL_SHOOTINGSTAR", CandlestickPattern);
indicatorMap.set("CDL_SPINNINGTOP", CandlestickPattern);
indicatorMap.set("CDL_MARUBOZU", CandlestickPattern);
indicatorMap.set("CDL_DRAGONFLYDOJI", CandlestickPattern);
indicatorMap.set("CDL_GRAVESTONEDOJI", CandlestickPattern);
indicatorMap.set("CDL_HANGINGMAN", CandlestickPattern);
indicatorMap.set("CDL_HIGHWAVE", CandlestickPattern);
indicatorMap.set("CDL_LONGLEGGEDDOJI", CandlestickPattern);
indicatorMap.set("CDL_LONGLINE", CandlestickPattern);
indicatorMap.set("CDL_SHORTLINE", CandlestickPattern);
indicatorMap.set("CDL_RICKSHAWMAN", CandlestickPattern);
indicatorMap.set("CDL_TAKURI", CandlestickPattern);
indicatorMap.set("CDL_BELTHOLD", CandlestickPattern);
indicatorMap.set("CDL_CLOSINGMARUBOZU", CandlestickPattern);

// Two candle patterns
indicatorMap.set("CDL_ENGULFING", CandlestickPattern);
indicatorMap.set("CDL_HARAMI", CandlestickPattern);
indicatorMap.set("CDL_HARAMICROSS", CandlestickPattern);
indicatorMap.set("CDL_PIERCING", CandlestickPattern);
indicatorMap.set("CDL_DARKCLOUDCOVER", CandlestickPattern);
indicatorMap.set("CDL_COUNTERATTACK", CandlestickPattern);
indicatorMap.set("CDL_DOJISTAR", CandlestickPattern);
indicatorMap.set("CDL_HOMINGPIGEON", CandlestickPattern);
indicatorMap.set("CDL_KICKING", CandlestickPattern);
indicatorMap.set("CDL_KICKINGBYLENGTH", CandlestickPattern);
indicatorMap.set("CDL_MATCHINGLOW", CandlestickPattern);
indicatorMap.set("CDL_INNECK", CandlestickPattern);
indicatorMap.set("CDL_ONNECK", CandlestickPattern);
indicatorMap.set("CDL_THRUSTING", CandlestickPattern);
indicatorMap.set("CDL_SEPARATINGLINES", CandlestickPattern);
indicatorMap.set("CDL_STICKSANDWICH", CandlestickPattern);
indicatorMap.set("CDL_GAPSIDESIDEWHITE", CandlestickPattern);
indicatorMap.set("CDL_2CROWS", CandlestickPattern);

// Three+ candle patterns
indicatorMap.set("CDL_MORNINGSTAR", CandlestickPattern);
indicatorMap.set("CDL_EVENINGSTAR", CandlestickPattern);
indicatorMap.set("CDL_MORNINGDOJISTAR", CandlestickPattern);
indicatorMap.set("CDL_EVENINGDOJISTAR", CandlestickPattern);
indicatorMap.set("CDL_3WHITESOLDIERS", CandlestickPattern);
indicatorMap.set("CDL_3BLACKCROWS", CandlestickPattern);
indicatorMap.set("CDL_3INSIDE", CandlestickPattern);
indicatorMap.set("CDL_3OUTSIDE", CandlestickPattern);
indicatorMap.set("CDL_3LINESTRIKE", CandlestickPattern);
indicatorMap.set("CDL_3STARSINSOUTH", CandlestickPattern);
indicatorMap.set("CDL_ABANDONEDBABY", CandlestickPattern);
indicatorMap.set("CDL_ADVANCEBLOCK", CandlestickPattern);
indicatorMap.set("CDL_BREAKAWAY", CandlestickPattern);
indicatorMap.set("CDL_CONCEALBABYSWALL", CandlestickPattern);
indicatorMap.set("CDL_IDENTICAL3CROWS", CandlestickPattern);
indicatorMap.set("CDL_LADDERBOTTOM", CandlestickPattern);
indicatorMap.set("CDL_MATHOLD", CandlestickPattern);
indicatorMap.set("CDL_RISEFALL3METHODS", CandlestickPattern);
indicatorMap.set("CDL_STALLEDPATTERN", CandlestickPattern);
indicatorMap.set("CDL_TASUKIGAP", CandlestickPattern);
indicatorMap.set("CDL_TRISTAR", CandlestickPattern);
indicatorMap.set("CDL_UNIQUE3RIVER", CandlestickPattern);
indicatorMap.set("CDL_UPSIDEGAP2CROWS", CandlestickPattern);
indicatorMap.set("CDL_XSIDEGAP3METHODS", CandlestickPattern);
indicatorMap.set("CDL_HIKKAKE", CandlestickPattern);
indicatorMap.set("CDL_HIKKAKEMOD", CandlestickPattern);


class Indicators {
    constructor(comms) {
        // Contains instantiated indicators.
        this.i_objs = {};
        this.comms = comms;
        // Store indicator configurations for editing
        this.indicators = {};
        // Store indicator data (values) for card display
        this.indicator_data = {};
    }

    create_indicators(indicators, charts) {
        for (let name in indicators) {
            if (!indicators[name].visible) continue;

            let i_type = indicators[name].type;
            let IndicatorClass = indicatorMap.get(i_type);

            if (IndicatorClass) {
                let { args, class: IndicatorConstructor } = IndicatorClass.getIndicatorConfig();

                let preparedArgs = args.map(arg => {
                    if (arg === 'name') return name;
                    if (arg === 'charts') return charts;
                    if (arg === 'chart_1') return charts.chart_1;
                    if (arg === 'color') return indicators[name].color;
                    if (arg === 'color_1') return  indicators[name].color_1 || 'red';
                    if (arg === 'color_2') return  indicators[name].color_2 || 'white';
                    if (arg === 'color_3') return  indicators[name].color_3 || 'blue';
                    if (arg === 'bullish_color') return indicators[name].bullish_color || '#00C853';
                    if (arg === 'bearish_color') return indicators[name].bearish_color || '#FF5252';
                });

                this.i_objs[name] = new IndicatorConstructor(...preparedArgs);
            } else {
                console.error(`Unknown indicator type: ${i_type}`);
            }
        }
    }

    // Method to retrieve outputs for each indicator
    getIndicatorOutputs() {
        let indicatorOutputs = {};
        for (let name in this.i_objs) {
            if (this.i_objs[name].outputs) {
                indicatorOutputs[name] = this.i_objs[name].outputs;
            } else {
                indicatorOutputs[name] = ['value'];  // Default output if not defined
            }
        }

        // Include external indicators (historical API data)
        const externalIndicators = window.UI?.signals?.externalIndicators || [];
        for (const extInd of externalIndicators) {
            // External indicators have a single 'value' output
            indicatorOutputs[extInd.name] = ['value'];
        }

        return indicatorOutputs;
    }

    addToCharts(charts, idata){
        /*
            Receives indicator data, creates and stores the indicator
            objects, then inserts the data into the charts.
        */
        // Always set up the visibility form event handler
        this._setupIndicatorForm();

        // Store indicator configurations for card rendering and editing
        if (idata.indicators) {
            this.indicators = idata.indicators;
        }

        // Store indicator data (values) for card display
        if (idata.indicator_data) {
            this.indicator_data = idata.indicator_data;
        }

        if (idata.indicators && Object.keys(idata.indicators).length > 0) {
            this.create_indicators(idata.indicators, charts);
            // Initialize each indicator with the data directly
            if (idata.indicator_data && Object.keys(idata.indicator_data).length > 0) {
                this.init_indicators(idata.indicator_data);
            } else {
                console.warn('Indicator data is empty. No indicators to initialize.');
            }
        } else {
            console.log('No indicators defined for this user.');
        }

        // Render indicator cards in the UI (after init so values are available)
        this.renderIndicators();
    }

    _setupIndicatorForm(){
        // Set up the visibility form event handler
        const form = document.getElementById('indicator-form');
        if (form && !form._hasSubmitListener) {
            form.addEventListener('submit', this.handleFormSubmit.bind(this));
            form._hasSubmitListener = true;  // Prevent duplicate listeners
        }
    }


    init_indicators(data){
        // Loop through all the indicators.
        for (name in data){
            // Call the initialization function for each indicator.
            if (!this.i_objs[name]){
                console.log('could not load:', name);
                continue;
            }
            this.i_objs[name].init(data[name]);
        }
    }

    // This updates all the indicator data (re-initializes with full dataset)
    update(updates){
        for (let name in updates){
            if (window.UI.indicators.i_objs[name]) {
                // Use init() to refresh with full data on candle close
                window.UI.indicators.i_objs[name].init(updates[name]);

                // Update stored data and card display
                this.indicator_data[name] = updates[name];
                this._updateCardValue(name, updates[name]);
            } else {
                console.warn(`Indicator "${name}" not found in i_objs, skipping update`);
            }
        }
    }

    /**
     * Updates a single indicator card's displayed value
     */
    _updateCardValue(name, data) {
        const valueEl = document.getElementById(`indicator_card_value_${name}`);
        if (!valueEl) return;

        let displayValue = '--';
        if (data && Array.isArray(data) && data.length > 0) {
            const lastPoint = data[data.length - 1];
            if (lastPoint.value !== undefined && lastPoint.value !== null) {
                displayValue = this._formatValue(lastPoint.value);
            } else if (lastPoint.macd !== undefined) {
                displayValue = this._formatValue(lastPoint.macd);
            } else if (lastPoint.middle !== undefined) {
                displayValue = this._formatValue(lastPoint.middle);
            } else if (lastPoint.upper !== undefined) {
                displayValue = this._formatValue(lastPoint.upper);
            }
        }
        valueEl.textContent = displayValue;
    }

    deleteIndicator(indicator, event) {
        this.comms.deleteIndicator(indicator).then(response => {
            if (response.success) {
                const indicatorElement = event.target.closest('.indicator-row');
                indicatorElement.remove();  // Remove from DOM

                // Remove the indicator from the chart and legend
                if (this.i_objs[indicator]) {
                    let chart;

                    // Determine which chart the indicator is on, based on its type
                    const indicatorType = this.i_objs[indicator].constructor.name;
                    if (indicator.includes('RSI')) {
                        chart = window.UI.charts.chart2;  // Assume RSI is on chart2
                    } else if (indicator.includes('MACD')) {
                        chart = window.UI.charts.chart3;  // Assume MACD is on chart3
                    } else if (indicator.includes('%B') || indicator.includes('PercentB')) {
                        chart = window.UI.charts.chart4;  // %B is on chart4
                    } else if (indicatorType === 'CandlestickPattern') {
                        chart = window.UI.charts.chart5;  // Candlestick patterns on chart5
                    } else {
                        chart = window.UI.charts.chart_1;  // Default to the main chart
                    }

                    // Pass the correct chart object when removing the indicator
                    this.i_objs[indicator].removeFromChart(chart);

                    // Remove the indicator object from i_objs
                    delete this.i_objs[indicator];

                    // Optionally: Clear the legend entry
                    iOutput.clear_legend(indicator);
                }
            } else {
                alert('Failed to delete the indicator.');
            }
        });
    }

    // This updates a specific indicator
    updateIndicator(event) {
        event.preventDefault(); // Prevent default form submission behavior

        const row = event.target.closest('.indicator-row');
        const inputs = row.querySelectorAll('input, select');

        // Gather the indicator name from the row
        const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name
        const indicatorName = nameDiv.innerText.trim(); // Get the indicator name

        // Initialize formObj with the name of the indicator
        const formObj = {
            name: indicatorName,
            visible: false,  // Default value for visible (will be updated based on the checkbox input)
            source: {},      // Initialize the source object directly at the top level
            properties: {}   // Initialize the properties object
        };

        // Define an exclusion list for properties that should not be parsed as numbers
        const exclusionFields = ['custom_field_name']; // Add field names that should NOT be parsed as numbers

        // Function to check if a value contains mixed data (e.g., 'abc123')
        const isMixedData = (value) => /\D/.test(value) && /\d/.test(value);

        // Iterate over each input (text, checkbox, select) and add its name and value to formObj
        inputs.forEach(input => {
            let value = input.value;

            // Handle the 'visible' checkbox separately
            if (input.name === 'visible') {
                formObj.visible = input.checked;
            } else if (['market', 'timeframe', 'exchange'].includes(input.name)) {
                // Directly map inputs to source object fields
                formObj.source[input.name] = input.value;
            } else {
                // Check if the value should be parsed as a number
                if (!exclusionFields.includes(input.name) && !isMixedData(value)) {
                    const parsedValue = parseFloat(value);
                    value = isNaN(parsedValue) ? value : parsedValue;
                } else if (input.type === 'checkbox') {
                    value = input.checked;
                }

                // Add the processed value to the properties object
                formObj.properties[input.name] = value;
            }
        });

        // Call comms to send data to the server
        this.comms.updateIndicator(formObj).then(response => {
            if (response.success) {
                window.location.reload(); // This triggers a full page refresh
            } else {
                alert('Failed to update the indicator.');
            }
        }).catch(error => {
            console.error('Error updating indicator:', error);
            alert('An unexpected error occurred while updating the indicator.');
        });
    }



    add_to_list(){
        // Adds user input to a list and displays it in a HTML element.
        // called from html button click add property
        // Collect the property name and value input by the user
        let n = document.getElementById("new_prop_name").value.trim();
        let v = document.getElementById("new_prop_value").value.trim();

        // Ensure the property name and value are not empty
        if (!n || !v) {
            alert("Property name and value are required.");
            return;
        }

        // Converts css color name to hex
        if (n === 'color'){
            // list of valid css colors
            let colours = {
                "aliceblue":"#f0f8ff", "antiquewhite":"#faebd7", "aqua":"#00ffff", "aquamarine":"#7fffd4", "azure":"#f0ffff",  "beige":"#f5f5dc", "bisque":"#ffe4c4", "black":"#000000", "blanchedalmond":"#ffebcd", "blue":"#0000ff", "blueviolet":"#8a2be2", "brown":"#a52a2a", "burlywood":"#deb887",  "cadetblue":"#5f9ea0", "chartreuse":"#7fff00", "chocolate":"#d2691e", "coral":"#ff7f50", "cornflowerblue":"#6495ed", "cornsilk":"#fff8dc", "crimson":"#dc143c", "cyan":"#00ffff",  "darkblue":"#00008b", "darkcyan":"#008b8b", "darkgoldenrod":"#b8860b", "darkgray":"#a9a9a9", "darkgreen":"#006400", "darkkhaki":"#bdb76b", "darkmagenta":"#8b008b", "darkolivegreen":"#556b2f",  "darkorange":"#ff8c00", "darkorchid":"#9932cc", "darkred":"#8b0000", "darksalmon":"#e9967a", "darkseagreen":"#8fbc8f", "darkslateblue":"#483d8b", "darkslategray":"#2f4f4f", "darkturquoise":"#00ced1",  "darkviolet":"#9400d3", "deeppink":"#ff1493", "deepskyblue":"#00bfff", "dimgray":"#696969", "dodgerblue":"#1e90ff",  "firebrick":"#b22222", "floralwhite":"#fffaf0", "forestgreen":"#228b22", "fuchsia":"#ff00ff",  "gainsboro":"#dcdcdc", "ghostwhite":"#f8f8ff", "gold":"#ffd700", "goldenrod":"#daa520", "gray":"#808080", "green":"#008000", "greenyellow":"#adff2f",
                "honeydew":"#f0fff0", "hotpink":"#ff69b4", "indianred ":"#cd5c5c", "indigo":"#4b0082", "ivory":"#fffff0", "khaki":"#f0e68c",  "lavender":"#e6e6fa", "lavenderblush":"#fff0f5", "lawngreen":"#7cfc00", "lemonchiffon":"#fffacd", "lightblue":"#add8e6", "lightcoral":"#f08080", "lightcyan":"#e0ffff", "lightgoldenrodyellow":"#fafad2",  "lightgrey":"#d3d3d3", "lightgreen":"#90ee90", "lightpink":"#ffb6c1", "lightsalmon":"#ffa07a", "lightseagreen":"#20b2aa", "lightskyblue":"#87cefa", "lightslategray":"#778899", "lightsteelblue":"#b0c4de",  "lightyellow":"#ffffe0", "lime":"#00ff00", "limegreen":"#32cd32", "linen":"#faf0e6",  "magenta":"#ff00ff", "maroon":"#800000", "mediumaquamarine":"#66cdaa", "mediumblue":"#0000cd", "mediumorchid":"#ba55d3", "mediumpurple":"#9370d8", "mediumseagreen":"#3cb371", "mediumslateblue":"#7b68ee",        "mediumspringgreen":"#00fa9a", "mediumturquoise":"#48d1cc", "mediumvioletred":"#c71585", "midnightblue":"#191970", "mintcream":"#f5fffa", "mistyrose":"#ffe4e1", "moccasin":"#ffe4b5", "navajowhite":"#ffdead", "navy":"#000080",  "oldlace":"#fdf5e6", "olive":"#808000", "olivedrab":"#6b8e23", "orange":"#ffa500", "orangered":"#ff4500", "orchid":"#da70d6",  "palegoldenrod":"#eee8aa",
                "palegreen":"#98fb98", "paleturquoise":"#afeeee", "palevioletred":"#d87093", "papayawhip":"#ffefd5", "peachpuff":"#ffdab9", "peru":"#cd853f", "pink":"#ffc0cb", "plum":"#dda0dd", "powderblue":"#b0e0e6", "purple":"#800080",  "rebeccapurple":"#663399", "red":"#ff0000", "rosybrown":"#bc8f8f", "royalblue":"#4169e1",  "saddlebrown":"#8b4513", "salmon":"#fa8072", "sandybrown":"#f4a460", "seagreen":"#2e8b57", "seashell":"#fff5ee", "sienna":"#a0522d", "silver":"#c0c0c0", "skyblue":"#87ceeb", "slateblue":"#6a5acd", "slategray":"#708090", "snow":"#fffafa", "springgreen":"#00ff7f", "steelblue":"#4682b4",   "tan":"#d2b48c", "teal":"#008080", "thistle":"#d8bfd8", "tomato":"#ff6347", "turquoise":"#40e0d0", "violet":"#ee82ee",   "wheat":"#f5deb3", "white":"#ffffff", "whitesmoke":"#f5f5f5", "yellow":"#ffff00", "yellowgreen":"#9acd32"
            };
            // if the value is in the list of colors convert it.
            if (v.toLowerCase() in colours) {
                v = colours[v.toLowerCase()];
            }
        }
        // Create a new property row with a clear button
        const newPropHTML = `
            <div class="property-item" style="display:flex;align-items:center;">
                <button style="color:darkred;margin-right:5px;" type="button" onclick="UI.indicators.remove_prop(this)">&#10008;</button>
                <span>${n}: ${v}</span>
            </div>`;

        // Insert the new property into the property list
        document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', newPropHTML);

        // Update the hidden property object with the new property
        const propObj = JSON.parse(document.getElementById("new_prop_obj").value || '{}');
        propObj[n] = v;
        document.getElementById("new_prop_obj").value = JSON.stringify(propObj);

        // Clear the input fields
        document.getElementById("new_prop_name").value = '';
        document.getElementById("new_prop_value").value = '';
    }
    // Function to remove a property from the list
    remove_prop(buttonElement) {
        const propertyDiv = buttonElement.parentElement;
        const propertyText = propertyDiv.querySelector('span').textContent;
        const [propName] = propertyText.split(':');

        // Remove the property div from the DOM
        propertyDiv.remove();

        // Remove the property from the hidden input object
        const propObj = JSON.parse(document.getElementById("new_prop_obj").value || '{}');
        delete propObj[propName.trim()];
        document.getElementById("new_prop_obj").value = JSON.stringify(propObj);
    }
    // Call to display Create new signal dialog.
    open_form() {
        // Show the form
        document.getElementById("new_ind_form").style.display = "grid";

        // Prefill the form fields with the current chart data
        // Always use the current chart view values to ensure indicator is created
        // for the currently viewed exchange/symbol/timeframe
        const marketField = document.getElementById('ei_symbol');
        const timeframeField = document.getElementById('ei_timeframe');
        const exchangeField = document.getElementById('ei_exchange_name');

        // Always set to current chart view values
        if (window.UI.data.trading_pair) {
            marketField.value = window.UI.data.trading_pair;
        }
        if (window.UI.data.interval) {
            timeframeField.value = window.UI.data.interval;
        }
        if (window.UI.data.exchange) {
            exchangeField.value = window.UI.data.exchange;
        }
    }
    // Call to hide Create new signal dialog.
    close_form() { document.getElementById("new_ind_form").style.display = "none"; }
    submit_new_i() {
        /* Populates a hidden <input> with a value from another element then submits the form
           Used in the create indicator panel.*/

        // Perform validation
        const name = document.getElementById('newi_name').value;
        const type = document.getElementById('newi_type').value;
        let market = document.getElementById('ei_symbol').value;
        const timeframe = document.getElementById('ei_timeframe').value;
        const exchange = document.getElementById('ei_exchange_name').value;

        let errorMsg = '';

        if (!name) {
            errorMsg += 'Indicator name is required.\n';
        }
        if (!type) {
            errorMsg += 'Indicator type is required.\n';
        }
        if (!market) {
            errorMsg += 'Indicator market is required.\n';
        }
        if (!timeframe) {
            errorMsg += 'Timeframe is required.\n';
        }
        if (!exchange) {
            errorMsg += 'Exchange name is required.\n';
        }

        if (errorMsg) {
            alert(errorMsg);  // Display the error messages
            return;  // Stop form submission if there are errors
        }

        // Collect and update properties
        const propObj = {}

        // Optionally add name, type, market, timeframe, and exchange to the properties if needed
        propObj["name"] = name;
        propObj["type"] = type;
        propObj["source"] = {'market':market,'timeframe': timeframe,'exchange':exchange};
        propObj["properties"] = document.getElementById("new_prop_obj").value;

        // Call comms to send data to the server
        this.comms.submitIndicator(propObj).then(response => {
            if (response.success) {
                window.location.reload(); // This triggers a full page refresh
            } else {
                alert(response.message || 'Failed to create a new Indicator.');
            }
        });

    }
    // Method to handle form submission
    handleFormSubmit(event) {
        event.preventDefault(); // Prevent default form submission behavior

        // Get the form element
        const form = event.target;

        // Get all the checked checkboxes (indicators)
        const checkboxes = form.querySelectorAll('input[name="indicator"]:checked');
        let selectedIndicators = [];

        checkboxes.forEach(function (checkbox) {
            selectedIndicators.push(checkbox.value);
        });

        // Prepare the form data
        const formData = new FormData(form);
        formData.delete('indicator');  // Remove the single value (from original HTML behavior)

        // Append all selected indicators as a single array-like structure
        formData.append('indicator', JSON.stringify(selectedIndicators));

        // Hide the popup immediately
        const popup = document.getElementById('indicators');
        if (popup) {
            popup.style.display = 'none';
        }

        // Show a loading overlay on the page
        const overlay = document.createElement('div');
        overlay.id = 'loading-overlay';
        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:9999;';
        overlay.innerHTML = '<div style="background:white;padding:20px 40px;border-radius:10px;font-size:18px;font-weight:bold;">Updating indicators...</div>';
        document.body.appendChild(overlay);

        // Send form data via AJAX (fetch)
        fetch(form.action, {
            method: form.method,
            body: formData
        }).then(response => {
            if (response.ok) {
                // Handle success (you can reload the page or update the UI)
                window.location.reload();
            } else {
                // Remove overlay on error
                const overlay = document.getElementById('loading-overlay');
                if (overlay) overlay.remove();
                alert('Failed to update indicators.');
            }
        }).catch(error => {
            console.error('Error:', error);
            const overlay = document.getElementById('loading-overlay');
            if (overlay) overlay.remove();
            alert('An error occurred while updating the indicators.');
        });
    }

    // ==================== Card Display Methods ====================

    /**
     * Renders all indicators as icon cards in the indicators_list container
     */
    renderIndicators() {
        const container = document.getElementById('indicators_list');
        if (!container) return;

        container.innerHTML = '';

        for (const [name, indicator] of Object.entries(this.indicators)) {
            const item = document.createElement('div');
            const isVisible = indicator.visible !== false;
            item.className = `indicator-item ${isVisible ? 'visible' : 'hidden'}`;
            item.setAttribute('data-indicator-name', name);

            // Get indicator type symbol
            const symbol = this.getIndicatorSymbol(indicator.type);

            // Get current value from indicator_data
            let displayValue = '--';
            const data = this.indicator_data[name];
            if (data && Array.isArray(data) && data.length > 0) {
                const lastPoint = data[data.length - 1];
                // Handle different indicator data formats
                if (lastPoint.value !== undefined && lastPoint.value !== null) {
                    // Simple indicators (RSI, SMA, EMA, etc.)
                    displayValue = this._formatValue(lastPoint.value);
                } else if (lastPoint.macd !== undefined) {
                    // MACD - show the MACD value
                    displayValue = this._formatValue(lastPoint.macd);
                } else if (lastPoint.middle !== undefined) {
                    // Bollinger Bands - show middle value
                    displayValue = this._formatValue(lastPoint.middle);
                } else if (lastPoint.upper !== undefined) {
                    // Other band indicators
                    displayValue = this._formatValue(lastPoint.upper);
                }
            }

            const indicatorType = indicator.type || 'Unknown';
            item.innerHTML = `
                <button class="visibility-btn" onclick="UI.indicators.toggleVisibility('${name}'); event.stopPropagation();" title="${isVisible ? 'Hide' : 'Show'}">
                    ${isVisible ? '👁' : '👁‍🗨'}
                </button>
                <button class="delete-btn" onclick="UI.indicators.deleteIndicatorCard('${name}'); event.stopPropagation();" title="Delete">×</button>
                <div class="indicator-icon" title="${indicatorType}">
                    <span class="indicator-symbol">${symbol}</span>
                </div>
                <div class="indicator-name" title="${name}">${name}</div>
                <div class="indicator-type">${indicatorType}</div>
                <div class="indicator-value" id="indicator_card_value_${name}">${displayValue}</div>
            `;

            // Click to edit
            item.addEventListener('click', () => this.openEditDialog(name));

            container.appendChild(item);
        }
    }

    /**
     * Formats a numeric value for display
     */
    _formatValue(value) {
        if (value === null || value === undefined) return '--';
        if (typeof value === 'number') {
            // For large numbers, show fewer decimals
            if (Math.abs(value) >= 1000) {
                return value.toFixed(0);
            } else if (Math.abs(value) >= 100) {
                return value.toFixed(1);
            } else {
                return value.toFixed(2);
            }
        }
        return String(value);
    }

    /**
     * Returns an emoji/symbol for the given indicator type
     */
    getIndicatorSymbol(type) {
        // All keys are uppercase for consistent matching
        const symbols = {
            'RSI': '📊',
            'MACD': '📈',
            'SMA': '〰️',
            'EMA': '〰️',
            'LREG': '➡️',
            'BOLBANDS': '📉',
            'BOL%B': '📉',
            'ATR': '📏',
            'VOLUME': '📊',
        };

        if (!type) return '📈';

        const upperType = type.toUpperCase();

        // Check for candlestick patterns (they start with CDL_)
        if (upperType.startsWith('CDL_')) {
            return '🕯️';
        }

        return symbols[upperType] || '📈'; // default chart emoji
    }

    /**
     * Opens the edit dialog for an indicator
     */
    openEditDialog(indicatorName) {
        const indicator = this.indicators[indicatorName];
        if (!indicator) return;

        const form = document.getElementById('edit_indicator_form');
        if (!form) {
            console.error('Edit indicator form not found');
            return;
        }

        // Populate form fields
        document.getElementById('edit_indicator_name').value = indicatorName;
        document.getElementById('edit_ind_display_name').value = indicatorName;
        document.getElementById('edit_ind_type').value = indicator.type || 'Unknown';
        document.getElementById('edit_ind_visible').checked = indicator.visible !== false;

        // Source fields
        const source = indicator.source || {};
        document.getElementById('edit_ind_market').value = source.market || '';
        document.getElementById('edit_ind_timeframe').value = source.timeframe || '';
        document.getElementById('edit_ind_exchange').value = source.exchange || '';

        // Color
        document.getElementById('edit_ind_color').value = indicator.color || '#667eea';

        // Render dynamic properties
        const propsContainer = document.getElementById('edit_ind_properties');
        propsContainer.innerHTML = '';

        // Properties to exclude from dynamic rendering
        const excludeProps = ['type', 'value', 'color', 'visible', 'source', 'name',
                              'color_1', 'color_2', 'color_3', 'bullish_color', 'bearish_color'];

        for (const [key, value] of Object.entries(indicator)) {
            if (!excludeProps.includes(key)) {
                const div = document.createElement('div');
                div.innerHTML = `
                    <label style="font-size: 12px;">${key}</label>
                    <input type="text" name="${key}" value="${value}" style="width: 100%; margin-top: 3px;">
                `;
                propsContainer.appendChild(div);
            }
        }

        // Show and position the form
        form.style.display = 'block';
        form.style.left = '50%';
        form.style.top = '50%';
        form.style.transform = 'translate(-50%, -50%)';
        form.style.position = 'fixed';
        form.style.zIndex = '1000';
    }

    /**
     * Closes the edit dialog
     */
    closeEditDialog() {
        const form = document.getElementById('edit_indicator_form');
        if (form) {
            form.style.display = 'none';
        }
    }

    /**
     * Saves changes from the edit dialog
     */
    saveEditDialog() {
        const indicatorName = document.getElementById('edit_indicator_name').value;

        const formObj = {
            name: indicatorName,
            visible: document.getElementById('edit_ind_visible').checked,
            color: document.getElementById('edit_ind_color').value,
            source: {
                market: document.getElementById('edit_ind_market').value,
                timeframe: document.getElementById('edit_ind_timeframe').value,
                exchange: document.getElementById('edit_ind_exchange').value
            },
            properties: {}
        };

        // Gather dynamic properties
        const propsContainer = document.getElementById('edit_ind_properties');
        propsContainer.querySelectorAll('input').forEach(input => {
            const value = parseFloat(input.value);
            formObj.properties[input.name] = isNaN(value) ? input.value : value;
        });

        this.comms.updateIndicator(formObj).then(response => {
            if (response.success) {
                this.closeEditDialog();
                // Refresh page to reload indicators with new settings
                window.location.reload();
            } else {
                alert('Failed to update indicator: ' + (response.message || 'Unknown error'));
            }
        }).catch(error => {
            console.error('Error updating indicator:', error);
            alert('An error occurred while updating the indicator.');
        });
    }

    /**
     * Deletes an indicator card with confirmation
     */
    deleteIndicatorCard(indicatorName) {
        if (!confirm(`Delete indicator "${indicatorName}"?`)) return;

        this.comms.deleteIndicator(indicatorName).then(response => {
            if (response.success) {
                // Remove from chart
                if (this.i_objs[indicatorName]) {
                    // Determine which chart the indicator is on
                    let chart;
                    const indicator = this.i_objs[indicatorName];
                    const indicatorType = indicator.constructor.name;

                    if (indicatorName.includes('RSI') || indicatorType === 'RSI') {
                        chart = window.UI.charts.chart2;
                    } else if (indicatorName.includes('MACD') || indicatorType === 'MACD') {
                        chart = window.UI.charts.chart3;
                    } else if (indicatorName.includes('%B') || indicatorType === 'BollingerPercentB') {
                        chart = window.UI.charts.chart4;
                    } else if (indicatorType === 'CandlestickPattern') {
                        chart = window.UI.charts.chart5;
                    } else {
                        chart = window.UI.charts.chart_1;
                    }

                    indicator.removeFromChart(chart);
                    delete this.i_objs[indicatorName];
                    iOutput.clear_legend(indicatorName);
                }

                // Remove from indicators object
                delete this.indicators[indicatorName];

                // Re-render the cards
                this.renderIndicators();
            } else {
                alert('Failed to delete indicator: ' + (response.message || 'Unknown error'));
            }
        }).catch(error => {
            console.error('Error deleting indicator:', error);
            alert('An error occurred while deleting the indicator.');
        });
    }

    /**
     * Toggles visibility of an indicator on the chart
     */
    toggleVisibility(indicatorName) {
        const indicator = this.indicators[indicatorName];
        if (!indicator) return;

        // Toggle the visibility
        indicator.visible = !indicator.visible;

        // Save to server and reload to apply chart changes
        this.comms.updateIndicator({
            name: indicatorName,
            visible: indicator.visible
        }).then(response => {
            if (response.success) {
                // Refresh page to properly update chart display
                window.location.reload();
            } else {
                // Revert the toggle if save failed
                indicator.visible = !indicator.visible;
                alert('Failed to update visibility');
            }
        }).catch(error => {
            console.error('Error updating visibility:', error);
            indicator.visible = !indicator.visible;
        });
    }

    /**
     * Makes a dialog draggable by its header
     */
    _makeDialogDraggable(dialog, handle) {
        if (!dialog || !handle) return;

        let offsetX = 0, offsetY = 0, isDragging = false;

        handle.style.cursor = 'move';

        handle.onmousedown = (e) => {
            if (e.target.tagName === 'BUTTON') return; // Don't drag when clicking buttons
            isDragging = true;
            offsetX = e.clientX - dialog.offsetLeft;
            offsetY = e.clientY - dialog.offsetTop;
            document.onmousemove = onMouseMove;
            document.onmouseup = onMouseUp;
        };

        function onMouseMove(e) {
            if (!isDragging) return;
            dialog.style.left = (e.clientX - offsetX) + 'px';
            dialog.style.top = (e.clientY - offsetY) + 'px';
            dialog.style.transform = 'none';
        }

        function onMouseUp() {
            isDragging = false;
            document.onmousemove = null;
            document.onmouseup = null;
        }
    }

}
