class TechnicalAnalysis {
    constructor(options = {}) {
        this.CONFIG = {
            API_TIMEOUT: 1e4,
            DEFAULT_PERIOD: "1D",
            NEEDLE_ANIMATION_DELAY: 500,
            SCORE_UPDATE_DELAY: 500,
            ANGLE_RANGES: {
                strongSell: {
                    min: -90,
                    max: -60,
                    center: -75
                },
                sell: {
                    min: -60,
                    max: -30,
                    center: -45
                },
                neutral: {
                    min: -30,
                    max: 30,
                    center: 0
                },
                buy: {
                    min: 30,
                    max: 60,
                    center: 45
                },
                strongBuy: {
                    min: 60,
                    max: 90,
                    center: 75
                }
            },
            STRONG_SIGNAL_THRESHOLD: .6,
            PIVOTS_LABEL: [ "R3", "R2", "R1", "P", "S1", "S2", "S3" ]
        };
        this.symbol = options.symbol || "";
        this.exchange = options.exchange || "";
        this.period = options.period || this.CONFIG.DEFAULT_PERIOD;
        this.root = options.rootNode || document;
        this.autoFetch = options.autoFetch !== undefined ? options.autoFetch : false;
        this.enableTableValue = options.enableTableValue !== false;
        this.enableTableScore = options.enableTableScore !== false;
        this.enableTableAction = options.enableTableAction !== false;
        this.enableTimeframeIcons = options.enableTimeframeIcons !== undefined ? options.enableTimeframeIcons : true;
        this.apiColumns = this.#buildApiColumns();
        this.state = {
            firstLoad: true,
            controller: null
        };
        this.dom = {};
        this.init();
    }
    #buildApiColumns() {
        const cols = [];
        const hasSummaryGauge = !!this.root.getElementById("summry-needle");
        if (this.root.querySelector(".metrics-grid") || this.root.querySelector(".gauge-container")) {
            cols.push("signal");
        }
        if (hasSummaryGauge || this.root.getElementById("osc-needle") || this.root.querySelector(".oscillators-table")) {
            cols.push("oscillators");
        }
        if (hasSummaryGauge || this.root.getElementById("ma-needle") || this.root.querySelector(".moving-averages-table")) {
            cols.push("ma_avg");
        }
        if (this.root.querySelector(".pivots-table")) {
            cols.push("pivots");
        }
        return cols.length ? cols.join(",") : "signal,oscillators,ma_avg,pivots";
    }
    get lang() {
        return getLang("symbol_detail.technicals", {});
    }
    init() {
        this.#cacheDOMElements();
        this.#bindEvents();
        const activeBtn = this.root.querySelector(".control-btn[data-timeframe].active");
        if (activeBtn) {
            scrollToCenter(activeBtn, this.root.querySelector(".data-controls-left"));
        }
        if (this.autoFetch) {
            this.#fetchData();
        }
    }
    #cacheDOMElements() {
        this.dom = {
            timeframeBtns: this.root.querySelectorAll(".control-btn[data-timeframe]"),
            technicalSection: this.root.querySelector(".technical-section"),
            loadingInitial: this.root.querySelector(".menu-search-result-initial-loading"),
            needles: this.root.querySelectorAll(".needle")
        };
    }
    #bindEvents() {
        this.dom.timeframeBtns.forEach(btn => {
            btn.addEventListener("click", e => {
                this.#handleTimeframeChange(e);
            });
        });
    }
    #handleTimeframeChange(e) {
        e.preventDefault();
        const btn = e.currentTarget;
        const newPeriod = btn.dataset.timeframe;
        if (newPeriod === this.period) return;
        scrollToCenter(btn, this.root.querySelector(".data-controls-left"));
        this.dom.timeframeBtns.forEach(b => b.classList.remove("active"));
        btn.classList.add("active");
        this.period = newPeriod;
        this.removeNeedleAnimations();
        this.showButtonLoading();
        this.#fetchData();
    }
    #updateTimeframeButtons(allSignals) {
        const symbolData = allSignals[Object.keys(allSignals)[0]];
        if (!symbolData) return;
        const btns = this.dom.timeframeBtns;
        const len = btns.length;
        for (let i = 0; i < len; i++) {
            const btn = btns[i];
            const timeframe = btn.getAttribute("data-timeframe");
            if (symbolData[timeframe]) {
                const summary = symbolData[timeframe].summary.toLowerCase();
                const signalData = getValueStyle(summary);
                const svgHtml = `<svg width="18" height="18" fill="none" stroke="currentColor" class="${signalData.class}"><use href="#${signalData.icon}"></use></svg>`;
                const controlIcon = btn.querySelector(".control-icon");
                if (controlIcon) {
                    controlIcon.innerHTML = svgHtml;
                }
            }
        }
    }
    async #fetchData() {
        if (this.state.controller) {
            this.state.controller.abort();
        }
        this.state.controller = new AbortController;
        try {
            const result = await sendPostRequest(`${API_ENDPOINT}forex/advance`, {
                columns: this.apiColumns + (this.autoFetch ? ",profile,meta" : ""),
                symbol: this.symbol,
                exchange: this.exchange,
                period: this.period,
                type: "global",
                ...this.enableTimeframeIcons ? {
                    all_signals: 1
                } : {}
            }, {
                signal: this.state.controller.signal,
                timeout: this.CONFIG.API_TIMEOUT
            });
            this.updateFromExternal(result);
        } catch (error) {
            if (error.name !== "AbortError") {
                console.error("Technical data error:", error);
            }
        } finally {
            this.#hideLoading();
        }
    }
    updateFromExternal(result) {
        try {
            if (result.status && result.response && result.response.length > 0) {
                const data = result.response[0];
                this.#updateSymbolHeader(data);
                this.#updateAllData(data);
                this.#updatePageIntro(data);
                if (result.info && result.info.all_signals) {
                    this.#updateTimeframeButtons(result.info.all_signals);
                }
                requestAnimationFrame(() => {
                    setTimeout(() => {
                        this.#addNeedleAnimations();
                    }, 300);
                });
            }
        } catch (error) {
            console.error("Update error:", error);
        } finally {
            this.#hideLoading();
        }
    }
    #updatePageIntro(data) {
        const el = document.getElementById("tech-page-intro");
        if (!el) return;
        const fragments = typeof getLang === "function" ? getLang("symbol_detail.technicals.intro_fragments", null) : null;
        if (!fragments || typeof fragments !== "object") return;
        const profile = data.profile || {};
        const signal = data.signal || {};
        const osc = data.oscillators || {};
        const name = profile.name || profile.symbol || "";
        const symbol = profile.symbol || "";
        const exchange = profile.exchange || "";
        const sig = signal.summary || "Neutral";
        const score = signal.score !== undefined ? (+signal.score).toFixed(1) : "0";
        const conf = signal.confidence || "Low";
        const trend = signal.trend || "Moderate";
        const vol = signal.volatility || "Medium";
        const rsiVal = osc.rsi ? (+osc.rsi.v).toFixed(1) : "--";
        const rsiSig = osc.rsi ? osc.rsi.s || "" : "";
        const rsiZone = +rsiVal >= 70 ? "overbought" : +rsiVal <= 30 ? "oversold" : "neutral";
        const rsiZoneSuffix = +rsiVal >= 70 ? ", approaching overbought" : +rsiVal <= 30 ? ", approaching oversold" : "";
        const macdSig = osc.macd ? osc.macd.s || "" : "";
        const adxVal = osc.adx ? (+osc.adx.v).toFixed(1) : "--";
        const adxNum = osc.adx ? +osc.adx.v : 0;
        const adxCtx = adxNum >= 40 ? "very strong" : adxNum >= 25 ? "strong" : adxNum >= 15 ? "moderate" : "weak";
        const values = {
            name: name,
            symbol: symbol,
            exchange: exchange,
            signal: sig,
            score: score,
            confidence: conf,
            trend: trend,
            volatility: vol,
            rsi: rsiVal,
            rsi_signal: rsiSig,
            rsi_zone: rsiZone,
            rsi_zone_suffix: rsiZoneSuffix,
            macd_signal: macdSig,
            adx: adxVal,
            adx_context: adxCtx,
            indicator_count: 27
        };
        const fill = tpl => tpl.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, k) => values[k] ?? "");
        const fnv = s => {
            let h = 2166136261;
            for (let i = 0; i < s.length; i++) {
                h ^= s.charCodeAt(i);
                h = h * 16777619 >>> 0;
            }
            return h;
        };
        const base = symbol + "|" + exchange;
        const pick = (arr, seed) => Array.isArray(arr) && arr.length ? arr[fnv(base + seed) % arr.length] : "";
        const opening = fill(pick(fragments.opening, "_open"));
        const rating = fill(pick(fragments.rating, "_rate"));
        const rsiLine = fill(pick(fragments.rsi_line, "_rsi"));
        const macdAdx = fill(pick(fragments.macd_adx, "_macd"));
        const closing = fill(pick(fragments.closing, "_close"));
        el.innerHTML = "";
        el.textContent = `${opening} ${rating}. ${rsiLine}, ${macdAdx}. ${closing}`;
        el.removeAttribute("aria-busy");
    }
    #updateAllData(data) {
        this.updateMetrics(data.signal || null);
        this.updateGaugeCharts(data);
        this.updateOscillatorsTable(data.oscillators || null);
        this.updateMovingAveragesTable(data.ma_avg || null);
        this.updatePivotsTable(data.pivots || null);
    }
    #updateSymbolHeader(data) {
        const container = this.root.querySelector(".technical-symbol-header");
        if (!container) return;
        const profile = data.profile || {};
        const meta = data.meta || {};
        const logoEl = container.querySelector(".tech-symbol-logo");
        if (logoEl && typeof generateCoinLogoHtml === "function") {
            logoEl.innerHTML = generateCoinLogoHtml(profile).replace(/data-src="/g, 'src="');
        }
        const nameEl = container.querySelector(".tech-symbol-name");
        if (nameEl) nameEl.textContent = profile.name || meta.base_currency_desc || "";
        const symbolEl = container.querySelector(".tech-symbol-ticker");
        if (symbolEl) symbolEl.textContent = profile.symbol || "";
        const exchangeEl = container.querySelector(".tech-symbol-exchange");
        if (exchangeEl) exchangeEl.textContent = profile.exchange || "";
    }
    updateMetrics(signal) {
        if (!signal) return;
        this.#updateScore(signal.score);
        this.#updateConfidence(signal.confidence);
        this.#updateTrend(signal.trend);
        this.#updateVolatility(signal.volatility || "MEDIUM");
    }
    #updateScore(score) {
        setTimeout(() => {
            const fill = this.root.getElementById("progress-fill");
            if (fill) {
                fill.style.width = Math.abs(score) + "%";
                fill.classList.remove("positive", "negative");
                fill.classList.add(score >= 0 ? "positive" : "negative");
            }
        }, this.CONFIG.SCORE_UPDATE_DELAY);
        const scorePercentage = this.root.getElementById("score-percentage");
        if (scorePercentage) {
            scorePercentage.textContent = score + "%";
        }
        const icon = score >= 0 ? '<svg width="24" height="24" fill="none" stroke="currentColor" class="fx-positive ms-2"><use href="#trending-up"></use></svg>' : '<svg width="24" height="24" fill="none" stroke="currentColor" class="fx-negative ms-2"><use href="#trending-down"></use></svg>';
        const scoreValue = this.root.getElementById("score-value");
        if (scoreValue) {
            scoreValue.innerHTML = score + icon;
        }
    }
    #updateConfidence(level) {
        if (!level) return;
        const lowerLevel = level.toLowerCase();
        const confidenceLang = this.lang.confidence || {};
        const levelsLang = this.lang.levels || {};
        const text = confidenceLang[lowerLevel] || level;
        const levelText = levelsLang[lowerLevel] || level;
        const confidenceValue = this.root.getElementById("confidence-value");
        if (confidenceValue) {
            confidenceValue.textContent = levelText;
            confidenceValue.className = "metric-value confidence-" + lowerLevel;
        }
        const confidenceText = this.root.getElementById("confidence-text");
        if (confidenceText) {
            confidenceText.textContent = text;
        }
        const levels = [ "low", "medium", "high" ];
        this.#updateBars(".confidence-bar", levels, lowerLevel);
    }
    #updateTrend(level) {
        if (!level) return;
        const lowerLevel = level.toLowerCase();
        const trendLang = this.lang.trend || {};
        const levelsLang = this.lang.levels || {};
        const text = trendLang[lowerLevel] || level;
        const levelText = levelsLang[lowerLevel] || level;
        const trendValue = this.root.getElementById("trend-value");
        if (trendValue) {
            trendValue.textContent = levelText;
            trendValue.className = "metric-value trend-" + lowerLevel;
        }
        const trendStatus = this.root.getElementById("trend-status");
        if (trendStatus) {
            trendStatus.textContent = text;
        }
        const levels = [ "weak", "moderate", "strong" ];
        this.#updateBars(".trend-point", levels, lowerLevel);
    }
    #updateVolatility(level) {
        const lowerLevel = level.toLowerCase();
        const volatilityLang = this.lang.volatility || {};
        const levelsLang = this.lang.levels || {};
        const text = volatilityLang[lowerLevel] || level;
        const levelText = levelsLang[lowerLevel] || level;
        const volatilityValue = this.root.getElementById("volatility-value");
        if (volatilityValue) {
            volatilityValue.textContent = levelText;
            volatilityValue.className = "metric-value volatility-" + lowerLevel;
        }
        const volatilityLevel = this.root.getElementById("volatility-level");
        if (volatilityLevel) {
            volatilityLevel.textContent = text;
        }
        const levels = [ "low", "medium", "high", "extreme" ];
        this.#updateBars(".volatility-bar", levels, lowerLevel);
    }
    #updateBars(selector, levels, current) {
        const index = levels.indexOf(current);
        const bars = this.root.querySelectorAll(selector);
        const len = bars.length;
        for (let i = 0; i < len; i++) {
            bars[i].classList.toggle("color-bg-green", i <= index);
        }
    }
    updateGaugeCharts(data) {
        const oscCounts = this.#countSignals(data.oscillators);
        const maCounts = this.#countSignals(data.ma_avg);
        const oscScore = this.#countSignalsWithScore(data.oscillators);
        const maScore = this.#countSignalsWithScore(data.ma_avg);
        this.updateSummaryChart(data.signal, oscCounts, maCounts, oscScore, maScore);
        this.updateOscillatorsChart(oscCounts, oscScore);
        this.updateMovingAveragesChart(maCounts, maScore);
    }
    #countSignals(indicators) {
        if (!indicators) return {
            sell: 0,
            neutral: 0,
            buy: 0
        };
        const counts = {
            sell: 0,
            neutral: 0,
            buy: 0
        };
        const keys = Object.keys(indicators);
        const len = keys.length;
        for (let i = 0; i < len; i++) {
            const signal = indicators[keys[i]].s.toLowerCase();
            if (signal.includes("sell")) {
                counts.sell++;
            } else if (signal.includes("buy")) {
                counts.buy++;
            } else {
                counts.neutral++;
            }
        }
        return counts;
    }
    #countSignalsWithScore(indicators) {
        if (!indicators) return {
            sell: 0,
            neutral: 0,
            buy: 0,
            sellScore: 0,
            neutralScore: 0,
            buyScore: 0,
            totalScore: 0
        };
        const counts = {
            sell: 0,
            neutral: 0,
            buy: 0
        };
        const scores = {
            sellScore: 0,
            neutralScore: 0,
            buyScore: 0
        };
        const keys = Object.keys(indicators);
        const len = keys.length;
        for (let i = 0; i < len; i++) {
            const indicator = indicators[keys[i]];
            const signal = indicator.s.toLowerCase();
            const apiScore = indicator.score || 0;
            if (signal.includes("sell")) {
                counts.sell++;
                scores.sellScore += apiScore;
            } else if (signal.includes("buy")) {
                counts.buy++;
                scores.buyScore += apiScore;
            } else {
                counts.neutral++;
                scores.neutralScore += apiScore;
            }
        }
        counts.sellScore = scores.sellScore;
        counts.neutralScore = scores.neutralScore;
        counts.buyScore = scores.buyScore;
        counts.totalScore = scores.sellScore + scores.neutralScore + scores.buyScore;
        return counts;
    }
    updateSummaryChart(signal, oscCounts, maCounts, oscScore, maScore) {
        if (!signal) return;
        const overallSummary = this.root.querySelector(".overall-chart-summary");
        if (overallSummary) {
            const translatedSummary = this.#translateSignal(signal.summary);
            overallSummary.textContent = translatedSummary;
            overallSummary.className = "mb-3 summary-detail-heading overall-chart-summary " + getValueStyle(signal.summary, "class");
        }
        const totalSell = oscCounts.sell + maCounts.sell;
        const totalNeutral = oscCounts.neutral + maCounts.neutral;
        const totalBuy = oscCounts.buy + maCounts.buy;
        const chartSellPrice = this.root.querySelector(".chart-sell-price");
        if (chartSellPrice) chartSellPrice.textContent = totalSell;
        const chartNeutralPrice = this.root.querySelector(".chart-neutral-price");
        if (chartNeutralPrice) chartNeutralPrice.textContent = totalNeutral;
        const chartBuyPrice = this.root.querySelector(".chart-buy-price");
        if (chartBuyPrice) chartBuyPrice.textContent = totalBuy;
        const chartSellScore = this.root.querySelector(".chart-sell-score");
        if (chartSellScore) chartSellScore.textContent = oscScore.sellScore + maScore.sellScore;
        const chartNeutralScore = this.root.querySelector(".chart-neutral-score");
        if (chartNeutralScore) chartNeutralScore.textContent = oscScore.neutralScore + maScore.neutralScore;
        const chartBuyScore = this.root.querySelector(".chart-buy-score");
        if (chartBuyScore) chartBuyScore.textContent = oscScore.buyScore + maScore.buyScore;
        this.#updateNeedle("summry-needle", signal.summary, signal.score);
        this.#updateGaugeSegments("summary-gauge-svg", signal.summary);
        this.#updateGaugeLabels("summary", signal.summary);
    }
    updateOscillatorsChart(counts, scores) {
        const dominantSignal = this.#getDominantSignalScores(scores);
        const translatedSignal = this.#translateSignal(dominantSignal);
        const oscSummary = this.root.querySelector(".osc-chart-summary");
        if (oscSummary) {
            oscSummary.textContent = translatedSignal;
            oscSummary.className = "mb-3 summary-detail-heading osc-chart-summary " + getValueStyle(dominantSignal, "class");
        }
        const oscSellPrice = this.root.querySelector(".osc-chart-sell-price");
        if (oscSellPrice) oscSellPrice.textContent = counts.sell;
        const oscNeutralPrice = this.root.querySelector(".osc-chart-neutral-price");
        if (oscNeutralPrice) oscNeutralPrice.textContent = counts.neutral;
        const oscBuyPrice = this.root.querySelector(".osc-chart-buy-price");
        if (oscBuyPrice) oscBuyPrice.textContent = counts.buy;
        const oscSellScore = this.root.querySelector(".osc-chart-sell-score");
        if (oscSellScore) oscSellScore.textContent = scores.sellScore;
        const oscNeutralScore = this.root.querySelector(".osc-chart-neutral-score");
        if (oscNeutralScore) oscNeutralScore.textContent = scores.neutralScore;
        const oscBuyScore = this.root.querySelector(".osc-chart-buy-score");
        if (oscBuyScore) oscBuyScore.textContent = scores.buyScore;
        this.#updateNeedle("osc-needle", dominantSignal, scores);
        this.#updateGaugeSegments("oscillators-gauge-svg", dominantSignal);
        this.#updateGaugeLabels("oscillators", dominantSignal);
    }
    updateMovingAveragesChart(counts, scores) {
        const dominantSignal = this.#getDominantSignalScores(scores);
        const translatedSignal = this.#translateSignal(dominantSignal);
        const maSummary = this.root.querySelector(".ma-chart-summary");
        if (maSummary) {
            maSummary.textContent = translatedSignal;
            maSummary.className = "mb-3 summary-detail-heading ma-chart-summary " + getValueStyle(dominantSignal, "class");
        }
        const maSellPrice = this.root.querySelector(".ma-chart-sell-price");
        if (maSellPrice) maSellPrice.textContent = counts.sell;
        const maNeutralPrice = this.root.querySelector(".ma-chart-neutral-price");
        if (maNeutralPrice) maNeutralPrice.textContent = counts.neutral;
        const maBuyPrice = this.root.querySelector(".ma-chart-buy-price");
        if (maBuyPrice) maBuyPrice.textContent = counts.buy;
        const maSellScore = this.root.querySelector(".ma-chart-sell-score");
        if (maSellScore) maSellScore.textContent = scores.sellScore;
        const maNeutralScore = this.root.querySelector(".ma-chart-neutral-score");
        if (maNeutralScore) maNeutralScore.textContent = scores.neutralScore;
        const maBuyScore = this.root.querySelector(".ma-chart-buy-score");
        if (maBuyScore) maBuyScore.textContent = scores.buyScore;
        this.#updateNeedle("ma-needle", dominantSignal, scores);
        this.#updateGaugeSegments("moving-averages-gauge-svg", dominantSignal);
        this.#updateGaugeLabels("moving-averages", dominantSignal);
    }
    #getDominantSignalScores(scores) {
        const sellScore = Math.abs(scores.sellScore);
        const buyScore = scores.buyScore;
        const neutralScore = Math.abs(scores.neutralScore);
        const totalScore = sellScore + buyScore + neutralScore;
        if (buyScore > sellScore && buyScore > neutralScore) {
            return buyScore > totalScore * this.CONFIG.STRONG_SIGNAL_THRESHOLD ? "Strong Buy" : "Buy";
        } else if (sellScore > buyScore && sellScore > neutralScore) {
            return sellScore > totalScore * this.CONFIG.STRONG_SIGNAL_THRESHOLD ? "Strong Sell" : "Sell";
        }
        return "Neutral";
    }
    #updateNeedle(needleId, signal, signalData) {
        const needle = this.root.getElementById(needleId);
        if (!needle) return;
        let angle = 0;
        const lower = signal.toLowerCase();
        if (signalData && typeof signalData === "object" && signalData.totalScore !== undefined) {
            angle = this.#calculateAngleFromScores(signalData, lower);
        } else if (signalData && typeof signalData === "number") {
            angle = this.#calculateAngleFromSingleScore(signalData, lower);
        } else {
            angle = this.#getStaticAngle(lower);
        }
        needle.style.transform = `translateX(-50%) rotate(${angle}deg)`;
        needle.style.setProperty("--needle-angle", angle + "deg");
    }
    #calculateAngleFromScores(scores, signal) {
        const sellScore = Math.abs(scores.sellScore);
        const buyScore = scores.buyScore;
        const neutralScore = Math.abs(scores.neutralScore);
        const totalScore = sellScore + buyScore + neutralScore;
        if (totalScore === 0) return 0;
        const sellRatio = sellScore / totalScore;
        const buyRatio = buyScore / totalScore;
        const netBias = buyRatio - sellRatio;
        return this.#calculateAngleInRange(signal, netBias);
    }
    #calculateAngleFromSingleScore(score, signal) {
        const normalized = score / 100;
        return this.#calculateAngleInRange(signal, normalized);
    }
    #calculateAngleInRange(signal, bias) {
        const ranges = this.CONFIG.ANGLE_RANGES;
        let range, angle;
        if (signal.includes("strong sell")) {
            range = ranges.strongSell;
            angle = range.center + bias * 15;
        } else if (signal.includes("sell")) {
            range = ranges.sell;
            angle = range.center + bias * 15;
        } else if (signal.includes("strong buy")) {
            range = ranges.strongBuy;
            angle = range.center + bias * 15;
        } else if (signal.includes("buy")) {
            range = ranges.buy;
            angle = range.center + bias * 15;
        } else {
            range = ranges.neutral;
            angle = bias * 30;
        }
        return Math.max(range.min, Math.min(range.max, angle));
    }
    #getStaticAngle(signal) {
        const ranges = this.CONFIG.ANGLE_RANGES;
        if (signal.includes("strong sell")) return ranges.strongSell.center;
        if (signal.includes("sell")) return ranges.sell.center;
        if (signal.includes("strong buy")) return ranges.strongBuy.center;
        if (signal.includes("buy")) return ranges.buy.center;
        return ranges.neutral.center;
    }
    #updateGaugeSegments(svgId, signal) {
        const svg = this.root.getElementById(svgId);
        if (!svg) return;
        const paths = svg.querySelectorAll("path");
        const len = paths.length;
        for (let i = 0; i < len; i++) {
            paths[i].classList.remove("active");
        }
        const lower = signal.toLowerCase();
        const classMap = {
            "strong sell": ".strong-sell-svg",
            "sell": ".sell-svg",
            "neutral": ".segment-neutral-svg",
            "strong buy": ".strong-buy-svg",
            "buy": ".buy-svg"
        };
        for (let key in classMap) {
            if (lower.includes(key)) {
                const path = svg.querySelector(classMap[key]);
                if (path) path.classList.add("active");
                break;
            }
        }
    }
    #updateGaugeLabels(chartType, signal) {
        let container;
        if (chartType === "summary") {
            container = this.root.querySelector(".summary-container");
        } else {
            const svgId = chartType === "oscillators" ? "oscillators-gauge-svg" : "moving-averages-gauge-svg";
            const svg = this.root.getElementById(svgId);
            container = svg?.closest(".gauge-container");
        }
        if (!container) return;
        container.querySelectorAll(".gauge-label").forEach(label => label.classList.remove("active"));
        const lower = signal.toLowerCase();
        const labelMap = {
            "strong sell": ".label-strong-sell",
            "sell": ".label-sell",
            "neutral": ".label-neutral",
            "strong buy": ".label-strong-buy",
            "buy": ".label-buy"
        };
        for (let key in labelMap) {
            if (lower.includes(key)) {
                const label = container.querySelector(labelMap[key]);
                if (label) label.classList.add("active");
                break;
            }
        }
    }
    #translateIndicator(name) {
        const indicators = this.lang.indicators || {};
        return indicators[name] || name;
    }
    #translateSignal(signal) {
        const signals = this.lang.signals || {};
        const key = signal.toLowerCase().replace(/\s+/g, "_");
        return signals[key] || signal;
    }
    updateOscillatorsTable(oscillators) {
        if (!oscillators) return;
        const table = this.root.querySelector(".oscillators-table");
        const tbody = table?.querySelector("tbody");
        if (!tbody) return;
        const keys = Object.keys(oscillators);
        const rows = [];
        for (let i = 0; i < keys.length; i++) {
            const ind = oscillators[keys[i]];
            const score = ind.score || 0;
            const translatedName = this.#translateIndicator(ind.n);
            const translatedSignal = this.#translateSignal(ind.s);
            let row = `<tr><td>${translatedName}</td>`;
            if (this.enableTableValue) row += `<td class="text-end">${addThousandSeparator(String(ind.v))}</td>`;
            if (this.enableTableScore) row += `<td class="text-end">${score}</td>`;
            if (this.enableTableAction) row += `<td><span class="indicator-action ${getValueStyle(ind.s, "class")}">${translatedSignal}</span></td>`;
            row += `</tr>`;
            rows.push(row);
        }
        tbody.innerHTML = rows.join("");
        table.classList.remove("technical-table-skeleton");
        table.closest(".table-responsive")?.removeAttribute("aria-busy");
    }
    updateMovingAveragesTable(movingAverages) {
        if (!movingAverages) return;
        const table = this.root.querySelector(".moving-averages-table");
        const tbody = table?.querySelector("tbody");
        if (!tbody) return;
        const keys = Object.keys(movingAverages);
        const rows = [];
        for (let i = 0; i < keys.length; i++) {
            const ind = movingAverages[keys[i]];
            const score = ind.score || 0;
            const translatedName = this.#translateIndicator(ind.n);
            const translatedSignal = this.#translateSignal(ind.s);
            let row = `<tr><td>${translatedName}</td>`;
            if (this.enableTableValue) row += `<td class="text-end">${addThousandSeparator(String(ind.v))}</td>`;
            if (this.enableTableScore) row += `<td class="text-end">${score}</td>`;
            if (this.enableTableAction) row += `<td><span class="indicator-action ${getValueStyle(ind.s, "class")}">${translatedSignal}</span></td>`;
            row += `</tr>`;
            rows.push(row);
        }
        tbody.innerHTML = rows.join("");
        table.classList.remove("technical-table-skeleton");
        table.closest(".table-responsive")?.removeAttribute("aria-busy");
    }
    updatePivotsTable(pivots) {
        if (!pivots) return;
        const table = this.root.querySelector(".pivots-table");
        const tbody = table?.querySelector("tbody");
        if (!tbody) return;
        const labels = this.CONFIG.PIVOTS_LABEL;
        const rows = [];
        for (let i = 0; i < labels.length; i++) {
            const label = labels[i];
            const isP = label === "P";
            const wrapStart = isP ? "<strong>" : "";
            const wrapEnd = isP ? "</strong>" : "";
            const classic = addThousandSeparator(String(pivots.classic[label]));
            const fibonacci = addThousandSeparator(String(pivots.fibonacci[label]));
            const camarilla = addThousandSeparator(String(pivots.camarilla[label]));
            const woodie = addThousandSeparator(String(pivots.woodie[label]));
            const demark = addThousandSeparator(String(pivots.demark?.[label]));
            rows.push(`\n                <tr>\n                    <td>${wrapStart}${label}${wrapEnd}</td>\n                    <td class="text-end">${wrapStart}${classic}${wrapEnd}</td>\n                    <td class="text-end">${wrapStart}${fibonacci}${wrapEnd}</td>\n                    <td class="text-end">${wrapStart}${camarilla}${wrapEnd}</td>\n                    <td class="text-end">${wrapStart}${woodie}${wrapEnd}</td>\n                    <td class="text-end">${wrapStart}${demark}${wrapEnd}</td>\n                </tr>\n            `);
        }
        tbody.innerHTML = rows.join("");
        table.classList.remove("technical-ma-skeleton");
        table.closest(".table-responsive")?.removeAttribute("aria-busy");
    }
    #addNeedleAnimations() {
        const needles = this.root.querySelectorAll(".needle");
        if (!needles.length) return;
        needles.forEach(needle => {
            needle.removeEventListener("transitionend", needle._onTransitionEnd);
        });
        let completed = 0;
        const total = needles.length;
        const onTransitionEnd = e => {
            if (e.propertyName === "transform") {
                completed++;
                e.target.removeEventListener("transitionend", onTransitionEnd);
                if (completed === total) {
                    requestAnimationFrame(() => {
                        needles.forEach(needle => {
                            needle.classList.add("animated");
                        });
                    });
                }
            }
        };
        for (let i = 0; i < total; i++) {
            needles[i]._onTransitionEnd = onTransitionEnd;
            needles[i].addEventListener("transitionend", onTransitionEnd);
        }
        setTimeout(() => {
            if (completed < total) {
                needles.forEach(needle => {
                    needle.removeEventListener("transitionend", onTransitionEnd);
                    needle.classList.add("animated");
                });
            }
        }, 1e3);
    }
    removeNeedleAnimations() {
        const needles = this.root.querySelectorAll(".needle");
        for (let i = 0; i < needles.length; i++) {
            needles[i].classList.remove("animated");
        }
    }
    #showLoading() {
        if (this.state.firstLoad) {
            if (this.dom.loadingInitial) {
                this.dom.loadingInitial.style.display = "block";
            }
            if (this.dom.technicalSection) {
                this.dom.technicalSection.classList.add("loading");
            }
        }
    }
    showButtonLoading() {
        if (this.dom.technicalSection) {
            this.dom.technicalSection.classList.add("loading");
        }
    }
    #hideLoading() {
        if (this.dom.loadingInitial) {
            this.dom.loadingInitial.style.display = "none";
        }
        if (this.dom.technicalSection) {
            this.dom.technicalSection.classList.remove("loading");
        }
        this.state.firstLoad = false;
    }
    changePeriod(newPeriod) {
        if (newPeriod === this.period) return;
        this.period = newPeriod;
        this.dom.timeframeBtns.forEach(btn => btn.classList.remove("active"));
        const activeBtn = this.root.querySelector(`.control-btn[data-timeframe="${newPeriod}"]`);
        if (activeBtn) activeBtn.classList.add("active");
        this.removeNeedleAnimations();
        this.showButtonLoading();
        this.#fetchData();
    }
    reload() {
        this.removeNeedleAnimations();
        this.showButtonLoading();
        this.#fetchData();
    }
}

export { TechnicalAnalysis };

if (typeof window !== "undefined") {
    window.TechnicalAnalysis = TechnicalAnalysis;
}