class ForexRates {
    constructor(options = {}) {
        this.root = options.rootNode || document;
        this._sendPostRequest = options.sendPostRequest || (typeof sendPostRequest !== "undefined" ? sendPostRequest : null);
        this._addThousandSeparator = options.addThousandSeparator || (typeof addThousandSeparator !== "undefined" ? addThousandSeparator : null);
        this._initCustomSelects = options.initCustomSelects || (typeof initCustomSelects !== "undefined" ? initCustomSelects : null);
        this._scrollToCenter = options.scrollToCenter || (typeof scrollToCenter !== "undefined" ? scrollToCenter : null);
        this._getLang = options.getLang || (typeof getLang !== "undefined" ? getLang : null);
        this._buildDynamicUrl = options.buildDynamicUrl || (typeof buildDynamicUrl !== "undefined" ? buildDynamicUrl : null);
        this._apiEndpoint = options.apiEndpoint || (typeof API_ENDPOINT !== "undefined" ? API_ENDPOINT : "");
        this.iconPath = options.iconPath || (typeof ICON_PATH !== "undefined" ? ICON_PATH : "");
        this.iconType = options.iconType || (typeof ICON_TYPE !== "undefined" ? ICON_TYPE : ".svg");
        this.webSocketInstance = options.webSocketInstance || (typeof webSocketService !== "undefined" ? webSocketService : null);
        this.CONFIG = {
            EXCHANGE: "FX",
            PERIOD: "1D",
            DISPLAY_MODE: "c",
            DEFAULT_PRESET: "overview",
            SHOW_PRESET_BUTTONS: true,
            BUILD_PRESET_BUTTONS: false,
            ENABLE_STORAGE: true,
            enableLinks: true,
            enableUrlUpdates: true,
            linkTarget: "_self",
            linkUrl: "",
            maxColumnsThreshold: 12,
            largeTableClass: "cross-rates-large-table",
            PRESETS: {},
            OBSERVER_THRESHOLD: .1,
            OBSERVER_ROOT_MARGIN: "100px",
            heatmapThresholds: [ .5, 1, 2, 3 ],
            heatmapClasses: {
                positive: [ "heatmap-positive-1", "heatmap-positive-2", "heatmap-positive-3", "heatmap-positive-4", "heatmap-positive-5" ],
                negative: [ "heatmap-negative-1", "heatmap-negative-2", "heatmap-negative-3", "heatmap-negative-4", "heatmap-negative-5" ],
                neutral: "heatmap-neutral"
            },
            ...options
        };
        if (!this.CONFIG.linkUrl) {
            this.CONFIG.linkUrl = `BASE_URL/symbols/{{symbol}}?exchange=FX`;
        }
        this.state = {
            selectedCurrencies: [],
            currentPreset: null,
            rates: {},
            currencyData: {},
            tickerMap: {},
            currenciesLoaded: false
        };
        this.STORAGE_KEY = `cross_rates_selection_${this.CONFIG.DISPLAY_MODE}`;
        this.els = {
            table: null,
            thead: null,
            tbody: null,
            tableWrapper: null
        };
        this.observer = null;
        this.#init();
    }
    async #init() {
        this.#cacheElements();
        this.#loadSavedSelection();
        if (this.CONFIG.SHOW_PRESET_BUTTONS && this.CONFIG.BUILD_PRESET_BUTTONS) {
            this.#buildPresetButtons();
        } else {
            this.#attachPresetButtonListeners();
        }
        this.#scrollToActiveButton();
        await this.#waitForLang();
        this.#buildCurrencySelectPlaceholder();
        this.#initTimePeriodSelector();
        this.#setupObserver();
    }
    #waitForLang() {
        return new Promise(resolve => {
            if (window.LANG_LOADED) {
                resolve();
                return;
            }
            window.addEventListener("langLoaded", () => resolve(), {
                once: true
            });
            setTimeout(resolve, 500);
        });
    }
    #setupObserver() {
        if (!this.els.tableWrapper) return;
        this.observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.#loadCrossRates();
                    this.observer.disconnect();
                }
            });
        }, {
            threshold: this.CONFIG.OBSERVER_THRESHOLD,
            rootMargin: this.CONFIG.OBSERVER_ROOT_MARGIN
        });
        this.observer.observe(this.els.tableWrapper);
    }
    #$(selector) {
        return this.root.querySelector(selector);
    }
    #$$(selector) {
        return this.root.querySelectorAll(selector);
    }
    #cacheElements() {
        this.els.table = this.#$(".cross-rates-table");
        this.els.initialLoading = this.#$("#crossRatesInitialLoading");
        this.els.tableWrapper = this.#$("#crossRatesTableWrapper");
    }
    #loadSavedSelection() {
        if (this.CONFIG.ENABLE_STORAGE && typeof STORAGE !== "undefined") {
            const saved = STORAGE.get(this.STORAGE_KEY);
            if (saved?.currencies?.length > 0) {
                this.state.selectedCurrencies = saved.currencies;
                this.state.currentPreset = saved.preset || null;
                if (saved.period) {
                    this.CONFIG.PERIOD = saved.period;
                    this.#syncTimePeriodUI(saved.period);
                }
                if (this.state.currentPreset && this.CONFIG.PRESETS[this.state.currentPreset]) {
                    this.#activatePresetButton(this.state.currentPreset);
                } else {
                    this.#deactivateAllPresetButtons();
                }
                return;
            }
        }
        const activePreset = this.#detectActivePresetFromURL();
        if (activePreset) {
            const preset = this.CONFIG.PRESETS[activePreset];
            if (preset) {
                this.state.selectedCurrencies = [ ...preset.currencies ];
                this.state.currentPreset = activePreset;
                this.#activatePresetButton(activePreset);
                return;
            }
        }
        const preset = this.CONFIG.PRESETS[this.CONFIG.DEFAULT_PRESET];
        if (preset) {
            this.state.selectedCurrencies = [ ...preset.currencies ];
            this.state.currentPreset = this.CONFIG.DEFAULT_PRESET;
            if (preset.exchange) this.CONFIG.EXCHANGE = preset.exchange;
            this.#activatePresetButton(this.CONFIG.DEFAULT_PRESET);
        }
    }
    #detectActivePresetFromURL() {
        const currentURL = window.location.href;
        const buttons = this.#$$(".category-tab[href]");
        for (let i = 0; i < buttons.length; i++) {
            const button = buttons[i];
            if (button.href && currentURL === button.href) {
                return button.dataset.preset;
            }
        }
        return null;
    }
    #saveSelection() {
        if (!this.CONFIG.ENABLE_STORAGE || typeof STORAGE === "undefined") return;
        const data = {
            currencies: this.state.selectedCurrencies,
            preset: this.state.currentPreset,
            period: this.CONFIG.PERIOD,
            timestamp: Date.now()
        };
        STORAGE.set(this.STORAGE_KEY, data);
    }
    #buildPresetButtons() {
        const container = this.#$("#currencyPresetsContainer");
        if (!container) return;
        const wrapper = document.createElement("div");
        wrapper.className = "category-tabs";
        const presets = Object.entries(this.CONFIG.PRESETS);
        for (let i = 0; i < presets.length; i++) {
            const [key, preset] = presets[i];
            const btn = this.#createPresetButton(key, preset);
            wrapper.appendChild(btn);
        }
        container.innerHTML = "";
        container.appendChild(wrapper);
    }
    #attachPresetButtonListeners() {
        const buttons = this.#$$(".category-tab");
        const wrapper = this.#$(".category-tabs-container");
        for (let i = 0; i < buttons.length; i++) {
            const button = buttons[i];
            const presetKey = button.dataset.preset;
            if (!presetKey) continue;
            button.addEventListener("click", e => {
                e.preventDefault();
                if (wrapper && this._scrollToCenter) {
                    this._scrollToCenter(button, wrapper);
                }
                this.#setPreset(presetKey);
                if (this.CONFIG.enableUrlUpdates && button.tagName === "A" && button.href) {
                    window.history.pushState({}, "", button.href);
                }
            });
        }
    }
    #createPresetButton(key, preset) {
        const btn = document.createElement("button");
        btn.className = "category-tab";
        btn.dataset.preset = key;
        if (this.state.currentPreset === key) {
            btn.classList.add("active");
        }
        btn.textContent = preset.label;
        const wrapper = this.#$(".category-tabs-container");
        btn.addEventListener("click", e => {
            e.preventDefault();
            if (wrapper && this._scrollToCenter) {
                this._scrollToCenter(btn, wrapper);
            }
            this.#setPreset(key);
        });
        return btn;
    }
    #setPreset(presetKey) {
        const preset = this.CONFIG.PRESETS[presetKey];
        if (!preset) return;
        this.state.currentPreset = presetKey;
        this.state.selectedCurrencies = [ ...preset.currencies ];
        if (preset.exchange) {
            this.CONFIG.EXCHANGE = preset.exchange;
        }
        this.#activatePresetButton(presetKey);
        this.#syncCustomSelectWithPreset();
        this.#updateSelectCount();
        this.#saveSelection();
        this.#loadCrossRates();
    }
    #activatePresetButton(presetKey) {
        const buttons = this.#$$(".category-tab");
        for (let i = 0; i < buttons.length; i++) {
            const btn = buttons[i];
            btn.classList.toggle("active", btn.dataset.preset === presetKey);
        }
    }
    #deactivateAllPresetButtons() {
        const buttons = this.#$$(".category-tab");
        for (let i = 0; i < buttons.length; i++) {
            buttons[i].classList.remove("active");
        }
    }
    #scrollToActiveButton() {
        const wrapper = this.#$(".category-tabs-container");
        const activeButton = this.#$(".category-tab.active");
        if (wrapper && activeButton && this._scrollToCenter) {
            this._scrollToCenter(activeButton, wrapper);
        }
    }
    #buildCurrencySelectPlaceholder() {
        const container = this.#$("#currencySelectContainer");
        if (!container) return;
        let placeholderOptions = "";
        for (let i = 0; i < this.state.selectedCurrencies.length; i++) {
            const code = this.state.selectedCurrencies[i];
            placeholderOptions += `<li class="select-option selected" data-value="${code}">${code}</li>`;
        }
        const selectCurrenciesText = this._getLang ? this._getLang("forex_rates.select_currencies", "Select Currencies") : "Select Currencies";
        const searchCurrenciesText = this._getLang ? this._getLang("forex_rates.search_currencies", "Search currencies...") : "Search currencies...";
        container.innerHTML = `\n            <div class="dropdown-select bg-light-ui" id="currencyMultiSelect" data-multiple="true" data-search="true" data-default-text="${selectCurrenciesText}:" data-search-placeholder="${searchCurrenciesText}">\n                <div class="select-trigger">\n                    <span class="select-trigger-text">${selectCurrenciesText}</span>\n                    <svg width="18" height="18" fill="none" stroke="currentColor" class="select-arrow">\n                        <use href="#chevron-down"></use>\n                    </svg>\n                </div>\n                <div class="select-dropdown">\n                    <div class="menu-search-result-initial-loading py-5">\n                        <div class="loading-spinner-result"></div>\n                    </div>\n                    <ul class="select-options d-none">${placeholderOptions}</ul>\n                </div>\n            </div>\n        `;
        if (this._initCustomSelects) {
            this._initCustomSelects("#currencySelectContainer", this.root);
        }
        this.#setupLazySelectLoading();
    }
    #setupLazySelectLoading() {
        const selectElement = this.#$("#currencyMultiSelect");
        if (!selectElement) return;
        const selectTrigger = selectElement.querySelector(".select-trigger");
        if (!selectTrigger) return;
        selectTrigger.addEventListener("click", async () => {
            if (!this.state.currenciesLoaded) {
                const willBeOpen = !selectElement.classList.contains("active");
                await this.#loadCurrenciesList(willBeOpen);
            }
        });
    }
    async #loadCurrenciesList(shouldStayOpen = false) {
        if (this.state.currenciesLoaded) return;
        try {
            const response = await this._sendPostRequest(`${this._apiEndpoint}forex/list`, {
                type: "forex",
                exchange: this.CONFIG.EXCHANGE,
                merge: "profile,meta",
                columns: "profile.currency,profile.base_logo,profile.curr_logo,meta.base_currency_desc,meta.currency_desc,meta.base_currency"
            });
            if (response?.status && response.response) {
                const currencies = this.#processCurrenciesData(response.response);
                this.state.currenciesLoaded = true;
                const selectElement = this.#$("#currencyMultiSelect");
                const loadingSpinner = selectElement?.querySelector(".menu-search-result-initial-loading");
                const optionsList = selectElement?.querySelector(".select-options");
                if (loadingSpinner) loadingSpinner.classList.add("d-none");
                if (optionsList) optionsList.classList.remove("d-none");
                this.#buildCurrencySelect(currencies, shouldStayOpen);
            }
        } catch (error) {}
    }
    #processCurrenciesData(data) {
        const currencies = {};
        const tickerMap = {};
        for (let i = 0; i < data.length; i++) {
            const item = data[i];
            const {profile: profile, meta: meta, ticker: ticker} = item;
            if (!profile || !meta) continue;
            const symbol = profile.symbol || "";
            if (symbol && ticker) {
                tickerMap[symbol] = ticker;
            }
            if (meta.base_currency && !currencies[meta.base_currency]) {
                currencies[meta.base_currency] = {
                    code: meta.base_currency,
                    logo: profile.base_logo || "",
                    desc: meta.base_currency_desc || meta.base_currency
                };
            }
            if (profile.currency && !currencies[profile.currency]) {
                currencies[profile.currency] = {
                    code: profile.currency,
                    logo: profile.curr_logo || "",
                    desc: meta.currency_desc || profile.currency
                };
            }
        }
        this.state.tickerMap = tickerMap;
        this.state.currencyData = currencies;
        return currencies;
    }
    #buildCurrencySelect(currencies, shouldStayOpen = false) {
        const container = this.#$("#currencySelectContainer");
        if (!container) return;
        const codes = Object.keys(currencies).sort();
        const options = [];
        for (let i = 0; i < codes.length; i++) {
            const code = codes[i];
            const currency = currencies[code];
            const isSelected = this.state.selectedCurrencies.includes(code);
            const li = document.createElement("li");
            li.className = "select-option" + (isSelected ? " selected" : "");
            li.dataset.value = code;
            li.innerHTML = `\n                <span class="select-option-img">\n                    <div class="flag-icon-container">\n                        <img data-src="${this.iconPath}${currency.logo}${this.iconType}" alt="${currency.desc}" class="flag-img">\n                    </div>\n                    ${currency.desc}\n                </span>\n            `;
            options.push(li);
        }
        const selectCurrenciesText = this._getLang ? this._getLang("forex_rates.select_currencies", "Select Currencies") : "Select Currencies";
        const searchCurrenciesText = this._getLang ? this._getLang("forex_rates.search_currencies", "Search currencies...") : "Search currencies...";
        const selectHTML = `\n            <div class="dropdown-select bg-light-ui" id="currencyMultiSelect" data-multiple="true" data-search="true" data-default-text="${selectCurrenciesText}:" data-search-placeholder="${searchCurrenciesText}">\n                <div class="select-trigger">\n                    <span class="select-trigger-text">${selectCurrenciesText}</span>\n                    <svg width="18" height="18" fill="none" stroke="currentColor" class="select-arrow">\n                        <use href="#chevron-down"></use>\n                    </svg>\n                </div>\n                <div class="select-dropdown">\n                    <ul class="select-options"></ul>\n                </div>\n            </div>\n        `;
        container.innerHTML = selectHTML;
        const optionsList = container.querySelector(".select-options");
        for (let i = 0; i < options.length; i++) {
            optionsList.appendChild(options[i]);
        }
        if (this._initCustomSelects) {
            this._initCustomSelects("#currencySelectContainer", this.root);
        }
        const selectElement = this.#$("#currencyMultiSelect");
        if (selectElement) {
            requestAnimationFrame(() => {
                if (selectElement.customSelectAPI && this.state.selectedCurrencies.length > 0) {
                    selectElement.customSelectAPI.setValue(this.state.selectedCurrencies);
                }
            });
            selectElement.addEventListener("customSelectChange", e => {
                this.state.selectedCurrencies = e.detail.values || [];
                this.#unsetPreset();
                this.#saveSelection();
                this.#loadCrossRates();
            });
            if (shouldStayOpen && selectElement.customSelectAPI && typeof selectElement.customSelectAPI.open === "function") {
                requestAnimationFrame(() => {
                    selectElement.customSelectAPI.open();
                });
            }
        }
    }
    #syncCustomSelectWithPreset() {
        const selectElement = this.#$("#currencyMultiSelect");
        if (!selectElement) return;
        if (!this.state.currenciesLoaded) {
            const optionsList = selectElement.querySelector(".select-options");
            if (optionsList) {
                let placeholderOptions = "";
                for (let i = 0; i < this.state.selectedCurrencies.length; i++) {
                    const code = this.state.selectedCurrencies[i];
                    placeholderOptions += `<li class="select-option selected" data-value="${code}">${code}</li>`;
                }
                optionsList.innerHTML = placeholderOptions;
            }
            if (this._initCustomSelects) {
                this._initCustomSelects("#currencySelectContainer", this.root);
            }
        } else {
            requestAnimationFrame(() => {
                if (selectElement.customSelectAPI && this.state.selectedCurrencies.length > 0) {
                    selectElement.customSelectAPI.setValue(this.state.selectedCurrencies);
                }
            });
        }
    }
    #updateSelectCount() {
        const countElement = this.#$(".select-trigger-text-count");
        if (countElement) {
            countElement.textContent = this.state.selectedCurrencies.length;
        }
    }
    #unsetPreset() {
        const currentSorted = JSON.stringify([ ...this.state.selectedCurrencies ].sort());
        const presets = Object.entries(this.CONFIG.PRESETS);
        for (let i = 0; i < presets.length; i++) {
            const [presetKey, preset] = presets[i];
            const presetSorted = JSON.stringify([ ...preset.currencies ].sort());
            if (currentSorted === presetSorted) {
                this.state.currentPreset = presetKey;
                this.#activatePresetButton(presetKey);
                return;
            }
        }
        const buttons = this.#$$(".category-tab");
        for (let i = 0; i < buttons.length; i++) {
            buttons[i].classList.remove("active");
        }
        this.state.currentPreset = null;
    }
    #rebuildTable() {
        if (!this.els.table) return;
        let thead = this.els.table.querySelector("thead");
        let tbody = this.els.table.querySelector("tbody");
        if (!thead) {
            thead = document.createElement("thead");
            this.els.table.appendChild(thead);
        }
        if (!tbody) {
            tbody = document.createElement("tbody");
            tbody.id = "crossRatesBody";
            this.els.table.appendChild(tbody);
        }
        this.els.thead = thead;
        this.els.tbody = tbody;
        const headerRow = document.createElement("tr");
        const emptyTh = document.createElement("th");
        emptyTh.className = "row-sticky";
        headerRow.appendChild(emptyTh);
        for (let i = 0; i < this.state.selectedCurrencies.length; i++) {
            const code = this.state.selectedCurrencies[i];
            const th = this.#createHeaderCell(code);
            headerRow.appendChild(th);
        }
        thead.innerHTML = "";
        thead.appendChild(headerRow);
        tbody.innerHTML = "";
        for (let i = 0; i < this.state.selectedCurrencies.length; i++) {
            const rowCurrency = this.state.selectedCurrencies[i];
            const tr = this.#createTableRow(rowCurrency);
            tbody.appendChild(tr);
        }
        this.#applyLargeTableClass();
    }
    #createHeaderCell(code) {
        const th = document.createElement("th");
        const logoPath = this.#getCurrencyLogoPath(code);
        const currencyInfo = this.state.currencyData[code];
        const desc = currencyInfo?.desc || code;
        th.setAttribute("data-tooltip-title", desc);
        th.setAttribute("data-tooltip-trigger", "click");
        th.innerHTML = `\n            <div class="cross-rates-currency-header">\n                <div class="search-result-icon-container">\n                    <img data-src="${this.iconPath}${logoPath}${this.iconType}" alt="${code}" class="flag-img lazy-init">\n                </div>\n                <span class="currency-label">${code}</span>\n            </div>\n        `;
        return th;
    }
    #createTableRow(rowCurrency) {
        const tr = document.createElement("tr");
        tr.dataset.currency = rowCurrency;
        const logoPath = this.#getCurrencyLogoPath(rowCurrency);
        const stickyTd = document.createElement("td");
        stickyTd.className = "row-sticky";
        stickyTd.innerHTML = `\n            <div class="cross-rates-currency-header">\n                <div class="search-result-icon-container">\n                    <img data-src="${this.iconPath}${logoPath}${this.iconType}" alt="${rowCurrency}" class="flag-img lazy-init">\n                </div>\n                <span class="currency-label">${rowCurrency}</span>\n            </div>\n        `;
        tr.appendChild(stickyTd);
        for (let i = 0; i < this.state.selectedCurrencies.length; i++) {
            const colCurrency = this.state.selectedCurrencies[i];
            const td = this.#createRateCell(rowCurrency, colCurrency);
            tr.appendChild(td);
        }
        return tr;
    }
    #createRateCell(rowCurrency, colCurrency) {
        const td = document.createElement("td");
        td.className = "rate-cell";
        if (rowCurrency === colCurrency) {
            td.classList.add("self");
            return td;
        }
        const symbol = rowCurrency + colCurrency;
        const ticker = this.state.tickerMap[symbol] || `${this.CONFIG.EXCHANGE}:${symbol}`;
        td.dataset.ticker = ticker;
        td.dataset.timeperiod = this.CONFIG.PERIOD;
        td.dataset.rowCurrency = rowCurrency;
        td.dataset.colCurrency = colCurrency;
        const existingRate = this.state.rates[symbol];
        const rateDiv = document.createElement("div");
        rateDiv.className = "rate-value";
        const dynamicClass = this.#generateRateValueClass(ticker, this.CONFIG.DISPLAY_MODE, this.CONFIG.PERIOD);
        if (existingRate) {
            const rateDisplay = this._addThousandSeparator(existingRate.rate);
            if (this.CONFIG.enableLinks && this._buildDynamicUrl) {
                const fromName = this.state.currencyData[rowCurrency]?.desc || rowCurrency;
                const toName = this.state.currencyData[colCurrency]?.desc || colCurrency;
                const url = this._buildDynamicUrl(this.CONFIG.linkUrl, {
                    symbol: symbol,
                    name: fromName + "-" + toName,
                    exchange: this.CONFIG.EXCHANGE,
                    from: rowCurrency,
                    to: colCurrency
                });
                const link = document.createElement("a");
                link.href = url;
                if (this.CONFIG.linkTarget === "_blank") {
                    link.target = "_blank";
                    link.rel = "noopener noreferrer";
                }
                link.textContent = rateDisplay;
                link.classList.add(dynamicClass);
                rateDiv.appendChild(link);
            } else {
                rateDiv.textContent = rateDisplay;
                rateDiv.classList.add(dynamicClass);
            }
        } else {
            if (!this.CONFIG.enableLinks) {
                rateDiv.classList.add(dynamicClass);
            }
        }
        td.appendChild(rateDiv);
        td.addEventListener("mouseenter", () => this.#highlightCellReferences(rowCurrency, colCurrency));
        td.addEventListener("mouseleave", () => this.#unhighlightCellReferences());
        return td;
    }
    #generateRateValueClass(ticker, valueType, period) {
        const sanitizedTicker = ticker.replace(/[^a-zA-Z0-9]/g, "_");
        return `${sanitizedTicker}_${valueType}_${period}`.toUpperCase();
    }
    #getCurrencyLogoPath(currencyCode) {
        const currencyData = this.state.currencyData[currencyCode];
        return currencyData?.logo || "country/" + (currencyCode || "").substring(0, 2).toUpperCase();
    }
    #highlightCellReferences(rowCurrency, colCurrency) {
        const colIndex = this.state.selectedCurrencies.indexOf(colCurrency);
        if (colIndex !== -1) {
            const headerCells = this.els.thead.querySelectorAll("th");
            if (headerCells[colIndex + 1]) {
                headerCells[colIndex + 1].classList.add("highlight");
            }
        }
        const rows = this.els.tbody.querySelectorAll("tr");
        for (let i = 0; i < rows.length; i++) {
            if (rows[i].dataset.currency === rowCurrency) {
                const rowLabel = rows[i].querySelector(".row-sticky");
                if (rowLabel) {
                    rowLabel.classList.add("highlight");
                }
                break;
            }
        }
    }
    #unhighlightCellReferences() {
        const highlightedHeaders = this.els.thead.querySelectorAll("th.highlight");
        for (let i = 0; i < highlightedHeaders.length; i++) {
            highlightedHeaders[i].classList.remove("highlight");
        }
        const highlightedRows = this.els.tbody.querySelectorAll(".row-sticky.highlight");
        for (let i = 0; i < highlightedRows.length; i++) {
            highlightedRows[i].classList.remove("highlight");
        }
    }
    #applyLargeTableClass() {
        if (!this.els.table || !this.els.thead) return;
        const thCount = this.els.thead.querySelectorAll("th").length;
        if (thCount >= this.CONFIG.maxColumnsThreshold) {
            this.els.table.classList.add(this.CONFIG.largeTableClass);
        } else {
            this.els.table.classList.remove(this.CONFIG.largeTableClass);
        }
    }
    async #loadCrossRates() {
        if (!this.state.selectedCurrencies || this.state.selectedCurrencies.length < 2) {
            return;
        }
        const currKey = (this.CONFIG.EXCHANGE || "") + ":" + this.state.selectedCurrencies.join(",");
        const needsRebuild = currKey !== this._lastCurrKey;
        try {
            this.#blurTable(true);
            const rates = await this.#fetchRates();
            if (needsRebuild) {
                this._lastCurrKey = currKey;
                this.#rebuildTable();
            }
            if (rates && Object.keys(rates).length > 0) {
                this.state.rates = rates;
                this.#updateTable(rates);
            } else {
                this.state.rates = {};
                this.#updateTable({});
            }
        } catch (error) {
            if (needsRebuild) {
                this._lastCurrKey = currKey;
                this.#rebuildTable();
            }
            this.state.rates = {};
            this.#updateTable({});
        } finally {
            this.#blurTable(false);
            this.#delayedInit();
        }
    }
    async #fetchRates() {
        try {
            const currencies = this.state.selectedCurrencies.join(",");
            const response = await this._sendPostRequest(`${this._apiEndpoint}forex/cross`, {
                symbol: currencies,
                type: "forex",
                crossrates: 1,
                exchange: this.CONFIG.EXCHANGE,
                period: this.CONFIG.PERIOD
            });
            if (response?.status && response.response) {
                const profiles = response.info?.profiles || {};
                return this.#processRatesResponse(response.response, profiles);
            }
            throw new Error("Invalid response");
        } catch (error) {
            return null;
        }
    }
    #processRatesResponse(data, profiles = {}) {
        const rates = {};
        const profileKeys = Object.keys(profiles);
        for (let i = 0; i < profileKeys.length; i++) {
            const currencyCode = profileKeys[i];
            const profileArray = profiles[currencyCode];
            if (profileArray && profileArray.length > 0 && !this.state.currencyData[currencyCode]) {
                const profile = profileArray[0];
                this.state.currencyData[currencyCode] = {
                    code: currencyCode,
                    logo: profile.base_logo || "",
                    desc: profile.base_currency_desc || currencyCode
                };
            }
        }
        const baseCurrencies = Object.keys(data);
        for (let i = 0; i < baseCurrencies.length; i++) {
            const baseCurrency = baseCurrencies[i];
            const pairs = data[baseCurrency];
            if (!pairs || typeof pairs !== "object") continue;
            const symbols = Object.keys(pairs);
            for (let j = 0; j < symbols.length; j++) {
                const symbol = symbols[j];
                const pairData = pairs[symbol];
                const ticker = `${this.CONFIG.EXCHANGE}:${symbol}`;
                this.state.tickerMap[symbol] = ticker;
                rates[symbol] = {
                    c: pairData.c || 0,
                    chp: pairData.chp || 0,
                    ticker: ticker,
                    symbol: pairData.s || symbol
                };
            }
        }
        return rates;
    }
    #updateTable(rates) {
        if (!rates || Object.keys(rates).length === 0) {
            const cells = this.#$$(".rate-cell:not(.self) .rate-value");
            for (let i = 0; i < cells.length; i++) {
                cells[i].textContent = "—";
                cells[i].classList.remove("loading");
            }
            return;
        }
        let updatedCells = 0;
        const symbols = Object.keys(rates);
        const mode = this.CONFIG.DISPLAY_MODE;
        const period = this.CONFIG.PERIOD;
        for (let i = 0; i < symbols.length; i++) {
            const symbol = symbols[i];
            const rateData = rates[symbol];
            const {ticker: ticker} = rateData;
            const cell = this.#$(`[data-ticker="${ticker}"]`);
            if (!cell) continue;
            cell.setAttribute("data-timeperiod", period);
            const rateValue = cell.querySelector(".rate-value");
            if (!rateValue) continue;
            const value = rateData[mode] || 0;
            let rateDisplay;
            if (mode === "chp") {
                const sign = value > 0 ? "+" : value < 0 ? "−" : "";
                rateDisplay = `<span class="sign">${sign}</span>${this._addThousandSeparator(parseFloat(Math.abs(value)).toFixed(2))}%`;
            } else {
                rateDisplay = this._addThousandSeparator(value);
            }
            const dynamicClass = this.#generateRateValueClass(ticker, mode, period);
            let targetElement;
            if (this.CONFIG.enableLinks && this._buildDynamicUrl) {
                const rowCur = cell.dataset.rowCurrency || "";
                const colCur = cell.dataset.colCurrency || "";
                const fromName = this.state.currencyData[rowCur]?.desc || rowCur;
                const toName = this.state.currencyData[colCur]?.desc || colCur;
                const url = this._buildDynamicUrl(this.CONFIG.linkUrl, {
                    symbol: symbol,
                    name: fromName + "-" + toName,
                    exchange: this.CONFIG.EXCHANGE,
                    from: rowCur,
                    to: colCur
                });
                const existingLink = rateValue.querySelector("a");
                if (existingLink) {
                    existingLink.innerHTML = rateDisplay;
                    existingLink.href = url;
                    this.#updateDynamicClass(existingLink, dynamicClass);
                    targetElement = existingLink;
                } else {
                    const link = document.createElement("a");
                    link.href = url;
                    if (this.CONFIG.linkTarget === "_blank") {
                        link.target = "_blank";
                        link.rel = "noopener noreferrer";
                    }
                    link.innerHTML = rateDisplay;
                    link.classList.add(dynamicClass);
                    rateValue.innerHTML = "";
                    rateValue.appendChild(link);
                    targetElement = link;
                }
            } else {
                rateValue.innerHTML = rateDisplay;
                this.#updateDynamicClass(rateValue, dynamicClass);
                targetElement = rateValue;
            }
            rateValue.classList.remove("loading");
            updatedCells++;
            if (mode === "chp" && targetElement) {
                const chpValue = rateData.chp || 0;
                this.#removeHeatmapClasses(targetElement);
                const heatmapClass = this.#getHeatmapClass(chpValue);
                targetElement.classList.add(heatmapClass);
            }
            cell.classList.add("flash-update");
            setTimeout(() => cell.classList.remove("flash-update"), 500);
        }
        const allCells = this.#$$(".rate-cell[data-ticker]");
        for (let i = 0; i < allCells.length; i++) {
            const cell = allCells[i];
            const rateValue = cell.querySelector(".rate-value");
            if (rateValue && !rateValue.textContent.trim()) {
                rateValue.textContent = "—";
            }
        }
    }
    #updateDynamicClass(element, newClass) {
        const classes = [ ...element.classList ];
        for (let i = 0; i < classes.length; i++) {
            if (classes[i].startsWith("FX:") || classes[i].includes("_")) {
                element.classList.remove(classes[i]);
            }
        }
        element.classList.add(newClass);
    }
    #getHeatmapClass(changePercent) {
        const absChange = Math.abs(changePercent);
        const thresholds = this.CONFIG.heatmapThresholds;
        const {positive: positive, negative: negative, neutral: neutral} = this.CONFIG.heatmapClasses;
        if (absChange < .01) {
            return neutral;
        }
        let level = 0;
        for (let i = 0; i < thresholds.length; i++) {
            if (absChange >= thresholds[i]) {
                level = i + 1;
            }
        }
        level = Math.min(level, 4);
        if (changePercent > 0) {
            return positive[level];
        } else {
            return negative[level];
        }
    }
    #removeHeatmapClasses(element) {
        const {positive: positive, negative: negative, neutral: neutral} = this.CONFIG.heatmapClasses;
        const allClasses = [ ...positive, ...negative, neutral ];
        for (let i = 0; i < allClasses.length; i++) {
            element.classList.remove(allClasses[i]);
        }
    }
    #blurTable(blur) {
        const table = this.#$("#crossRatesTableWrapper .fx-table");
        if (!table) return;
        table.classList.toggle("loading", blur);
    }
    #delayedInit() {
        requestAnimationFrame(() => {
            if (typeof initializeTableScrollShadows === "function") {
                initializeTableScrollShadows(".fx-table-scroll-shadow", this.root);
            }
            this.#subscribeToWebSocket();
        });
    }
    #subscribeToWebSocket() {
        if (this.webSocketInstance && typeof this.webSocketInstance.subscribeAllSymbols === "function") {
            this.webSocketInstance.subscribeAllSymbols();
        }
    }
    #initTimePeriodSelector() {
        const timePeriodSelect = this.#$("#timePeriodSelect");
        if (!timePeriodSelect) return;
        if (this._initCustomSelects) {
            this._initCustomSelects("#timePeriodSelect", this.root);
        }
        timePeriodSelect.addEventListener("click", e => {
            const option = e.target.closest(".select-option");
            if (!option) return;
            const value = option.dataset.value;
            if (!value || value.toUpperCase() === this.CONFIG.PERIOD) return;
            this.CONFIG.PERIOD = value.toUpperCase();
            const triggerText = timePeriodSelect.querySelector(".select-trigger-text");
            if (triggerText) {
                triggerText.textContent = option.textContent;
            }
            const allOptions = timePeriodSelect.querySelectorAll(".select-option");
            allOptions.forEach(opt => opt.classList.remove("selected"));
            option.classList.add("selected");
            this.#saveSelection();
            this.#loadCrossRates();
        });
    }
    #syncTimePeriodUI(period) {
        const select = this.#$("#timePeriodSelect");
        if (!select) return;
        const options = select.querySelectorAll(".select-option");
        options.forEach(opt => {
            opt.classList.toggle("selected", opt.dataset.value?.toUpperCase() === period);
        });
        const triggerText = select.querySelector(".select-trigger-text");
        if (triggerText) {
            const active = select.querySelector(".select-option.selected");
            if (active) triggerText.textContent = active.textContent;
        }
    }
}

export { ForexRates };

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