/**
 * SigUIManager - Handles DOM updates and signal card rendering
 */
class SigUIManager {
    constructor() {
        this.targetEl = null;
        this.formElement = null;
        this.onDeleteSignal = null;
        this.externalIndicatorsListEl = null;
        this.externalIndicatorsSectionEl = null;
    }

    /**
     * Initializes the UI elements with provided IDs.
     * @param {string} targetId - The ID of the HTML element where signals will be displayed.
     * @param {string} formElId - The ID of the HTML element for the signal creation form.
     */
    initUI(targetId, formElId) {
        this.targetEl = document.getElementById(targetId);
        if (!this.targetEl) {
            console.warn(`Element for displaying signals "${targetId}" not found.`);
        }

        this.formElement = document.getElementById(formElId);
        if (!this.formElement) {
            console.warn(`Signals form element "${formElId}" not found.`);
        }

        // External indicators elements (in indicators panel)
        this.externalIndicatorsListEl = document.getElementById('external_indicators_list');
        this.externalIndicatorsSectionEl = document.getElementById('external_indicators_section');
    }

    /**
     * Displays the form for creating or editing a signal.
     * @param {string} action - The action to perform ('new' or 'edit').
     * @param {object|null} signalData - The data of the signal to edit (only applicable for 'edit' action).
     */
    displayForm(action, signalData = null) {
        if (!this.formElement) {
            console.error("Form element not initialized.");
            return;
        }

        const headerTitle = this.formElement.querySelector("h1");
        const submitCreateBtn = this.formElement.querySelector("#submit-create-signal");
        const submitEditBtn = this.formElement.querySelector("#submit-edit-signal");
        const nameBox = this.formElement.querySelector('#signal_name');
        const publicCheckbox = this.formElement.querySelector('#signal_public_checkbox');
        const tblKeyInput = this.formElement.querySelector('#signal_tbl_key');

        // Reset form to panel 1
        const panel1 = this.formElement.querySelector('#panel_1');
        const panel2 = this.formElement.querySelector('#panel_2');
        const panel3 = this.formElement.querySelector('#panel_3');
        if (panel1) panel1.style.display = 'grid';
        if (panel2) panel2.style.display = 'none';
        if (panel3) panel3.style.display = 'none';

        if (action === 'new') {
            if (headerTitle) headerTitle.textContent = "Add New Signal";
            if (submitCreateBtn) submitCreateBtn.style.display = "inline-block";
            if (submitEditBtn) submitEditBtn.style.display = "none";
            if (nameBox) nameBox.value = '';
            if (publicCheckbox) publicCheckbox.checked = false;
            if (tblKeyInput) tblKeyInput.value = '';

            // Add external sources and indicators to the signal source dropdown
            if (UI.signals) {
                UI.signals.addExternalSourcesToDropdown('sig_source');
                UI.signals.addExternalIndicatorsToDropdown('sig_source');
            }

            // Initialize property dropdowns based on first selected source
            // This also populates the comparison source dropdown (sig2_source) via updateSignalFormForIndicatorType
            const sigSource = this.formElement.querySelector('#sig_source');
            if (sigSource && sigSource.value && UI.signals) {
                UI.signals.fill_prop('sig_prop', sigSource.value);
            }
        } else if (action === 'edit' && signalData) {
            if (headerTitle) headerTitle.textContent = "Edit Signal";
            if (submitCreateBtn) submitCreateBtn.style.display = "none";
            if (submitEditBtn) submitEditBtn.style.display = "inline-block";
            if (nameBox) nameBox.value = signalData.name || '';
            if (publicCheckbox) publicCheckbox.checked = !!signalData.public;
            if (tblKeyInput) tblKeyInput.value = signalData.tbl_key || '';

            // Pre-fill source fields
            const sigSource = this.formElement.querySelector('#sig_source');
            const sigProp = this.formElement.querySelector('#sig_prop');
            const sig2Source = this.formElement.querySelector('#sig2_source');
            const sig2Prop = this.formElement.querySelector('#sig2_prop');
            const sigType = this.formElement.querySelector('#select_s_type');
            const valueInput = this.formElement.querySelector('#value');

            if (sigSource && signalData.source1) sigSource.value = signalData.source1;
            if (sigProp && signalData.prop1) {
                // Fill prop options first, then set value
                if (UI.signals && signalData.source1) {
                    UI.signals.fill_prop('sig_prop', signalData.source1);
                    // Update form UI for indicator type (pattern vs standard)
                    // This is called after fill_prop to ensure UI is properly configured
                    setTimeout(() => {
                        UI.signals.updateSignalFormForIndicatorType(signalData.source1);
                    }, 25);
                }
                setTimeout(() => { if (sigProp) sigProp.value = signalData.prop1; }, 50);
            }

            // Handle source2 - could be 'value' for fixed value type
            if (signalData.source2 === 'value') {
                if (sigType) sigType.value = 'Value';
                if (valueInput) valueInput.value = signalData.prop2 || '';
            } else {
                if (sigType) sigType.value = 'Comparison';
                // Set sig2_source and sig2_prop after dropdown is populated
                setTimeout(() => {
                    if (sig2Source && signalData.source2) {
                        sig2Source.value = signalData.source2;
                        if (UI.signals && signalData.source2) {
                            UI.signals.fill_prop('sig2_prop', signalData.source2);
                        }
                    }
                    setTimeout(() => { if (sig2Prop) sig2Prop.value = signalData.prop2; }, 50);
                }, 50);
            }

            // Check if this is a pattern operator
            const patternOperators = ['is_bullish', 'is_bearish', 'is_detected'];
            const isPatternOperator = patternOperators.includes(signalData.operator);

            if (isPatternOperator) {
                // Set pattern operator
                const patternRadios = this.formElement.querySelectorAll('input[name="PatternOperator"]');
                patternRadios.forEach(radio => {
                    radio.checked = radio.value === signalData.operator;
                });
            } else {
                // Set standard operator
                const operatorRadios = this.formElement.querySelectorAll('input[name="Operator"]');
                operatorRadios.forEach(radio => {
                    radio.checked = radio.value === signalData.operator;
                });

                // Set range if applicable
                if (signalData.operator === '+/-' && signalData.range) {
                    const rangeVal = this.formElement.querySelector('#rangeVal');
                    const rangeSlider = this.formElement.querySelector('#rangeSlider');
                    if (rangeVal) rangeVal.value = signalData.range;
                    if (rangeSlider) rangeSlider.value = signalData.range;
                }
            }
        }

        this.formElement.style.display = "grid";
    }

    /**
     * Hides the signal form.
     */
    hideForm() {
        if (this.formElement) {
            this.formElement.style.display = 'none';
        }
    }

    /**
     * Updates the HTML representation of the signals as cards.
     * @param {Object[]} signals - The list of signals to display.
     */
    updateSignalsHtml(signals) {
        if (!this.targetEl) {
            console.error("Target element for displaying signals is not set.");
            return;
        }

        // Clear existing content
        while (this.targetEl.firstChild) {
            this.targetEl.removeChild(this.targetEl.firstChild);
        }

        // Create and append new elements for all signals
        for (const signal of signals) {
            try {
                const signalCard = this._createSignalCard(signal);
                this.targetEl.appendChild(signalCard);
            } catch (error) {
                console.error(`Error processing signal:`, error, signal);
            }
        }
    }

    /**
     * Creates a signal card HTML element.
     * @param {Object} signal - The signal data.
     * @returns {HTMLElement} - The card element.
     */
    _createSignalCard(signal) {
        const signalItem = document.createElement('div');
        signalItem.className = 'signal-item';
        signalItem.setAttribute('data-signal-id', signal.tbl_key || signal.name);

        // Add state-based styling
        const isTrue = signal.state === true || signal.state === 'true' || signal.state === 1;
        if (isTrue) {
            signalItem.classList.add('signal-true');
        } else {
            signalItem.classList.add('signal-false');
        }

        // Delete button
        const deleteButton = document.createElement('button');
        deleteButton.className = 'delete-button';
        deleteButton.innerHTML = '&#10008;';
        deleteButton.addEventListener('click', (e) => {
            e.stopPropagation();
            if (this.onDeleteSignal) {
                this.onDeleteSignal(signal.tbl_key || signal.name);
            }
        });
        signalItem.appendChild(deleteButton);

        // Signal icon container
        const signalIcon = document.createElement('div');
        signalIcon.className = 'signal-icon';
        signalIcon.addEventListener('click', () => {
            // Open edit form when clicking on signal
            this.displayForm('edit', signal);
        });

        // Signal name
        const signalName = document.createElement('div');
        signalName.className = 'signal-name';
        signalName.textContent = signal.name || 'Unnamed Signal';
        signalIcon.appendChild(signalName);

        // State indicator
        const stateIndicator = document.createElement('div');
        stateIndicator.className = 'signal-state';
        stateIndicator.id = `${signal.name}_state`;
        stateIndicator.textContent = isTrue ? 'TRUE' : 'FALSE';
        signalIcon.appendChild(stateIndicator);

        signalItem.appendChild(signalIcon);

        // Hover details panel
        const signalHover = document.createElement('div');
        signalHover.className = 'signal-hover';

        // Check if this is a pattern operator
        const patternOperators = ['is_bullish', 'is_bearish', 'is_detected'];
        const isPatternOperator = patternOperators.includes(signal.operator);

        // Get user-friendly operator label
        const getOperatorLabel = (op) => {
            const labels = {
                'is_bullish': 'Is Bullish',
                'is_bearish': 'Is Bearish',
                'is_detected': 'Is Detected'
            };
            return labels[op] || op;
        };

        // Build hover content
        let hoverHtml = `<strong>${signal.name || 'Unnamed Signal'}</strong>`;
        hoverHtml += `<div class="signal-details">`;
        hoverHtml += `<span>Source: ${signal.source1} (${signal.prop1})</span>`;
        hoverHtml += `<span id="${signal.name}_value1">Value: ${signal.value1 ?? signal.last_value1 ?? 'N/A'}</span>`;

        if (isPatternOperator) {
            // Pattern operators - show friendly label
            hoverHtml += `<span>Condition: ${getOperatorLabel(signal.operator)}</span>`;
        } else {
            hoverHtml += `<span>Operator: ${signal.operator}${signal.operator === '+/-' ? ` (range: ${signal.range})` : ''}</span>`;

            if (signal.source2 === 'value') {
                hoverHtml += `<span>Compare to: ${signal.prop2}</span>`;
            } else {
                hoverHtml += `<span>Source 2: ${signal.source2} (${signal.prop2})</span>`;
                hoverHtml += `<span id="${signal.name}_value2">Value: ${signal.value2 ?? signal.last_value2 ?? 'N/A'}</span>`;
            }
        }

        // State display
        const stateClass = isTrue ? 'state-true' : 'state-false';
        hoverHtml += `<span class="${stateClass}">State: ${isTrue ? 'TRUE' : 'FALSE'}</span>`;

        if (signal.public) {
            hoverHtml += `<span class="signal-public-badge">Public</span>`;
        }

        hoverHtml += `</div>`;

        signalHover.innerHTML = hoverHtml;
        signalItem.appendChild(signalHover);

        return signalItem;
    }

    /**
     * Updates a single signal's state display.
     * @param {string} signalName - The name of the signal.
     * @param {boolean} state - The new state.
     */
    updateSignalState(signalName, state) {
        const stateEl = document.getElementById(`${signalName}_state`);
        if (stateEl) {
            const isTrue = state === true || state === 'true' || state === 1;
            stateEl.textContent = isTrue ? 'TRUE' : 'FALSE';

            // Update parent card styling
            const card = stateEl.closest('.signal-item');
            if (card) {
                card.classList.remove('signal-true', 'signal-false');
                card.classList.add(isTrue ? 'signal-true' : 'signal-false');
            }
        }
    }

    /**
     * Sets the callback function for deleting a signal.
     * @param {Function} callback - The callback function.
     */
    registerDeleteSignalCallback(callback) {
        this.onDeleteSignal = callback;
    }
}


/**
 * SigDataManager - Manages in-memory signal data store
 */
class SigDataManager {
    constructor() {
        this.signals = [];
    }

    /**
     * Fetches the saved signals from the server.
     * @param {Object} comms - The communications instance.
     * @param {Object} data - An object containing user data.
     */
    fetchSavedSignals(comms, data) {
        if (comms) {
            try {
                const requestData = {
                    request: 'signals',
                    user_name: data?.user_name
                };
                comms.sendToApp('request', requestData);
            } catch (error) {
                console.error("Error fetching saved signals:", error.message);
            }
        } else {
            throw new Error('Communications instance not available.');
        }
    }

    /**
     * Adds a new signal to the local store.
     * @param {Object} data - The signal data.
     */
    addNewSignal(data) {
        const signalData = data.signal || data;
        console.log("Adding new signal:", signalData);
        if (!signalData.name) {
            console.error("Signal data missing 'name' field:", signalData);
            return;
        }

        // Check for duplicates
        const exists = this.signals.find(s => s.tbl_key === signalData.tbl_key || s.name === signalData.name);
        if (!exists) {
            this.signals.push(signalData);
        }
    }

    /**
     * Retrieves a signal by its tbl_key.
     * @param {string} tbl_key - The tbl_key of the signal.
     * @returns {Object|null} - The signal object or null.
     */
    getSignalById(tbl_key) {
        return this.signals.find(signal => signal.tbl_key === tbl_key) || null;
    }

    /**
     * Retrieves a signal by its name.
     * @param {string} name - The name of the signal.
     * @returns {Object|null} - The signal object or null.
     */
    getSignalByName(name) {
        return this.signals.find(signal => signal.name === name) || null;
    }

    /**
     * Updates signal data.
     * @param {Object} data - The updated signal data.
     */
    updateSignalData(data) {
        const signalData = data.signal || data;
        const signalKey = signalData.tbl_key || signalData.name;
        if (!signalKey) return;

        const index = this.signals.findIndex(
            signal => signal.tbl_key === signalKey || signal.name === signalKey
        );

        if (index !== -1) {
            this.signals[index] = { ...this.signals[index], ...signalData };
        } else {
            this.signals.push(signalData);
        }
    }

    /**
     * Removes a signal from the store.
     * @param {string} identifier - The tbl_key or name of the signal.
     */
    removeSignal(identifier) {
        console.log(`Removing signal: ${identifier}`);
        this.signals = this.signals.filter(
            sig => sig.tbl_key !== identifier && sig.name !== identifier
        );
    }

    /**
     * Updates signal states from server updates.
     * @param {Object} stateUpdates - Map of signal names to states.
     */
    applyStateUpdates(stateUpdates) {
        for (const name in stateUpdates) {
            const signal = this.getSignalByName(name);
            if (signal) {
                signal.state = stateUpdates[name];
            }
        }
    }

    /**
     * Returns all signals.
     * @returns {Object[]} - The list of signals.
     */
    getAllSignals() {
        return this.signals;
    }

    /**
     * Sets all signals (used when loading from server).
     * @param {Object[]} signals - The list of signals.
     */
    setSignals(signals) {
        this.signals = Array.isArray(signals) ? signals : [];
    }
}


/**
 * Signals - Main coordinator class that manages SigUIManager, SigDataManager, and SocketIO communication
 */
class Signals {
    constructor(ui) {
        this.ui = ui;
        this.comms = ui?.data?.comms;
        this.indicatorData = ui?.data?.indicators;
        this.data = ui?.data;

        this.dataManager = new SigDataManager();
        this.uiManager = new SigUIManager();

        // Set up delete callback
        this.uiManager.registerDeleteSignalCallback(this.deleteSignal.bind(this));

        // Bind methods
        this.submitSignal = this.submitSignal.bind(this);

        this._initialized = false;
    }

    /**
     * Initializes the Signals instance.
     * @param {string} targetId - The ID of the signals container element.
     * @param {string} formElId - The ID of the signal form element.
     */
    initialize(targetId, formElId) {
        try {
            this.uiManager.initUI(targetId, formElId);

            if (!this.comms) {
                console.error("Communications instance not available.");
                return;
            }

            // Register handlers with Comms
            this.comms.on('signals', this.handleSignalsResponse.bind(this));
            this.comms.on('signal_created', this.handleSignalCreated.bind(this));
            this.comms.on('signal_updated', this.handleSignalUpdated.bind(this));
            this.comms.on('signal_deleted', this.handleSignalDeleted.bind(this));
            this.comms.on('signal_error', this.handleSignalError.bind(this));
            this.comms.on('updates', this.handleUpdates.bind(this));

            // Fetch saved signals
            this.dataManager.fetchSavedSignals(this.comms, this.data);

            // Fetch external sources (signal types - real-time only)
            this.fetchExternalSources();

            // Fetch external indicators (historical data for backtesting)
            this.externalIndicators = [];
            this.fetchExternalIndicators();

            this._initialized = true;
        } catch (error) {
            console.error("Error initializing Signals:", error);
        }
    }

    /**
     * Handle initial signals list response from server.
     * @param {Array} data - List of signal objects.
     */
    handleSignalsResponse(data) {
        console.log("Received signals list:", data);
        if (Array.isArray(data)) {
            this.dataManager.setSignals(data);
            this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
        }
    }

    /**
     * Handle new signal created event.
     * @param {Object} data - Server response with signal data.
     */
    handleSignalCreated(data) {
        console.log("Signal created:", data);
        if (data.success) {
            this.dataManager.addNewSignal(data);
            this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
        } else {
            alert(`Failed to create signal: ${data.message}`);
        }
    }

    /**
     * Handle signal updated event.
     * @param {Object} data - Server response with updated signal data.
     */
    handleSignalUpdated(data) {
        console.log("Signal updated:", data);
        if (data.success) {
            this.dataManager.updateSignalData(data);
            this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
        } else {
            alert(`Failed to update signal: ${data.message}`);
        }
    }

    /**
     * Handle signal deleted event.
     * @param {Object} data - Server response with deleted signal info.
     */
    handleSignalDeleted(data) {
        console.log("Signal deleted:", data);
        const identifier = data.tbl_key || data.name;
        if (identifier) {
            this.dataManager.removeSignal(identifier);
            this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
        }
    }

    /**
     * Handle signal error.
     * @param {Object} data - Error data.
     */
    handleSignalError(data) {
        console.error("Signal error:", data.message);
        alert(`Signal error: ${data.message}`);
    }

    /**
     * Handle updates (including signal state changes).
     * @param {Object} data - Update data from server.
     */
    handleUpdates(data) {
        const { s_updates } = data;
        if (s_updates) {
            this.dataManager.applyStateUpdates(s_updates);
            // Update UI for state changes
            for (const name in s_updates) {
                this.uiManager.updateSignalState(name, s_updates[name]);
            }
        }
    }

    // ================ Form Methods ================

    /**
     * Opens the signal creation form.
     */
    open_signal_Form() {
        this.uiManager.displayForm('new');
    }

    /**
     * Closes the signal form.
     */
    close_signal_Form() {
        this.uiManager.hideForm();
    }

    /**
     * Requests signals from server.
     */
    request_signals() {
        if (this.comms) {
            this.comms.sendToApp('request', { request: 'signals', user_name: this.data?.user_name });
        }
    }

    /**
     * Deletes a signal by tbl_key or name.
     * @param {string} identifier - The tbl_key or name of the signal.
     */
    deleteSignal(identifier) {
        if (!this.comms) {
            console.error("Comms instance not available.");
            return;
        }

        const signal = this.dataManager.getSignalById(identifier) ||
                       this.dataManager.getSignalByName(identifier);

        const deleteData = signal
            ? { tbl_key: signal.tbl_key, name: signal.name }
            : { name: identifier };

        this.comms.sendToApp('delete_signal', deleteData);
    }

    /**
     * Submits a new or edited signal.
     * @param {string} action - 'new' or 'edit'.
     */
    submitSignal(action) {
        const formElement = this.uiManager.formElement;
        if (!formElement) {
            console.error("Form element not available.");
            return;
        }

        const name = formElement.querySelector('#signal_name')?.value?.trim();
        const source1 = formElement.querySelector('#sig_source')?.value;
        const prop1 = formElement.querySelector('#sig_prop')?.value;
        const source2 = formElement.querySelector('#sig2_source')?.value;
        const prop2 = formElement.querySelector('#sig2_prop')?.value;
        const range = formElement.querySelector('#rangeVal')?.value;
        const sigType = formElement.querySelector('#select_s_type')?.value;
        const value = formElement.querySelector('#value')?.value;
        const publicCheckbox = formElement.querySelector('#signal_public_checkbox');
        const tblKey = formElement.querySelector('#signal_tbl_key')?.value;

        // Check if this is a candlestick pattern indicator
        const indicatorConfig = this.indicatorData ? this.indicatorData[source1] : null;
        const isPattern = indicatorConfig && this.isCandlestickPattern(indicatorConfig.type);

        // Get the appropriate operator
        let operator;
        if (isPattern) {
            operator = formElement.querySelector('input[name="PatternOperator"]:checked')?.value;
        } else {
            operator = formElement.querySelector('input[name="Operator"]:checked')?.value;
        }

        if (!name) {
            alert("Please provide a name for the signal.");
            return;
        }
        if (!prop1) {
            alert("Please select a property for the signal source.");
            return;
        }

        // Build signal data
        let actualSource2 = source2;
        let actualProp2 = prop2;

        if (isPattern) {
            // For pattern signals, we compare against fixed value 0
            actualSource2 = 'value';
            actualProp2 = '0';
        } else if (sigType !== 'Comparison') {
            actualSource2 = 'value';
            actualProp2 = value;
        }

        const signalData = {
            name,
            source1,
            prop1,
            operator,
            source2: actualSource2,
            prop2: actualProp2,
            state: false,
            value1: null,
            value2: null,
            public: publicCheckbox?.checked ? 1 : 0,
            user_name: this.data?.user_name
        };

        if (operator === '+/-') {
            signalData.range = parseFloat(range) || 0;
        }

        if (action === 'edit' && tblKey) {
            signalData.tbl_key = tblKey;
        }

        const messageType = action === 'new' ? 'new_signal' : 'edit_signal';
        this.comms.sendToApp(messageType, signalData);

        this.close_signal_Form();
    }

    /**
     * Submits a new signal (legacy method name).
     */
    submitNewSignal() {
        this.submitSignal('new');
    }

    // ================ Helper Methods ================

    /**
     * Checks if an indicator type is a candlestick pattern.
     * @param {string} indicatorType - The indicator type.
     * @returns {boolean} - True if it's a candlestick pattern.
     */
    isCandlestickPattern(indicatorType) {
        return indicatorType && indicatorType.startsWith('CDL_');
    }

    /**
     * Populates the comparison source dropdown (sig2_source) with non-pattern indicators.
     */
    populateComparisonSourceDropdown() {
        const sig2Source = document.getElementById('sig2_source');
        if (!sig2Source) return;

        // Clear existing options
        while (sig2Source.options.length > 0) {
            sig2Source.remove(0);
        }

        // Add only non-pattern indicators
        if (this.indicatorData) {
            for (const [name, config] of Object.entries(this.indicatorData)) {
                if (!this.isCandlestickPattern(config.type)) {
                    const opt = document.createElement('option');
                    opt.value = name;
                    opt.textContent = name;
                    sig2Source.appendChild(opt);
                }
            }
        }

        // Add external sources as well
        if (this.externalSources && this.externalSources.length > 0) {
            const optgroup = document.createElement('optgroup');
            optgroup.label = 'External Sources';

            for (const source of this.externalSources) {
                const opt = document.createElement('option');
                opt.value = source.name;
                opt.textContent = `${source.name} (${source.last_value ?? 'N/A'})`;
                opt.setAttribute('data-type', 'external');
                optgroup.appendChild(opt);
            }

            if (optgroup.children.length > 0) {
                sig2Source.appendChild(optgroup);
            }
        }

        // Add external indicators (historical API) as well
        if (this.externalIndicators && this.externalIndicators.length > 0) {
            const optgroup = document.createElement('optgroup');
            optgroup.label = 'External Indicators';

            for (const indicator of this.externalIndicators) {
                const opt = document.createElement('option');
                opt.value = indicator.name;
                opt.textContent = indicator.name;
                opt.setAttribute('data-type', 'external_indicator');
                optgroup.appendChild(opt);
            }

            if (optgroup.children.length > 0) {
                sig2Source.appendChild(optgroup);
            }
        }

        // Fill property dropdown for the first option
        if (sig2Source.options.length > 0) {
            this.fill_prop('sig2_prop', sig2Source.value);
        }
    }

    /**
     * Updates the signal form UI based on whether source indicator is a pattern.
     * @param {string} indicatorName - The name of the selected indicator.
     */
    updateSignalFormForIndicatorType(indicatorName) {
        const indicatorConfig = this.indicatorData ? this.indicatorData[indicatorName] : null;
        const isPattern = indicatorConfig && this.isCandlestickPattern(indicatorConfig.type);

        const standardOperators = document.getElementById('sig_operator');
        const patternOperators = document.getElementById('pattern_operator');
        const signalTypeRow = document.getElementById('signal_type_row');
        const subpanel1 = document.getElementById('subpanel_1');
        const subpanel2 = document.getElementById('subpanel_2');

        if (isPattern) {
            // Show pattern-specific operators, hide standard ones
            if (standardOperators) standardOperators.style.display = 'none';
            if (patternOperators) patternOperators.style.display = 'block';
            // Hide comparison option for patterns (patterns compare against fixed values)
            if (signalTypeRow) signalTypeRow.style.display = 'none';
            if (subpanel1) subpanel1.style.display = 'none';
            if (subpanel2) subpanel2.style.display = 'none';
        } else {
            // Show standard operators, hide pattern-specific ones
            if (standardOperators) standardOperators.style.display = 'block';
            if (patternOperators) patternOperators.style.display = 'none';
            if (signalTypeRow) signalTypeRow.style.display = 'block';
            // Populate comparison dropdown with non-pattern indicators
            this.populateComparisonSourceDropdown();
            // Restore subpanels based on signal type selection
            const sigType = document.getElementById('select_s_type')?.value;
            if (sigType === 'Value') {
                if (subpanel2) subpanel2.style.display = 'block';
                if (subpanel1) subpanel1.style.display = 'none';
            } else {
                if (subpanel1) subpanel1.style.display = 'block';
                if (subpanel2) subpanel2.style.display = 'none';
            }
        }
    }

    /**
     * Fills property dropdown based on indicator type.
     * @param {string} target_id - The ID of the select element.
     * @param {string} indctr - The indicator name.
     */
    fill_prop(target_id, indctr) {
        const target = document.getElementById(target_id);
        const indicatorConfig = this.indicatorData ? this.indicatorData[indctr] : null;

        if (!target) return;

        // Clear existing options
        while (target.options.length > 0) {
            target.remove(0);
        }

        // Check if this is an external source
        const externalSource = this.externalSources?.find(s => s.name === indctr);
        if (externalSource) {
            // External sources only have a 'value' property
            const opt = document.createElement("option");
            opt.value = 'value';
            opt.textContent = 'value';
            target.appendChild(opt);

            // Update form UI for external sources (show standard operators)
            if (target_id === 'sig_prop') {
                this.updateSignalFormForIndicatorType(indctr);
            }
            return;
        }

        // Check if this is an external indicator (historical API)
        const externalIndicator = this.externalIndicators?.find(i => i.name === indctr);
        if (externalIndicator) {
            // External indicators only have a 'value' property
            const opt = document.createElement("option");
            opt.value = 'value';
            opt.textContent = 'value';
            target.appendChild(opt);

            // Update form UI for external indicators (show standard operators)
            if (target_id === 'sig_prop') {
                this.updateSignalFormForIndicatorType(indctr);
            }
            return;
        }

        if (!indicatorConfig) {
            console.warn(`Indicator "${indctr}" not found in indicator data`);
            return;
        }

        // Get the indicator outputs based on type
        const outputMap = {
            'SMA': ['value'],
            'EMA': ['value'],
            'LREG': ['value'],
            'RSI': ['value'],
            'ATR': ['value'],
            'Volume': ['value'],
            'MACD': ['macd', 'signal', 'hist'],
            'BOLBands': ['upper', 'middle', 'lower']
        };

        const indicatorType = indicatorConfig.type;
        const outputs = outputMap[indicatorType] || ['value'];

        for (const output of outputs) {
            const opt = document.createElement("option");
            opt.value = output;
            opt.textContent = output;
            target.appendChild(opt);
        }

        // Update form UI if this is the primary source selector
        if (target_id === 'sig_prop') {
            this.updateSignalFormForIndicatorType(indctr);
        }
    }

    /**
     * Switches between form panels.
     * @param {string} p1 - Panel to hide.
     * @param {string} p2 - Panel to show.
     */
    switch_panel(p1, p2) {
        const panel1 = document.getElementById(p1);
        const panel2 = document.getElementById(p2);
        if (panel1) panel1.style.display = 'none';
        if (panel2) panel2.style.display = 'grid';
    }

    /**
     * Conditionally hides an element.
     * @param {*} firstValue - First value to compare.
     * @param {*} scndValue - Second value to compare.
     * @param {string} id - Element ID to show/hide.
     */
    hideIfTrue(firstValue, scndValue, id) {
        const el = document.getElementById(id);
        if (el) {
            el.style.display = firstValue === scndValue ? 'none' : 'block';
        }
    }

    /**
     * Gets the current value of an indicator property from the DOM.
     * @param {string} source - The indicator source name.
     * @param {string} prop - The property name.
     * @returns {string} - The value.
     */
    _getIndicatorValue(source, prop) {
        let element = document.getElementById(source + '_' + prop)
            || document.getElementById(source + '_value');

        if (!element) {
            console.warn(`Could not find indicator value element for ${source}_${prop}`);
            return '0';
        }

        let rawValue = element.value || element.textContent || '0';

        // Parse multi-value format if needed
        if (rawValue.includes(':') && rawValue.includes(',')) {
            const parts = rawValue.split(',').map(p => p.trim());
            for (const part of parts) {
                const [key, val] = part.split(':').map(s => s.trim());
                if (key === prop) {
                    return val;
                }
            }
        }

        return rawValue;
    }

    /**
     * Gets a human-readable label for a pattern operator.
     * @param {string} operator - The pattern operator.
     * @returns {string} - Human-readable label.
     */
    getPatternOperatorLabel(operator) {
        const labels = {
            'is_bullish': 'Is Bullish',
            'is_bearish': 'Is Bearish',
            'is_detected': 'Is Detected'
        };
        return labels[operator] || operator;
    }

    /**
     * Handles panel 1 "Next" button click.
     * @param {number} n - Panel number.
     */
    ns_next(n) {
        if (n === 1) {
            const sigName = document.getElementById('signal_name')?.value;
            const sigSource = document.getElementById('sig_source')?.value;
            const sigProp = document.getElementById('sig_prop')?.value;

            if (!sigName) {
                alert('Please give the signal a name.');
                return;
            }
            if (!sigProp) {
                alert('Please select a property.');
                return;
            }

            const display = document.getElementById('sig_display');
            if (display) {
                display.innerHTML = `${sigName}: {${sigSource}:${sigProp}}`;
            }

            // Get current indicator value
            const indctrVal = this._getIndicatorValue(sigSource, sigProp);
            const valueInput = document.getElementById('value');
            if (valueInput) {
                valueInput.value = indctrVal || '0';
            }

            // Update form for indicator type (pattern vs standard)
            this.updateSignalFormForIndicatorType(sigSource);

            this.switch_panel('panel_1', 'panel_2');
        }

        if (n === 2) {
            const sigName = document.getElementById('signal_name')?.value;
            const sigSource = document.getElementById('sig_source')?.value;
            const sigProp = document.getElementById('sig_prop')?.value;
            const sig2Source = document.getElementById('sig2_source')?.value;
            const sig2Prop = document.getElementById('sig2_prop')?.value;
            const range = document.getElementById('rangeVal')?.value;
            const sigType = document.getElementById('select_s_type')?.value;
            const value = document.getElementById('value')?.value;

            // Check if this is a candlestick pattern indicator
            const indicatorConfig = this.indicatorData ? this.indicatorData[sigSource] : null;
            const isPattern = indicatorConfig && this.isCandlestickPattern(indicatorConfig.type);

            let operator, operatorStr, sig2, sig2_realtime, evalStr;
            const sig1_realtime = this._getIndicatorValue(sigSource, sigProp);

            if (isPattern) {
                operator = document.querySelector('input[name="PatternOperator"]:checked')?.value;
                operatorStr = this.getPatternOperatorLabel(operator);
                sig2 = '';  // Patterns don't compare to another value
                sig2_realtime = '';

                // Evaluate pattern operators
                const patternValue = parseFloat(sig1_realtime);
                if (operator === 'is_bullish') {
                    evalStr = patternValue > 0;
                } else if (operator === 'is_bearish') {
                    evalStr = patternValue < 0;
                } else if (operator === 'is_detected') {
                    evalStr = patternValue !== 0;
                }
            } else {
                operator = document.querySelector('input[name="Operator"]:checked')?.value;
                operatorStr = operator === '+/-' ? `${operator} ${range}` : operator;
                sig2 = sigType === 'Comparison' ? `${sig2Source} : ${sig2Prop}` : value;
                sig2_realtime = sigType === 'Comparison'
                    ? this._getIndicatorValue(sig2Source, sig2Prop)
                    : value;

                // Evaluate standard operators
                if (operator === '==') evalStr = parseFloat(sig1_realtime) === parseFloat(sig2_realtime);
                if (operator === '>') evalStr = parseFloat(sig1_realtime) > parseFloat(sig2_realtime);
                if (operator === '<') evalStr = parseFloat(sig1_realtime) < parseFloat(sig2_realtime);
                if (operator === '+/-') evalStr = Math.abs(parseFloat(sig1_realtime) - parseFloat(sig2_realtime)) <= parseFloat(range);
            }

            const sig1 = `${sigSource} : ${sigProp}`;
            const sigDisplayStr = isPattern
                ? `(${sigName}) (${sig1}) (${operatorStr})`
                : `(${sigName}) (${sig1}) (${operatorStr}) (${sig2})`;

            const display2 = document.getElementById('sig_display2');
            const realtime = document.getElementById('sig_realtime');
            const evalEl = document.getElementById('sig_eval');

            if (display2) display2.innerHTML = sigDisplayStr;
            if (realtime) {
                if (isPattern) {
                    realtime.innerHTML = `(${sigProp} : ${sig1_realtime}) (${operatorStr})`;
                } else {
                    realtime.innerHTML = `(${sigProp} : ${sig1_realtime}) (${operatorStr}) (${sig2_realtime})`;
                }
            }

            if (evalEl) evalEl.innerHTML = evalStr ? 'true' : 'false';

            this.switch_panel('panel_2', 'panel_3');
        }
    }

    // Legacy methods for backwards compatibility
    i_update(updates) {
        for (const signal of this.dataManager.getAllSignals()) {
            const s1 = signal.source1;
            if (s1 in updates) {
                const p1 = signal.prop1;
                const value1 = updates[s1].data[0][p1];
                signal.value1 = value1.toFixed(2);
            }
            if (signal.source2 !== 'value') {
                const s2 = signal.source2;
                if (s2 in updates) {
                    const p2 = signal.prop2;
                    const value2 = updates[s2].data[0][p2];
                    signal.value2 = value2.toFixed(2);
                }
            }
            const val1El = document.getElementById(signal.name + '_value1');
            const val2El = document.getElementById(signal.name + '_value2');
            if (val1El) val1El.innerHTML = signal.value1;
            if (val2El) val2El.innerHTML = signal.value2;
        }
    }

    update_signal_states(s_updates) {
        for (const name in s_updates) {
            this.uiManager.updateSignalState(name, s_updates[name]);
        }
    }

    set_data(signals) {
        // Legacy method - now handled by handleSignalsResponse
        if (Array.isArray(signals)) {
            for (const sig of signals) {
                const obj = typeof sig === 'string' ? JSON.parse(sig) : sig;
                this.dataManager.addNewSignal(obj);
            }
            this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
        }
    }

    delete_signal(signal_name) {
        // Legacy method - redirect to new method
        this.deleteSignal(signal_name);
    }

    // ================ External Sources (Signal Types) ================

    /**
     * In-memory store for external sources.
     */
    externalSources = [];

    /**
     * Opens the signal type creation form.
     */
    openSignalTypeForm() {
        const form = document.getElementById('new_signal_type_form');
        if (form) {
            // Reset form
            document.getElementById('signal_type_name').value = '';
            document.getElementById('signal_type_url').value = '';
            document.getElementById('signal_type_auth_header').value = 'X-CMC_PRO_API_KEY';
            document.getElementById('signal_type_auth_key').value = '';
            document.getElementById('signal_type_json_path').value = '$.data[0].value';
            document.getElementById('signal_type_refresh').value = '300';
            document.getElementById('signal_type_tbl_key').value = '';
            document.getElementById('signal_type_test_result').style.display = 'none';
            document.getElementById('signal_type_loading').style.display = 'none';

            form.style.display = 'block';
            // Center the form
            form.style.top = '100px';
            form.style.left = '50%';
            form.style.transform = 'translateX(-50%)';
        }
    }

    /**
     * Closes the signal type form.
     */
    closeSignalTypeForm() {
        const form = document.getElementById('new_signal_type_form');
        if (form) {
            form.style.display = 'none';
        }
    }

    /**
     * Tests the signal type API connection.
     */
    async testSignalType() {
        const name = document.getElementById('signal_type_name')?.value?.trim();
        const url = document.getElementById('signal_type_url')?.value?.trim();
        const authHeader = document.getElementById('signal_type_auth_header')?.value?.trim();
        const authKey = document.getElementById('signal_type_auth_key')?.value?.trim();
        const jsonPath = document.getElementById('signal_type_json_path')?.value?.trim();

        if (!url) {
            alert('Please enter an API endpoint URL');
            return;
        }

        const loadingEl = document.getElementById('signal_type_loading');
        const resultEl = document.getElementById('signal_type_test_result');

        loadingEl.style.display = 'block';
        resultEl.style.display = 'none';

        try {
            const response = await fetch('/api/external_sources/test', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    url: url,
                    auth_header: authHeader,
                    auth_key: authKey,
                    json_path: jsonPath
                })
            });

            const data = await response.json();
            loadingEl.style.display = 'none';
            resultEl.style.display = 'block';

            if (data.success) {
                resultEl.className = 'success';
                resultEl.innerHTML = `<strong>Success!</strong> Value: ${data.value}`;
            } else {
                resultEl.className = 'error';
                resultEl.innerHTML = `<strong>Error:</strong> ${data.error}`;
            }
        } catch (error) {
            loadingEl.style.display = 'none';
            resultEl.style.display = 'block';
            resultEl.className = 'error';
            resultEl.innerHTML = `<strong>Error:</strong> ${error.message}`;
        }
    }

    /**
     * Submits the signal type form to create a new external source.
     */
    async submitSignalType() {
        const name = document.getElementById('signal_type_name')?.value?.trim();
        const url = document.getElementById('signal_type_url')?.value?.trim();
        const authHeader = document.getElementById('signal_type_auth_header')?.value?.trim();
        const authKey = document.getElementById('signal_type_auth_key')?.value?.trim();
        const jsonPath = document.getElementById('signal_type_json_path')?.value?.trim();
        const refreshInterval = parseInt(document.getElementById('signal_type_refresh')?.value) || 300;

        if (!name) {
            alert('Please enter a name for this signal type');
            return;
        }
        if (!url) {
            alert('Please enter an API endpoint URL');
            return;
        }

        const loadingEl = document.getElementById('signal_type_loading');
        loadingEl.style.display = 'block';

        try {
            const response = await fetch('/api/external_sources/create', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    name: name,
                    url: url,
                    auth_header: authHeader,
                    auth_key: authKey,
                    json_path: jsonPath,
                    refresh_interval: refreshInterval
                })
            });

            const data = await response.json();
            loadingEl.style.display = 'none';

            if (data.success) {
                this.closeSignalTypeForm();
                this.fetchExternalSources();
                alert(data.message);
            } else {
                alert(`Error: ${data.message}`);
            }
        } catch (error) {
            loadingEl.style.display = 'none';
            alert(`Error: ${error.message}`);
        }
    }

    /**
     * Fetches external sources from the server.
     */
    async fetchExternalSources() {
        try {
            const response = await fetch('/api/external_sources/list');
            const data = await response.json();

            if (data.success) {
                this.externalSources = data.sources || [];
                this.renderExternalSources();

                // Add external sources to indicator data for signal source dropdown
                this.addExternalSourcesToIndicatorData();
            }
        } catch (error) {
            console.error('Error fetching external sources:', error);
        }
    }

    /**
     * Adds external sources to indicator data so they appear in signal source dropdowns.
     */
    addExternalSourcesToIndicatorData() {
        if (!this.indicatorData) {
            this.indicatorData = {};
        }

        for (const source of this.externalSources) {
            this.indicatorData[source.name] = {
                type: 'EXTERNAL',
                value: source.last_value,
                source_type: 'external',
                tbl_key: source.tbl_key
            };
        }
    }

    /**
     * Adds external sources to a signal source dropdown.
     * @param {string} dropdownId - The ID of the select element.
     */
    addExternalSourcesToDropdown(dropdownId) {
        const dropdown = document.getElementById(dropdownId);
        if (!dropdown || !this.externalSources || this.externalSources.length === 0) {
            return;
        }

        // Check if external sources section already exists
        let optgroup = dropdown.querySelector('optgroup[label="External Sources"]');

        // Remove existing external source options to avoid duplicates
        if (optgroup) {
            optgroup.remove();
        }

        // Create optgroup for external sources
        optgroup = document.createElement('optgroup');
        optgroup.label = 'External Sources';

        for (const source of this.externalSources) {
            // Check if option already exists (from server-side rendering)
            const existingOption = dropdown.querySelector(`option[value="${source.name}"]`);
            if (existingOption) {
                continue;
            }

            const opt = document.createElement('option');
            opt.value = source.name;
            opt.textContent = `${source.name} (${source.last_value ?? 'N/A'})`;
            opt.setAttribute('data-type', 'external');
            optgroup.appendChild(opt);
        }

        if (optgroup.children.length > 0) {
            dropdown.appendChild(optgroup);
        }
    }

    /**
     * Renders external sources in the UI.
     */
    renderExternalSources() {
        const container = document.getElementById('external_sources_list');
        const section = document.getElementById('external_sources_section');

        if (!container || !section) return;

        if (this.externalSources.length === 0) {
            section.style.display = 'none';
            return;
        }

        section.style.display = 'block';
        container.innerHTML = '';

        for (const source of this.externalSources) {
            const item = document.createElement('div');
            item.className = 'external-source-item';
            item.setAttribute('data-tbl-key', source.tbl_key);

            const lastFetch = source.last_fetch_time
                ? new Date(source.last_fetch_time * 1000).toLocaleTimeString()
                : 'Never';

            item.innerHTML = `
                <div class="source-name">${source.name}</div>
                <div class="source-value">${source.last_value ?? 'N/A'}</div>
                <div class="source-time">Last update: ${lastFetch}</div>
                <button class="refresh-btn" onclick="UI.signals.refreshExternalSource('${source.tbl_key}'); event.stopPropagation();">&#8635;</button>
                <button class="delete-source-btn" onclick="UI.signals.deleteExternalSource('${source.tbl_key}'); event.stopPropagation();">&times;</button>
            `;

            container.appendChild(item);
        }
    }

    /**
     * Refreshes an external source.
     * @param {string} tblKey - The source's unique key.
     */
    async refreshExternalSource(tblKey) {
        try {
            const response = await fetch(`/api/external_sources/refresh/${tblKey}`, {
                method: 'POST'
            });
            const data = await response.json();

            if (data.success) {
                // Update local data
                const source = this.externalSources.find(s => s.tbl_key === tblKey);
                if (source) {
                    source.last_value = data.value;
                    source.last_fetch_time = Date.now() / 1000;
                    this.renderExternalSources();
                    this.addExternalSourcesToIndicatorData();
                }
            } else {
                alert(`Refresh failed: ${data.error}`);
            }
        } catch (error) {
            alert(`Error: ${error.message}`);
        }
    }

    /**
     * Deletes an external source.
     * @param {string} tblKey - The source's unique key.
     */
    async deleteExternalSource(tblKey) {
        if (!confirm('Are you sure you want to delete this signal type?')) {
            return;
        }

        try {
            const response = await fetch(`/api/external_sources/delete/${tblKey}`, {
                method: 'DELETE'
            });
            const data = await response.json();

            if (data.success) {
                this.externalSources = this.externalSources.filter(s => s.tbl_key !== tblKey);
                this.renderExternalSources();

                // Remove from indicator data
                for (const source of this.externalSources) {
                    if (this.indicatorData && this.indicatorData[source.name]) {
                        delete this.indicatorData[source.name];
                    }
                }
            } else {
                alert(`Delete failed: ${data.message}`);
            }
        } catch (error) {
            alert(`Error: ${error.message}`);
        }
    }

    // =========================================================================
    // External Indicators (Historical data for backtesting)
    // =========================================================================

    /**
     * Opens the external indicator configuration form.
     */
    openExternalIndicatorForm() {
        const form = document.getElementById('external_indicator_form');
        if (form) {
            // Clear form fields
            document.getElementById('ext_ind_name').value = '';
            document.getElementById('ext_ind_url').value = '';
            document.getElementById('ext_ind_auth_header').value = '';
            document.getElementById('ext_ind_auth_key').value = '';
            document.getElementById('ext_ind_value_jsonpath').value = '$.data[*].value';
            document.getElementById('ext_ind_timestamp_jsonpath').value = '$.data[*].timestamp';
            document.getElementById('ext_ind_date_format').value = 'ISO';
            document.getElementById('ext_ind_date_param_format').value = 'ISO';

            // Hide test result
            const testResult = document.getElementById('ext_ind_test_result');
            if (testResult) testResult.style.display = 'none';

            // 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 external indicator form.
     */
    closeExternalIndicatorForm() {
        const form = document.getElementById('external_indicator_form');
        if (form) {
            form.style.display = 'none';
        }
    }

    /**
     * Tests the external indicator API configuration.
     */
    async testExternalIndicator() {
        const url = document.getElementById('ext_ind_url')?.value?.trim();
        const authHeader = document.getElementById('ext_ind_auth_header')?.value?.trim();
        const authKey = document.getElementById('ext_ind_auth_key')?.value?.trim();
        const valueJsonpath = document.getElementById('ext_ind_value_jsonpath')?.value?.trim();
        const timestampJsonpath = document.getElementById('ext_ind_timestamp_jsonpath')?.value?.trim();
        const dateFormat = document.getElementById('ext_ind_date_format')?.value;
        const dateParamFormat = document.getElementById('ext_ind_date_param_format')?.value;

        if (!url) {
            alert('Please enter a Historical Data URL');
            return;
        }

        const loadingEl = document.getElementById('ext_ind_loading');
        const testResultEl = document.getElementById('ext_ind_test_result');

        if (loadingEl) loadingEl.style.display = 'block';
        if (testResultEl) testResultEl.style.display = 'none';

        try {
            const response = await fetch('/api/external_indicators/test', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    historical_url: url,
                    auth_header: authHeader,
                    auth_key: authKey,
                    value_jsonpath: valueJsonpath,
                    timestamp_jsonpath: timestampJsonpath,
                    date_format: dateFormat,
                    date_param_format: dateParamFormat
                })
            });

            const data = await response.json();
            if (loadingEl) loadingEl.style.display = 'none';

            if (testResultEl) {
                testResultEl.style.display = 'block';
                if (data.success) {
                    testResultEl.className = 'success';
                    testResultEl.innerHTML = `
                        <strong>Success!</strong><br>
                        Found ${data.data_points} data points<br>
                        Sample values: ${data.sample_values?.join(', ')}<br>
                        Sample dates: ${data.sample_dates?.join(', ')}
                    `;
                } else {
                    testResultEl.className = 'error';
                    testResultEl.innerHTML = `<strong>Error:</strong> ${data.error}`;
                }
            }
        } catch (error) {
            if (loadingEl) loadingEl.style.display = 'none';
            if (testResultEl) {
                testResultEl.style.display = 'block';
                testResultEl.className = 'error';
                testResultEl.innerHTML = `<strong>Error:</strong> ${error.message}`;
            }
        }
    }

    /**
     * Submits the external indicator form to create a new indicator.
     */
    async submitExternalIndicator() {
        const name = document.getElementById('ext_ind_name')?.value?.trim();
        const url = document.getElementById('ext_ind_url')?.value?.trim();
        const authHeader = document.getElementById('ext_ind_auth_header')?.value?.trim();
        const authKey = document.getElementById('ext_ind_auth_key')?.value?.trim();
        const valueJsonpath = document.getElementById('ext_ind_value_jsonpath')?.value?.trim();
        const timestampJsonpath = document.getElementById('ext_ind_timestamp_jsonpath')?.value?.trim();
        const dateFormat = document.getElementById('ext_ind_date_format')?.value;
        const dateParamFormat = document.getElementById('ext_ind_date_param_format')?.value;

        if (!name) {
            alert('Please enter an indicator name');
            return;
        }

        if (!url) {
            alert('Please enter a Historical Data URL');
            return;
        }

        const loadingEl = document.getElementById('ext_ind_loading');
        if (loadingEl) loadingEl.style.display = 'block';

        try {
            const response = await fetch('/api/external_indicators/create', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    name: name,
                    historical_url: url,
                    auth_header: authHeader,
                    auth_key: authKey,
                    value_jsonpath: valueJsonpath,
                    timestamp_jsonpath: timestampJsonpath,
                    date_format: dateFormat,
                    date_param_format: dateParamFormat
                })
            });

            const data = await response.json();
            if (loadingEl) loadingEl.style.display = 'none';

            if (data.success) {
                this.closeExternalIndicatorForm();
                this.fetchExternalIndicators();
                alert(data.message);
            } else {
                alert(`Error: ${data.message}`);
            }
        } catch (error) {
            if (loadingEl) loadingEl.style.display = 'none';
            alert(`Error: ${error.message}`);
        }
    }

    /**
     * Fetches external indicators from the server.
     */
    async fetchExternalIndicators() {
        try {
            const response = await fetch('/api/external_indicators/list');
            const data = await response.json();

            if (data.success) {
                this.externalIndicators = data.indicators || [];
                this.renderExternalIndicators();
                this.addExternalIndicatorsToIndicatorData();
            }
        } catch (error) {
            console.error('Error fetching external indicators:', error);
        }
    }

    /**
     * Adds external indicators to indicator data so they appear in dropdowns.
     */
    addExternalIndicatorsToIndicatorData() {
        if (!this.indicatorData) {
            this.indicatorData = {};
        }

        for (const indicator of (this.externalIndicators || [])) {
            this.indicatorData[indicator.name] = {
                type: 'EXTERNAL_HISTORICAL',
                source_type: 'external_indicator',
                tbl_key: indicator.tbl_key
            };
        }
    }

    /**
     * Renders external indicators in the UI.
     */
    renderExternalIndicators() {
        const container = this.uiManager.externalIndicatorsListEl;
        const section = this.uiManager.externalIndicatorsSectionEl;

        if (!container || !section) {
            console.warn('External indicators elements not found');
            return;
        }

        if (!this.externalIndicators || this.externalIndicators.length === 0) {
            section.style.display = 'none';
            return;
        }

        section.style.display = 'block';
        container.innerHTML = '';

        for (const indicator of this.externalIndicators) {
            const item = document.createElement('div');
            item.className = 'external-indicator-item';
            item.setAttribute('data-tbl-key', indicator.tbl_key);

            item.innerHTML = `
                <div class="indicator-name">${indicator.name}</div>
                <div class="indicator-type">Historical API</div>
                <button class="delete-indicator-btn" onclick="UI.signals.deleteExternalIndicator('${indicator.tbl_key}'); event.stopPropagation();">&times;</button>
            `;

            container.appendChild(item);
        }
    }

    /**
     * Adds external indicators to a dropdown.
     * @param {string} dropdownId - The ID of the select element.
     */
    addExternalIndicatorsToDropdown(dropdownId) {
        const dropdown = document.getElementById(dropdownId);
        if (!dropdown || !this.externalIndicators || this.externalIndicators.length === 0) {
            return;
        }

        // Check if external indicators section already exists
        let optgroup = dropdown.querySelector('optgroup[label="External Indicators"]');

        // Remove existing to avoid duplicates
        if (optgroup) {
            optgroup.remove();
        }

        // Create optgroup for external indicators
        optgroup = document.createElement('optgroup');
        optgroup.label = 'External Indicators';

        for (const indicator of this.externalIndicators) {
            const opt = document.createElement('option');
            opt.value = indicator.name;
            opt.textContent = indicator.name;
            opt.setAttribute('data-type', 'external_indicator');
            optgroup.appendChild(opt);
        }

        if (optgroup.children.length > 0) {
            dropdown.appendChild(optgroup);
        }
    }

    /**
     * Deletes an external indicator.
     * @param {string} tblKey - The indicator's unique key.
     */
    async deleteExternalIndicator(tblKey) {
        if (!confirm('Are you sure you want to delete this external indicator? All cached historical data will be removed.')) {
            return;
        }

        try {
            const response = await fetch(`/api/external_indicators/delete/${tblKey}`, {
                method: 'DELETE'
            });
            const data = await response.json();

            if (data.success) {
                this.externalIndicators = this.externalIndicators.filter(i => i.tbl_key !== tblKey);
                this.renderExternalIndicators();

                // Remove from indicator data
                for (const indicator of this.externalIndicators) {
                    if (this.indicatorData && this.indicatorData[indicator.name]) {
                        delete this.indicatorData[indicator.name];
                    }
                }
            } else {
                alert(`Delete failed: ${data.message}`);
            }
        } catch (error) {
            alert(`Error: ${error.message}`);
        }
    }
}
