// Self-contained Hurst Bands indicator
// Based on J.M. Hurst's cyclic price channel theory
// Using RMA + ATR displacement method

import { INTERVALS } from '../core/constants.js';

const SIGNAL_TYPES = {
    BUY: 'buy',
    SELL: 'sell'
};

const SIGNAL_COLORS = {
    buy: '#9e9e9e',
    sell: '#9e9e9e'
};

class BaseIndicator {
    constructor(config) {
        this.config = config; 
        this.id = config.id;
        this.type = config.type;
        this.name = config.name;
        this.params = config.params || {};
        this.timeframe = config.timeframe || 'chart';
        this.series = [];
        this.visible = config.visible !== false;
        
        if (config.cachedResults === undefined) config.cachedResults = null;
        if (config.cachedMeta === undefined) config.cachedMeta = null;
        if (config.cachedTimeframe === undefined) config.cachedTimeframe = null;
        if (config.isFetching === undefined) config.isFetching = false;
        if (config.lastProcessedTime === undefined) config.lastProcessedTime = 0;
    }
    
    get cachedResults() { return this.config.cachedResults; }
    set cachedResults(v) { this.config.cachedResults = v; }
    get cachedMeta() { return this.config.cachedMeta; }
    set cachedMeta(v) { this.config.cachedMeta = v; }
    get cachedTimeframe() { return this.config.cachedTimeframe; }
    set cachedTimeframe(v) { this.config.cachedTimeframe = v; }
    get isFetching() { return this.config.isFetching; }
    set isFetching(v) { this.config.isFetching = v; }
    get lastProcessedTime() { return this.config.lastProcessedTime; }
    set lastProcessedTime(v) { this.config.lastProcessedTime = v; }
}

// Optimized RMA that can start from a previous state
function calculateRMAIncremental(sourceValue, prevRMA, length) {
    if (prevRMA === null || isNaN(prevRMA)) return sourceValue;
    const alpha = 1 / length;
    return alpha * sourceValue + (1 - alpha) * prevRMA;
}

// Calculate RMA for a full array with stable initialization
function calculateRMA(sourceArray, length) {
    const rma = new Array(sourceArray.length).fill(null);
    let sum = 0;
    const alpha = 1 / length;
    const smaLength = Math.round(length); 
    
    for (let i = 0; i < sourceArray.length; i++) {
        if (i < smaLength - 1) {
            sum += sourceArray[i];
        } else if (i === smaLength - 1) {
            sum += sourceArray[i];
            rma[i] = sum / smaLength;
        } else {
            const prevRMA = rma[i - 1];
            rma[i] = (prevRMA === null || isNaN(prevRMA)) 
                ? sourceArray[i] 
                : alpha * sourceArray[i] + (1 - alpha) * prevRMA;
        }
    }
    return rma;
}

function calculateHurstSignal(indicator, lastCandle, prevCandle, values, prevValues) {
    const close = lastCandle.close;
    const prevClose = prevCandle?.close;
    const upper = values?.upper;
    const lower = values?.lower;
    const prevUpper = prevValues?.upper;
    const prevLower = prevValues?.lower;
    
    if (close === undefined || prevClose === undefined || !upper || !lower || !prevUpper || !prevLower) {
        return null;
    }
    
    // BUY: Price crosses DOWN through lower Hurst Band (dip entry)
    if (prevClose > prevLower && close <= lower) {
        return {
            type: 'buy',
            strength: 80,
            value: close,
            reasoning: `Price crossed DOWN through lower Hurst Band`
        };
    }
    
    // SELL: Price crosses DOWN through upper Hurst Band (reversal entry)
    if (prevClose > prevUpper && close <= upper) {
        return {
            type: 'sell',
            strength: 80,
            value: close,
            reasoning: `Price crossed DOWN through upper Hurst Band`
        };
    }
    
    return null;
}

function getEffectiveTimeframe(params) {
    return params.timeframe === 'chart' ? window.dashboard?.currentInterval || '1m' : params.timeframe;
}

function intervalToSeconds(interval) {
    const amount = parseInt(interval);
    const unit = interval.replace(/[0-9]/g, '');
    
    switch (unit) {
        case 'm': return amount * 60;
        case 'h': return amount * 3600;
        case 'd': return amount * 86400;
        case 'w': return amount * 604800;
        case 'M': return amount * 2592000;
        default: return 60;
    }
}

async function getCandlesForTimeframe(tf, startTime, endTime) {
    const url = `${window.APP_CONFIG.API_BASE_URL}/candles?symbol=BTC&interval=${tf}&start=${startTime.toISOString()}&end=${endTime.toISOString()}&limit=5000`;
    const response = await fetch(url);
    if (!response.ok) {
        console.error(`Failed to fetch candles for ${tf}:`, response.status, response.statusText);
        return [];
    }
    const data = await response.json();
    // API returns newest first (desc), but indicators need oldest first (asc)
    // Also convert time to numeric seconds to match targetCandles
    return (data.candles || []).reverse().map(c => ({
        ...c,
        time: Math.floor(new Date(c.time).getTime() / 1000)
    }));
}

/**
 * Robust forward filling for MTF data.
 * @param {Array} results - MTF results (e.g. 5m)
 * @param {Array} targetCandles - Chart candles (e.g. 1m)
 */
function forwardFillResults(results, targetCandles) {
    if (!results || results.length === 0) {
        return new Array(targetCandles.length).fill(null);
    }
    
    const filled = new Array(targetCandles.length).fill(null);
    let resIdx = 0;
    
    for (let i = 0; i < targetCandles.length; i++) {
        const targetTime = targetCandles[i].time;
        
        // Advance result index while next result time is <= target time
        while (resIdx < results.length - 1 && results[resIdx + 1].time <= targetTime) {
            resIdx++;
        }
        
        // If the current result is valid for this target time, use it
        // (result time must be <= target time)
        if (results[resIdx] && results[resIdx].time <= targetTime) {
            filled[i] = results[resIdx];
        }
    }
    
    return filled;
}

export class HurstBandsIndicator extends BaseIndicator {
    constructor(config) {
        super(config);
        
        if (!this.params.timeframe) this.params.timeframe = 'chart';
        if (!this.params.markerBuyShape) this.params.markerBuyShape = 'custom';
        if (!this.params.markerSellShape) this.params.markerSellShape = 'custom';
        if (!this.params.markerBuyColor) this.params.markerBuyColor = '#9e9e9e';
        if (!this.params.markerSellColor) this.params.markerSellColor = '#9e9e9e';
        if (!this.params.markerBuyCustom) this.params.markerBuyCustom = '▲';
        if (!this.params.markerSellCustom) this.params.markerSellCustom = '▼';
    }

    calculate(candles) {
        const effectiveTf = getEffectiveTimeframe(this.params);
        const lastCandle = candles[candles.length - 1];
        
        // Case 1: Different timeframe (MTF)
        if (effectiveTf !== window.dashboard?.currentInterval && this.params.timeframe !== 'chart') {
            // If we have cached results, try to forward fill them to match the current candle count
            if (this.cachedResults && this.cachedTimeframe === effectiveTf) {
                // If results are stale (last result time is behind last candle time), trigger background fetch
                const lastResult = this.cachedResults[this.cachedResults.length - 1];
                const needsFetch = !this.isFetching && (!lastResult || lastCandle.time > lastResult.time + (intervalToSeconds(effectiveTf) / 2));
                
                if (needsFetch) {
                    this._fetchAndCalculateMtf(effectiveTf, candles);
                }

                // If length matches exactly and params haven't changed, return
                if (this.cachedResults.length === candles.length && !this.shouldRecalculate()) {
                    return this.cachedResults;
                }

                // If length differs (e.g. new 1m candle but 5m not fetched yet), forward fill
                const filled = forwardFillResults(this.cachedResults.filter(r => r !== null), candles);
                this.cachedResults = filled;
                return filled;
            }

            // Initial fetch
            if (!this.isFetching) {
                this._fetchAndCalculateMtf(effectiveTf, candles);
            }
            return new Array(candles.length).fill(null);
        }
        
        // Case 2: Same timeframe as chart (Incremental or Full)
        // Check if we can do incremental update
        if (this.cachedResults && 
            this.cachedResults.length > 0 && 
            this.cachedTimeframe === effectiveTf && 
            !this.shouldRecalculate() &&
            candles.length >= this.cachedResults.length &&
            candles[this.cachedResults.length - 1].time === this.cachedResults[this.cachedResults.length - 1].time) {
            
            // Only calculate new candles
            if (candles.length > this.cachedResults.length) {
                const newResults = this._calculateIncremental(candles, this.cachedResults);
                this.cachedResults = newResults;
            }
            return this.cachedResults;
        }

        // Full calculation
        const results = this._calculateCore(candles);
        this.cachedTimeframe = effectiveTf;
        this.updateCachedMeta(this.params);
        this.cachedResults = results;
        return results;
    }

    _calculateCore(candles) {
        const mcl_t = this.params.period || 30;
        const mcm = this.params.multiplier || 1.8;
        
        const mcl = mcl_t / 2;
        const mcl_2 = Math.round(mcl / 2); 
        
        const results = new Array(candles.length).fill(null);
        const closes = candles.map(c => c.close);
        
        // True Range for ATR
        const trArray = candles.map((d, i) => {
            const prevClose = i > 0 ? candles[i - 1].close : null;
            if (prevClose === null || isNaN(prevClose)) return d.high - d.low;
            return Math.max(d.high - d.low, Math.abs(d.high - prevClose), Math.abs(d.low - prevClose));
        });
        
        const ma_mcl = calculateRMA(closes, mcl);
        const atr = calculateRMA(trArray, mcl);
        
        for (let i = 0; i < candles.length; i++) {
            const mcm_off = mcm * (atr[i] || 0);
            const historicalIndex = i - mcl_2;
            const historical_ma = historicalIndex >= 0 ? ma_mcl[historicalIndex] : null;
            const centerLine = (historical_ma === null || isNaN(historical_ma)) ? closes[i] : historical_ma;
            
            results[i] = {
                time: candles[i].time,
                upper: centerLine + mcm_off,
                lower: centerLine - mcm_off,
                ma: ma_mcl[i], // Store intermediate state for incremental updates
                atr: atr[i]
            };
        }
        return results;
    }

    _calculateIncremental(candles, oldResults) {
        const mcl_t = this.params.period || 30;
        const mcm = this.params.multiplier || 1.8;
        const mcl = mcl_t / 2;
        const mcl_2 = Math.round(mcl / 2);

        const results = [...oldResults];
        const startIndex = oldResults.length;

        for (let i = startIndex; i < candles.length; i++) {
            const close = candles[i].close;
            const prevClose = candles[i-1].close;
            const tr = Math.max(candles[i].high - candles[i].low, Math.abs(candles[i].high - prevClose), Math.abs(candles[i].low - prevClose));

            const prevMA = results[i-1]?.ma;
            const prevATR = results[i-1]?.atr;

            const currentMA = calculateRMAIncremental(close, prevMA, mcl);
            const currentATR = calculateRMAIncremental(tr, prevATR, mcl);

            // For displaced center line, we still need the MA from i - mcl_2
            // Since i >= oldResults.length, i - mcl_2 might be in the old results
            let historical_ma = null;
            const historicalIndex = i - mcl_2;
            if (historicalIndex >= 0) {
                historical_ma = historicalIndex < startIndex ? results[historicalIndex].ma : null; // In this simple incremental, we don't look ahead
            }

            const centerLine = (historical_ma === null || isNaN(historical_ma)) ? close : historical_ma;
            const mcm_off = mcm * (currentATR || 0);

            results[i] = {
                time: candles[i].time,
                upper: centerLine + mcm_off,
                lower: centerLine - mcm_off,
                ma: currentMA,
                atr: currentATR
            };
        }
        return results;
    }

    async _fetchAndCalculateMtf(effectiveTf, targetCandles) {
        this.isFetching = true;
        try {
            console.log(`[Hurst] Fetching MTF data for ${effectiveTf}...`);
            const chartData = window.dashboard?.allData?.get(window.dashboard?.currentInterval) || targetCandles;
            if (!chartData || chartData.length === 0) {
                console.warn('[Hurst] No chart data available for timeframe fetch');
                this.isFetching = false;
                return;
            }
            
            // Calculate warmup needed (period + half width)
            const mcl_t = this.params.period || 30;
            const warmupBars = mcl_t * 2; // Extra buffer
            const tfSeconds = intervalToSeconds(effectiveTf);
            const warmupOffsetSeconds = warmupBars * tfSeconds;
            
            // Candles endpoint expects ISO strings or timestamps. 
            // chartData[0].time is the earliest candle on chart.
            const startTime = new Date((chartData[0].time - warmupOffsetSeconds) * 1000);
            const endTime = new Date(chartData[chartData.length - 1].time * 1000);
            
            const tfCandles = await getCandlesForTimeframe(effectiveTf, startTime, endTime);
            if (tfCandles.length === 0) {
                console.warn(`[Hurst] No candles fetched for ${effectiveTf}`);
                this.isFetching = false;
                return;
            }
            
            console.log(`[Hurst] Fetched ${tfCandles.length} candles for ${effectiveTf}. Calculating...`);
            const tfResults = this._calculateCore(tfCandles);
            const finalResults = forwardFillResults(tfResults, targetCandles);
            
            // Persist results on the config object
            this.cachedResults = finalResults;
            this.cachedTimeframe = effectiveTf;
            this.updateCachedMeta(this.params);
            
            console.log(`[Hurst] MTF calculation complete for ${effectiveTf}. Triggering redraw.`);
            
            // Trigger a redraw of the dashboard to show the new data
            if (window.drawIndicatorsOnChart) {
                window.drawIndicatorsOnChart();
            }
        } catch (err) {
            console.error('[Hurst] Error in _fetchAndCalculateMtf:', err);
        } finally {
            this.isFetching = false;
        }
    }
    
    getMetadata() {
        return {
            name: 'Hurst Bands',
            description: 'Cyclic price channels based on Hurst theory',
            inputs: [
                { 
                    name: 'timeframe', 
                    label: 'Timeframe', 
                    type: 'select', 
                    default: 'chart', 
                    options: ['chart', ...INTERVALS],
                    labels: { chart: '(Main Chart)' }
                },
                { name: 'period', label: 'Hurst Cycle Length (mcl_t)', type: 'number', default: 30, min: 5, max: 200 },
                { name: 'multiplier', label: 'Multiplier (mcm)', type: 'number', default: 1.8, min: 0.5, max: 10, step: 0.1 }
            ],
            plots: [
                { id: 'upper', color: '#808080', title: 'Upper', lineWidth: 1 },
                { id: 'lower', color: '#808080', title: 'Lower', lineWidth: 1 }
            ],
            bands: [
                { topId: 'upper', bottomId: 'lower', color: 'rgba(128, 128, 128, 0.05)' }
            ],
            displayMode: 'overlay'
        };
    }
    
    shouldRecalculate() {
        const effectiveTf = getEffectiveTimeframe(this.params);
        return this.cachedTimeframe !== effectiveTf || 
               (this.cachedMeta && (this.cachedMeta.period !== this.params.period || 
                                   this.cachedMeta.multiplier !== this.params.multiplier));
    }
    
    updateCachedMeta(params) {
        this.cachedMeta = {
            period: params.period,
            multiplier: params.multiplier
        };
    }
}

export { calculateHurstSignal };
