import { getMetrics, getPresets } from "../data/screener-data.js";

import { DynamicFilterManager } from "./filter-manager.js";

class Screener {
    constructor(options = {}) {
        this.rootNode = options.rootNode || document;
        this.METRICS = getMetrics();
        this.CONFIG = {
            SEARCH_DEBOUNCE: 300,
            API_TIMEOUT: 1e4,
            SCROLL_ANIMATION: 300,
            PER_PAGE: 50,
            MIN_COLUMNS: 3,
            MAX_COLUMNS: 30,
            SCROLL_OFFSET: 150,
            DEFAULT_TIME_PERIOD: "1D",
            DEFAULT_PRESET: "overview",
            DEFAULT_COUNTRY: "US",
            ENABLE_FILTER_MANAGER: true,
            SHOW_FAVORITES_ADD_REMOVE: true,
            ENABLE_COLOR_GROUPS: true,
            SHOW_COLUMNS_BUTTON: true,
            DEFAULT_IS_PRIMARY: 1,
            ENABLE_STORAGE: true,
            ENABLE_INFINITE_SCROLL: false,
            ENABLE_SORT: true,
            OBSERVER_THRESHOLD: .1,
            OBSERVER_ROOT_MARGIN: "100px",
            SHOW_TICKER: true,
            SHOW_DESCRIPTION: true,
            SHOW_LOGO: true,
            SHOW_CURRENCY: true,
            COLORS: {
                "red": "#EF4444",
                "blue": "#3B82F6",
                "green": "#10B981",
                "orange": "#F7931A",
                "purple": "#8B5CF6",
                "cyan": "#26d0ce"
            },
            API_BASE_PATH: "crypto",
            CURRENT_ASSET_TYPE: typeof asset_type !== "undefined" ? asset_type : "crypto",
            enableLinks: true,
            linkUrl: null,
            linkType: null,
            linkTarget: "_self",
            ...options.config
        };
        this.PRESETS = this.CONFIG.CUSTOM_PRESETS || getPresets(this.CONFIG.CURRENT_ASSET_TYPE, this.rootNode);
        this.state = {
            selectedMetrics: [],
            entriesPerPage: this.CONFIG.PER_PAGE,
            favorites: {},
            currentSymbol: null,
            cachedApiData: null,
            currentSearchTerm: "",
            searchTimeout: null,
            currentSort: {
                column: null,
                direction: null
            },
            headerFilterActive: false,
            headerCurrentColor: null,
            currentFilter: "all",
            currentTimePeriod: this.CONFIG.DEFAULT_TIME_PERIOD,
            isInitialLoad: true,
            currentPage: 1,
            currentPaginationInfo: null,
            currentApiController: null,
            infiniteScrollObserver: null,
            isLoadingMore: false,
            isUserCustomizing: false,
            activeFilters: {},
            otherParams: {},
            storageKey: typeof storageKey !== "undefined" ? storageKey : "",
            favStorageKey: typeof favStorageKey !== "undefined" ? favStorageKey : "watchlist",
            ...options.state
        };
        this.filterManager = null;
        this.dom = {};
        this.#init();
    }
    #q(sel) {
        return this.rootNode.querySelector(sel);
    }
    #qAll(sel) {
        return this.rootNode.querySelectorAll(sel);
    }
    #qId(id) {
        return this.rootNode.querySelector("#" + id);
    }
    #getStorage(key) {
        if (!this.CONFIG.ENABLE_STORAGE) {
            return null;
        }
        return STORAGE.get(key);
    }
    #setStorage(key, value) {
        if (!this.CONFIG.ENABLE_STORAGE) {
            return false;
        }
        STORAGE.set(key, value);
        return true;
    }
    getMetrics(forType = "both") {
        const metrics = {};
        for (const [key, config] of Object.entries(this.METRICS)) {
            if (config.assets && !config.assets.includes(this.CONFIG.CURRENT_ASSET_TYPE)) {
                continue;
            }
            if (forType === "filter") {
                if (config.exclude_from_filter) {
                    continue;
                }
            }
            if (forType === "column") {
                if (config.exclude_from_column) {
                    continue;
                }
            }
            metrics[key] = {
                ...config,
                key: key
            };
        }
        return metrics;
    }
    getMetricsByCategory(forType = "both") {
        const metrics = this.getMetrics(forType);
        const grouped = {};
        for (const [key, metric] of Object.entries(metrics)) {
            const category = metric.category || "others";
            if (!grouped[category]) {
                grouped[category] = {};
            }
            grouped[category][key] = metric;
        }
        return grouped;
    }
    #generateColumnsHTML() {
        const metricsByCategory = this.getMetricsByCategory("column");
        let html = "";
        let othersHTML = "";
        for (const [category, categoryMetrics] of Object.entries(metricsByCategory)) {
            let categoryHTML = '<div class="metric-category">';
            categoryHTML += `<h6 class="category-title">${this.#formatCategoryName(category)}</h6>`;
            categoryHTML += '<div class="category-metrics">';
            for (const [key, metric] of Object.entries(categoryMetrics)) {
                const tooltip = metric.tooltip ? ` data-search="${metric.tooltip}"` : "";
                categoryHTML += `<span class="metric-tag" data-metric="${key}"${tooltip}>${metric.label}</span>`;
            }
            categoryHTML += "</div></div>";
            if (category === "others") {
                othersHTML = categoryHTML;
            } else {
                html += categoryHTML;
            }
        }
        return html + othersHTML;
    }
    #formatCategoryName(category) {
        const translated = getLang(`screener.categories.${category}`, "");
        if (translated) {
            return translated;
        }
        return category.split("_").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
    }
    #init() {
        this.#cacheDOMElements();
        this.#renderMetricsModal();
        this.#loadStoredState();
        if (this.rootNode !== document) {
            this.#bindShadowModalEvents();
        }
        if (this.dom.tableContainer) {
            this.dom.tableContainer.classList.add("loading");
        }
        const urlFilters = this.rootNode === document ? this.#parseUrlSharedFilters() : null;
        if (urlFilters) {
            this.CONFIG.EMBED_DEFAULT_FILTERS = urlFilters;
            try {
                if (typeof STORAGE !== "undefined") {
                    STORAGE.remove(`filters_${this.CONFIG.CURRENT_ASSET_TYPE}`);
                }
            } catch (e) {}
        }
        this.#initFilterManager();
        if (urlFilters && this.filterManager && typeof this.filterManager.saveFiltersToStorage === "function") {
            this.filterManager.saveFiltersToStorage();
            this.#cleanUrlSharedParam();
        }
        this.#bindEvents();
        this.#setupObserver();
        if (this.CONFIG.SHOW_COLUMNS_BUTTON && this.#q("#metricSearch")) {
            smartSearch("#metricSearch", ".available-metrics-section", ".metric-tag", ".metric-category", "data-search", this.rootNode);
        }
    }
    #bindShadowModalEvents() {
        this.rootNode.addEventListener("click", e => {
            const trigger = e.target.closest('[data-cm-toggle="custom-modal"]');
            if (trigger) {
                e.preventDefault();
                const targetSelector = trigger.getAttribute("data-cm-target");
                if (!targetSelector) return;
                const targetElement = this.rootNode.querySelector(targetSelector);
                if (!targetElement || typeof CustomModal === "undefined") return;
                const modal = CustomModal.getOrCreateInstance(targetElement);
                if (!modal.isTransitioning) modal.toggle();
            }
            const dismissBtn = e.target.closest('[data-cm-dismiss="custom-modal"]');
            if (dismissBtn) {
                e.preventDefault();
                const modal = dismissBtn.closest('.modal[data-custom-modal="true"]');
                if (!modal) return;
                const instance = CustomModal.getInstance(modal);
                if (instance) instance.hide();
            }
        });
    }
    #setupObserver() {
        const observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.#loadApiData();
                    observer.disconnect();
                }
            });
        }, {
            threshold: this.CONFIG.OBSERVER_THRESHOLD,
            rootMargin: this.CONFIG.OBSERVER_ROOT_MARGIN
        });
        observer.observe(this.dom.tableContainer);
    }
    #initFilterManager() {
        if (this.CONFIG.ENABLE_FILTER_MANAGER) {
            this.filterManager = new DynamicFilterManager(this);
        }
    }
    #renderMetricsModal() {
        this.dom.categoryMetrics = this.#qAll(".available-metrics-section .category-metrics");
    }
    #cacheDOMElements() {
        this.dom = {
            cryptoTable: this.#q(".screener-table"),
            tableBody: this.#q(".screener-table tbody"),
            tableHead: this.#q(".screener-table thead tr"),
            searchInput: this.#qId("tableSearch"),
            columnsModal: this.#qId("columnsModal"),
            selectedMetricsContainer: this.#qId("selectedMetricsContainer"),
            controlBtns: this.#qAll(".control-btn"),
            categoryMetrics: this.#qAll(".category-metrics"),
            tableContainer: this.#q(".screener-table-container"),
            colorPicker: this.#qId("dynamicColorPicker"),
            paginationNumbers: this.#qId("paginationNumbers"),
            paginationWrapper: this.#q(".pagination-wrapper"),
            metricTags: this.#qAll(".metric-tag")
        };
    }
    #loadStoredState() {
        const storedColumns = this.#getStorage(`columns_${this.state.storageKey}`);
        this.state.selectedMetrics = Array.isArray(storedColumns) && storedColumns.length > 0 ? storedColumns : this.#getPresetMetrics(this.CONFIG.DEFAULT_PRESET);
        this.state.currentTimePeriod = this.#getStorage(`timePeriod_${this.state.storageKey}`) || this.CONFIG.DEFAULT_TIME_PERIOD;
        this.state.entriesPerPage = this.#getStorage(`entriesPerPage_${this.state.storageKey}`) || this.CONFIG.PER_PAGE;
        this.#loadFavorites();
        this.#updateSelectUI("timePeriodSelect", this.state.currentTimePeriod);
        this.#updateSelectUI("perPageSelect", this.state.entriesPerPage);
        const lastColumns = this.#getStorage(`coloumsFixSelect_${this.state.storageKey}`) || this.CONFIG.DEFAULT_PRESET || "overview";
        this.#updateSelectUI("columnPresetSelect", lastColumns);
    }
    #bindEvents() {
        this.#bindSearchEvents();
        this.#bindPaginationEvents();
        this.#bindColumnEvents();
        this.#bindFavoriteEvents();
        this.#bindFilterEvents();
        this.#bindControlButtons();
        this.#bindRefreshButton();
        this.#bindPriceAnimationEvents();
    }
    #delayedInit() {
        requestAnimationFrame(() => {
            if (typeof initializeTableScrollShadows === "function") {
                initializeTableScrollShadows(".fx-table-scroll-shadow", this.rootNode);
            }
        });
    }
    async #loadApiData() {
        const existingError = this.#q(".empty-error-message");
        if (existingError) existingError.remove();
        if (this.state.currentApiController) {
            this.state.currentApiController.abort();
        }
        this.state.currentApiController = new AbortController;
        if (this.dom.tableContainer) {
            this.dom.tableContainer.classList.add("loading");
        }
        try {
            const postData = this.#preparePostData();
            const data = await sendPostRequest(`${API_ENDPOINT}${this.CONFIG.API_BASE_PATH}/advance/`, postData, {
                signal: this.state.currentApiController.signal
            });
            if (this.dom.tableContainer) {
                this.dom.tableContainer.classList.remove("loading");
                this.dom.tableContainer.removeAttribute("aria-busy");
                const skeletonWrapper = this.dom.tableContainer.querySelector(".skeleton-loading-wrapper");
                if (skeletonWrapper) skeletonWrapper.remove();
            }
            if (data.status && data.response && data.response.length > 0) {
                this.#handleApiSuccess(data);
            } else {
                this.state.cachedApiData = null;
                this.state.currentPaginationInfo = null;
                this.state.currentPage = 1;
                this.#updateTotalRecordsInHeader(0);
                if (this.dom.paginationWrapper) {
                    this.dom.paginationWrapper.classList.add("d-none");
                }
                this.#showErrorState(data.message || null);
            }
            this.#resetRefreshButton();
        } catch (error) {
            if (this.dom.tableContainer) {
                this.dom.tableContainer.classList.remove("loading");
                this.dom.tableContainer.removeAttribute("aria-busy");
                const skeletonWrapper = this.dom.tableContainer.querySelector(".skeleton-loading-wrapper");
                if (skeletonWrapper) skeletonWrapper.remove();
            }
            if (error.name !== "AbortError") {
                console.error("API Error:", error);
                this.state.cachedApiData = null;
                this.state.currentPaginationInfo = null;
                this.state.currentPage = 1;
                this.#updateTotalRecordsInHeader(0);
                if (this.dom.paginationWrapper) {
                    this.dom.paginationWrapper.classList.add("d-none");
                }
                this.#showErrorState();
            }
            this.#resetRefreshButton();
        } finally {
            const searchContainer = this.dom.searchInput?.closest(".search-input-container");
            if (searchContainer) {
                searchContainer.classList.remove("searching");
            }
            this.state.currentApiController = null;
            this.#delayedInit();
        }
    }
    #preparePostData() {
        const postData = {};
        if (this.CONFIG.CURRENT_ASSET_TYPE) {
            postData.type = this.CONFIG.CURRENT_ASSET_TYPE;
        }
        postData.period = this.state.currentTimePeriod;
        postData.page = this.state.currentPage;
        postData.per_page = this.state.entriesPerPage;
        if (this.state.selectedMetrics && this.state.selectedMetrics.length > 0) {
            const columnPaths = [ ...new Set(this.state.selectedMetrics.map(metric => {
                const metricConfig = this.METRICS[metric];
                if (!metricConfig) return null;
                const paths = [ metricConfig.path ];
                if (metricConfig.subText && metricConfig.subText.path) {
                    paths.push(metricConfig.subText.path);
                }
                return paths.filter(p => p !== null);
            }).filter(path => path !== null).flat()) ];
            if (columnPaths.length > 0) {
                postData.columns = columnPaths.join(",") + ",profile,ticker,meta.blockchain_id";
            }
        }
        const STOCK_LIKE_ASSETS = [ "stock", "dr", "fund", "index" ];
        if (STOCK_LIKE_ASSETS.includes(this.CONFIG.CURRENT_ASSET_TYPE)) {
            const country = this.state.activeFilters["meta.country_eq"];
            if (country && (Array.isArray(country) && country.length > 0 || !Array.isArray(country) && country)) {
                postData.country = Array.isArray(country) ? country[0] : country;
            } else {
                postData.country = this.CONFIG.DEFAULT_COUNTRY;
            }
            if (this.CONFIG.DEFAULT_IS_PRIMARY !== false && this.CONFIG.DEFAULT_IS_PRIMARY !== 0) {
                const storedPrimaryState = this.#getStorage(`is_primary_${this.CONFIG.CURRENT_ASSET_TYPE}`);
                if (storedPrimaryState !== false) {
                    postData.is_primary = 1;
                }
            }
        }
        if (this.state.currentSearchTerm && this.state.currentSearchTerm.trim()) {
            postData.search = this.state.currentSearchTerm.trim();
        }
        if (this.state.currentSort.column && this.state.currentSort.direction) {
            postData.sort_by = `${this.state.currentSort.column}_${this.state.currentSort.direction}`;
        }
        if (this.state.symbol) {
            postData.symbol = this.state.symbol;
            this.state.symbol = null;
        } else if (this.state.currentFilter !== "all") {
            const tickersForColor = Object.keys(this.state.favorites).filter(ticker => this.state.favorites[ticker] === this.state.currentFilter);
            if (tickersForColor.length > 0) {
                postData.symbol = tickersForColor.join(",");
            }
        }
        if (this.state.activeFilters && Object.keys(this.state.activeFilters).length > 0) {
            const filtersToSend = {
                ...this.state.activeFilters
            };
            delete filtersToSend["meta.country_eq"];
            if (Object.keys(filtersToSend).length > 0) {
                postData.filters = filtersToSend;
            }
        }
        if (this.state.otherParams && Object.keys(this.state.otherParams).length > 0) {
            Object.assign(postData, this.state.otherParams);
        }
        return postData;
    }
    #handleApiSuccess(data) {
        const existingError = this.#q(".empty-error-message");
        if (existingError) existingError.remove();
        if (!data.status || !data.response || data.response.length === 0) {
            this.state.cachedApiData = null;
            this.state.currentPaginationInfo = null;
            this.state.currentPage = 1;
            this.#updateTotalRecordsInHeader(0);
            if (this.dom.paginationWrapper) {
                this.dom.paginationWrapper.classList.add("d-none");
            }
            this.#showErrorState();
            return;
        }
        this.state.cachedApiData = data;
        if (data.info && data.info.pagination) {
            this.state.currentPaginationInfo = data.info.pagination;
            this.state.currentPage = this.state.currentPaginationInfo.page || 1;
        } else {
            this.state.currentPaginationInfo = null;
            this.state.currentPage = 1;
        }
        this.#renderTable();
        this.#applyStoredFavorites();
        this.#initializeTableSorting();
        this.#updatePagination();
        if (this.filterManager && typeof this.filterManager.updateExchangeOptions === "function") {
            this.filterManager.updateExchangeOptions();
        }
        if (typeof webSocketService !== "undefined" && typeof webSocketService.subscribeAllSymbols === "function") {
            webSocketService.subscribeAllSymbols();
        }
    }
    #renderTable(preserveTotalRecords = false) {
        this.#buildTableHeaders(true, preserveTotalRecords);
        const allRowsHTML = this.state.cachedApiData?.response ? this.state.cachedApiData.response.map((item, index) => this.#buildTableRow(item, index + 1)).join("") : "";
        if (this.dom.tableBody) {
            this.dom.tableBody.innerHTML = allRowsHTML;
        }
        this.#initializeTableSorting();
    }
    #buildTableHeaders(showFavoritesState = true, preserveTotalRecords = false) {
        const headerParts = [];
        const tooltipMsg = `data-tooltip-title="${this.#t("filter_by_watchlist", "Filter by watchlisted items")}"`;
        if (this.CONFIG.SHOW_FAVORITES_ADD_REMOVE) {
            if (showFavoritesState) {
                let headerStarClass = "star-btn";
                let headerIconSvg = "";
                if (this.state.headerFilterActive && this.state.headerCurrentColor) {
                    headerStarClass += " active";
                    const color = this.CONFIG.COLORS[this.state.headerCurrentColor];
                    headerIconSvg = `<svg width="18" height="18" fill="${color}" stroke="${color}"><use href="#star"></use></svg>`;
                } else {
                    headerIconSvg = '<svg width="18" height="18" fill="none" stroke="currentColor"><use href="#star"></use></svg>';
                }
                headerParts.push(`<td class="col-fav"><button class="${headerStarClass}" ${tooltipMsg}>${headerIconSvg}</button></td>`);
            } else {
                headerParts.push(`<td class="col-fav"><button class="star-btn" ${tooltipMsg}><svg width="18" height="18" fill="none" stroke="currentColor"><use href="#star"></use></svg></button></td>`);
            }
        }
        let nameColumnClass = "col-name row-sticky is-soring";
        let nameColumnDirection = "";
        if (this.state.currentSort.column === "profile.symbol" && this.state.currentSort.direction) {
            nameColumnClass += ` sort-${this.state.currentSort.direction}`;
            nameColumnDirection = `data-sort-direction="${this.state.currentSort.direction}"`;
        }
        let totalRecordsHTML = "";
        if (preserveTotalRecords && this.dom.tableHead) {
            const existingCount = this.dom.tableHead.querySelector(".total-records-count");
            if (existingCount) {
                totalRecordsHTML = ` <span class="total-records-count">${existingCount.textContent}</span>`;
            }
        }
        headerParts.push(`<th class="${nameColumnClass}" data-api-column="profile.symbol" ${nameColumnDirection}><span>${this.#t("name", "Name")}${totalRecordsHTML}</span></th>`);
        for (let i = 0; i < this.state.selectedMetrics.length; i++) {
            const metric = this.state.selectedMetrics[i];
            const metricConfig = this.METRICS[metric];
            const header = metricConfig ? metricConfig.label : metric;
            const dataPath = metricConfig ? metricConfig.path : metric;
            const dataAlign = metricConfig && metricConfig.text_align ? metricConfig.text_align : "";
            let sortClass = "";
            let sortDirection = "";
            const isSortable = this.CONFIG.ENABLE_SORT;
            if (isSortable && this.state.currentSort.column === dataPath && this.state.currentSort.direction) {
                sortClass = `sort-${this.state.currentSort.direction}`;
                sortDirection = `data-sort-direction="${this.state.currentSort.direction}"`;
            }
            const textAlign = dataAlign ? `data-align="${dataAlign}"` : "";
            const infoTooltip = metricConfig && metricConfig.tooltip ? metricConfig.tooltip : "";
            headerParts.push(`<th class="col-${metric} ${isSortable ? "is-soring" : ""} ${sortClass}" data-api-column="${dataPath}" ${sortDirection} ${textAlign} data-tooltip-title="${infoTooltip}">${header}</th>`);
        }
        if (this.CONFIG.SHOW_COLUMNS_BUTTON) {
            headerParts.push(`<th class="col-columns-btn"><button class="label-tag-blue columns-manage-btn" data-cm-toggle="custom-modal" data-cm-target="#columnsModal" data-tooltip-title="${this.#t("manage_columns", "Manage Columns")}"><svg width="20" height="20" fill="none" stroke="currentColor"><use href="#plus"></use></svg></button></th>`);
        }
        if (this.dom.tableHead) {
            this.dom.tableHead.innerHTML = headerParts.join("");
        }
    }
    #buildTableRow(apiItem, rank) {
        const profile = apiItem.profile || {};
        const symbol = profile.symbol || "—";
        const exchange = profile.exchange || "—";
        const name = profile.name || "—";
        const ticker = apiItem.ticker || `${exchange}:${symbol}`;
        const updateMode = profile.delay || null;
        let delayedIndicator = "";
        if (updateMode) {
            const delayMinutes = Math.floor(updateMode / 60);
            const tooltipText = delayMinutes === 1 ? this.#t("delayed_minute", "Data delayed by {minutes} minute", {
                minutes: delayMinutes
            }) : this.#t("delayed_minutes", "Data delayed by {minutes} minutes", {
                minutes: delayMinutes
            });
            delayedIndicator = `<sup class="symbol-suffix" data-tooltip-title="${tooltipText}">D</sup>`;
        }
        const imageHTML = this.CONFIG.SHOW_LOGO ? '<div class="search-result-icon-container">' + generateCoinLogoHtml(profile) + "</div>" : "";
        const linkType = this.CONFIG.linkType || this.CONFIG.CURRENT_ASSET_TYPE;
        const detailURL = buildDynamicUrl(this.CONFIG.linkUrl, {
            symbol: symbol,
            exchange: exchange,
            name: name,
            type: linkType
        });
        const rowParts = [ `<tr class="screener-row" tabindex="0" data-timeperiod="${this.state.currentTimePeriod}" data-ticker="${ticker}">` ];
        if (this.CONFIG.SHOW_FAVORITES_ADD_REMOVE) {
            rowParts.push('<td class="fav-cell"><button class="star-btn"><svg width="18" height="18" fill="none" stroke="currentColor"><use href="#star"></use></svg></button></td>');
        }
        let symbolDisplay = symbol;
        if (profile.type === "coin") {
            if (symbol.endsWith("USD")) {
                symbolDisplay = symbol.slice(0, -3);
            }
        }
        const tickerHTML = this.CONFIG.SHOW_TICKER ? `<span class="coin-symbol">${symbolDisplay}</span>${delayedIndicator}` : "";
        const descHTML = this.CONFIG.SHOW_DESCRIPTION ? `<span class="coin-name">${name}</span>` : "";
        if (this.CONFIG.enableLinks) {
            rowParts.push(`<td class="name-cell row-sticky">\n                <a href="${detailURL}" class="coin-info">${imageHTML}\n                    <div class="coin-details">\n                        <div>${tickerHTML}</div>\n                        ${descHTML}\n                    </div>\n                </a></td>`);
        } else {
            rowParts.push(`<td class="name-cell row-sticky">\n                <div class="coin-info">${imageHTML}\n                    <div class="coin-details">\n                        <div>${tickerHTML}</div>\n                        ${descHTML}\n                    </div>\n                </div></td>`);
        }
        for (let i = 0; i < this.state.selectedMetrics.length; i++) {
            const metric = this.state.selectedMetrics[i];
            const metricConfig = this.METRICS[metric];
            const dataAlign = metricConfig && metricConfig.text_align ? metricConfig.text_align : "";
            const textAlign = dataAlign ? `data-align="${dataAlign}"` : "";
            const apiCellData = this.#getApiCellData(apiItem, metric);
            let tooltip = "";
            if (apiCellData["tooltip"] === true) {
                tooltip = ` data-tooltip-title="${apiCellData["value"]}" data-tooltip-overflow="true"`;
            }
            rowParts.push(`<td class="${metric}-cell" ${tooltip} ${textAlign}>${apiCellData["value"]}</td>`);
        }
        if (this.CONFIG.SHOW_COLUMNS_BUTTON) {
            rowParts.push('<td class="columns-cell-empty"></td>');
        }
        rowParts.push("</tr>");
        return rowParts.join("");
    }
    #formatValueWithSubText(formattedValue, subTextHtml, metricConfig, tooltip = false) {
        if (!subTextHtml) {
            return {
                value: formattedValue,
                tooltip: tooltip
            };
        }
        const leftAlign = metricConfig.subText?.align === "left";
        const finalValue = leftAlign ? `${subTextHtml}${formattedValue}` : `${formattedValue}${subTextHtml}`;
        return {
            value: finalValue,
            tooltip: tooltip
        };
    }
    #getApiCellData(apiItem, metric) {
        const metricConfig = this.METRICS[metric];
        if (!metricConfig) {
            return {
                value: "—",
                tooltip: false
            };
        }
        const dataPath = metricConfig.path;
        let tooltip = false;
        if (!dataPath) {
            return {
                value: "—",
                tooltip: false
            };
        }
        let formattedValue = dataPath.split(".").reduce((acc, key) => acc && acc[key], apiItem);
        const colorChange = metricConfig.color_change;
        if (formattedValue === null || formattedValue === undefined) {
            return {
                value: "—",
                tooltip: tooltip
            };
        }
        let subTextHtml = "";
        if (metricConfig.subText && metricConfig.subText.path) {
            const isCurrencyPath = metricConfig.subText.path === "profile.currency";
            if (isCurrencyPath && !this.CONFIG.SHOW_CURRENCY) {} else {
                const subTextValue = metricConfig.subText.path.split(".").reduce((acc, key) => acc && acc[key], apiItem);
                if (subTextValue !== undefined && subTextValue !== null && subTextValue !== "") {
                    const alignSuffix = metricConfig.subText.align === "left" ? "-left" : "";
                    if (metricConfig.subText.type === "icon") {
                        const styleObj = getValueStyle(subTextValue);
                        subTextHtml = `<span class="subtext-icon${alignSuffix} ${styleObj.class}"><svg width="18" height="18" fill="none" stroke="currentColor"><use href="#${styleObj.icon}"></use></svg></span>`;
                    } else {
                        subTextHtml = `<span class="subtext-text${alignSuffix}">${subTextValue}</span>`;
                    }
                }
            }
        }
        if (Array.isArray(formattedValue) && formattedValue.length > 0) {
            if (metricConfig.innerArrayIndex) {
                const key = metricConfig.innerArrayIndex;
                const extractedValues = formattedValue.map(item => {
                    if (typeof item === "object" && item !== null && item[key] !== undefined) {
                        return item[key];
                    }
                    return null;
                }).filter(val => val !== null);
                formattedValue = extractedValues.length > 0 ? extractedValues.join(", ") : formattedValue.join(", ");
            } else {
                formattedValue = formattedValue.join(", ");
            }
            tooltip = true;
        } else if (formattedValue instanceof Set && formattedValue.size > 0) {
            formattedValue = Array.from(formattedValue).join(", ");
            tooltip = true;
        } else if (typeof formattedValue === "object" && formattedValue !== null && Object.keys(formattedValue).length > 0) {
            if (metricConfig.innerArrayIndex) {
                const key = metricConfig.innerArrayIndex;
                const extractedValues = Object.values(formattedValue).map(item => {
                    if (typeof item === "object" && item !== null && item[key] !== undefined) {
                        return item[key];
                    }
                    return null;
                }).filter(val => val !== null);
                formattedValue = extractedValues.length > 0 ? extractedValues.join(", ") : Object.values(formattedValue).join(", ");
            } else {
                formattedValue = Object.values(formattedValue).join(", ");
            }
            tooltip = true;
        }
        if (dataPath === "profile.exchange") {
            const excLogo = apiItem.profile?.exc_logo || "";
            if (!excLogo) return {
                value: formattedValue,
                tooltip: tooltip
            };
            return {
                value: `<div class="logo-with-text-cell"><div class="search-result-icon-container">${generateCoinLogoHtml({
                    "base_logo": excLogo
                })}</div><div>${formattedValue}</div></div>`,
                tooltip: tooltip
            };
        }
        if (dataPath === "meta.blockchain_name") {
            const networkLogo = apiItem.profile?.blockchain_logo || "";
            if (!networkLogo) return {
                value: formattedValue,
                tooltip: tooltip
            };
            return {
                value: `<div class="logo-with-text-cell"><div class="search-result-icon-container">${generateCoinLogoHtml({
                    "base_logo": networkLogo
                })}</div><div>${formattedValue}</div></div>`,
                tooltip: tooltip
            };
        }
        let finalValue = formattedValue;
        if (metric === "signal-summary" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.signal-summary.${formattedValue}`, formattedValue);
        }
        if (metric === "signal-confidence" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.volatility-level.${formattedValue}`, formattedValue);
        }
        if (metric === "signal-trend" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.signal-trend.${formattedValue}`, formattedValue);
        }
        if (metric === "signal-volatility" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.volatility-level.${formattedValue}`, formattedValue);
        }
        if (metric === "candle-type" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.candle-type.${formattedValue}`, formattedValue);
        }
        if (metric === "candle-pattern" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.candle-pattern.${formattedValue}`, formattedValue);
        }
        if (metric === "volatility-level" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.volatility-level.${formattedValue}`, formattedValue);
        }
        if (metric === "bollinger-squeeze" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.squeeze.${formattedValue}`, formattedValue);
        }
        if (metric === "country" && typeof formattedValue === "string") {
            finalValue = getLang(`market_data.countries.${formattedValue}`, formattedValue);
        }
        if (metric === "region" && typeof formattedValue === "string") {
            finalValue = getLang(`market_data.countries.${formattedValue}`, formattedValue);
        }
        if (metric === "sector" && typeof formattedValue === "string") {
            finalValue = getLang(`market_data.sectors.${formattedValue}`, formattedValue);
        }
        if (metric === "sub-type" && typeof formattedValue === "string") {
            const assetType = this.CONFIG.CURRENT_ASSET_TYPE || "stock";
            finalValue = getLang(`market_data.${assetType}.sub_type.${formattedValue}`, formattedValue);
        }
        if (metric === "type" && typeof formattedValue === "string") {
            finalValue = getLang(`select_options.type-stock.${formattedValue}`, formattedValue);
        }
        if (metricConfig.type === "formatDateTime") {
            finalValue = formatDateTime(formattedValue);
        } else if (metricConfig.type === "formatDate") {
            finalValue = formatDate(formattedValue);
        } else if (metricConfig.type === "sameReturn") {
            finalValue = formattedValue;
        } else if (metricConfig.type === "percentage") {
            finalValue = convertNumberToShortForm(formattedValue, true, 2) + "%";
        }
        if (metricConfig.type !== "sameReturn") {
            if (metricConfig.comma_format) {
                finalValue = addThousandSeparator(String(finalValue));
            } else if (metricConfig.type !== "percentage") {
                finalValue = convertNumberToShortForm(finalValue);
            } else {
                finalValue = addThousandSeparator(String(finalValue));
            }
        }
        const classes = [];
        if (metricConfig.socket_update) {
            classes.push("fx-socket-field");
            const profile = apiItem.profile || {};
            const symbol = profile.symbol || "";
            const exchange = profile.exchange || "";
            let ticker = apiItem.ticker || `${exchange}:${symbol}`;
            ticker = ticker.replace(/[^a-zA-Z0-9]/g, "_");
            const fieldSuffix = dataPath.split(".").pop();
            const classPrefix = metricConfig.class_prefix ? `_${metricConfig.class_prefix}` : "";
            classes.push(`${ticker}${classPrefix}_${fieldSuffix}_${this.state.currentTimePeriod}`.toUpperCase());
        }
        const isChangeMetric = dataPath.includes(".ch") || dataPath.includes(".chp");
        if (isChangeMetric && typeof formattedValue === "number" && formattedValue !== 0) {
            const sign = formattedValue > 0 ? "+" : "−";
            finalValue = `<span class="sign">${sign}</span>${String(finalValue).replace(/^-/, "")}`;
        }
        if (colorChange && colorChange === "yes" || isChangeMetric) {
            const signalClass = getValueStyle(formattedValue, "class");
            classes.push(signalClass);
        }
        if (classes.length > 0) {
            finalValue = `<span class="${classes.join(" ")}">${finalValue}</span>`;
        }
        return this.#formatValueWithSubText(finalValue, subTextHtml, metricConfig, tooltip);
    }
    #bindSearchEvents() {
        const debouncedSearch = this.#createDebouncedSearch(this.CONFIG.SEARCH_DEBOUNCE);
        let lastSearchTerm = "";
        let isComposing = false;
        if (!this.dom.searchInput) return;
        const searchContainer = this.dom.searchInput.closest(".search-input-container");
        this.dom.searchInput.addEventListener("input", () => {
            if (isComposing) return;
            if (searchContainer) {
                searchContainer.classList.toggle("has-value", this.dom.searchInput.value.trim() !== "");
            }
            this.#handleSearchInput(debouncedSearch, lastSearchTerm);
        });
        this.dom.searchInput.addEventListener("compositionstart", () => {
            isComposing = true;
        });
        this.dom.searchInput.addEventListener("compositionend", () => {
            isComposing = false;
            if (searchContainer) {
                searchContainer.classList.toggle("has-value", this.dom.searchInput.value.trim() !== "");
            }
            this.#handleSearchInput(debouncedSearch, lastSearchTerm);
        });
        this.dom.searchInput.addEventListener("keydown", e => {
            if (e.key === "Escape") {
                this.#clearSearch();
            } else if (e.key === "Enter") {
                e.preventDefault();
                this.#handleSearchInput(debouncedSearch, lastSearchTerm, true);
            }
        });
        const clearBtn = searchContainer?.querySelector(".search-clear-btn");
        if (clearBtn) {
            clearBtn.addEventListener("click", () => {
                this.#clearSearch();
            });
        }
    }
    #handleSearchInput(debouncedSearch, lastSearchTerm, immediate = false) {
        const searchTerm = this.dom.searchInput ? this.dom.searchInput.value.toLowerCase().trim() : "";
        this.state.currentSearchTerm = searchTerm;
        const searchContainer = this.dom.searchInput?.closest(".search-input-container");
        if (searchContainer) {
            searchContainer.classList.add("searching");
        }
        if (immediate) {
            this.#smartReload();
        } else {
            debouncedSearch(searchTerm);
        }
    }
    #smartReload() {
        if (this.state.headerFilterActive && this.state.headerCurrentColor) {
            this.#loadWatchlistData(this.state.headerCurrentColor);
        } else {
            this.#loadApiData();
        }
    }
    #clearSearch() {
        if (this.dom.searchInput) {
            this.dom.searchInput.value = "";
            const searchContainer = this.dom.searchInput.closest(".search-input-container");
            if (searchContainer) {
                searchContainer.classList.remove("searching", "has-value");
            }
        }
        this.state.currentSearchTerm = "";
        this.#smartReload();
    }
    #createDebouncedSearch(delay = 400) {
        let timeoutId;
        let lastQuery = "";
        return searchTerm => {
            if (searchTerm === lastQuery) return;
            clearTimeout(timeoutId);
            lastQuery = searchTerm;
            timeoutId = setTimeout(() => {
                this.state.currentSearchTerm = searchTerm.trim();
                this.#smartReload();
            }, delay);
        };
    }
    #bindPaginationEvents() {
        this.rootNode.addEventListener("click", e => {
            const pageBtn = e.target.closest(".page-num:not(.dots)");
            if (pageBtn) {
                const page = parseInt(pageBtn.dataset.page);
                if (page && page !== this.state.currentPage) {
                    this.state.currentPage = page;
                    this.#loadApiData();
                    this.#scrollToTable();
                }
            }
        });
        const prevBtn = this.#qId("prevPageBtn");
        if (prevBtn) {
            prevBtn.addEventListener("click", () => {
                if (this.state.currentPaginationInfo?.has_prev) {
                    this.state.currentPage = this.state.currentPaginationInfo.prev_page;
                    this.#loadApiData();
                    this.#scrollToTable();
                }
            });
        }
        const nextBtn = this.#qId("nextPageBtn");
        if (nextBtn) {
            nextBtn.addEventListener("click", () => {
                if (this.state.currentPaginationInfo?.has_next) {
                    this.state.currentPage = this.state.currentPaginationInfo.next_page;
                    this.#loadApiData();
                    this.#scrollToTable();
                }
            });
        }
        const perPageOptions = this.#qAll("#perPageSelect .select-option");
        perPageOptions.forEach(option => {
            option.addEventListener("click", () => {
                this.state.entriesPerPage = option.dataset.value;
                this.#setStorage(`entriesPerPage_${this.state.storageKey}`, this.state.entriesPerPage);
                this.state.currentPage = 1;
                this.#loadApiData();
                this.#scrollToTable();
            });
        });
    }
    #updatePagination() {
        if (this.CONFIG.ENABLE_INFINITE_SCROLL) {
            if (this.dom.paginationWrapper) {
                this.dom.paginationWrapper.classList.add("d-none");
            }
            const totalRecords = this.state.currentPaginationInfo?.total_records || 0;
            this.#updateTotalRecordsInHeader(totalRecords);
            this.#setupInfiniteScroll();
            return;
        }
        if (!this.state.currentPaginationInfo) {
            if (this.dom.paginationWrapper) {
                this.dom.paginationWrapper.classList.add("d-none");
            }
            this.#updateTotalRecordsInHeader(0);
            return;
        }
        const paginationInfo = this.state.currentPaginationInfo;
        const currentPage = paginationInfo.page || 1;
        const hasNext = paginationInfo.has_next || false;
        const totalRecords = paginationInfo.total_records || 0;
        const shouldShowPagination = this.#shouldShowPagination(currentPage, hasNext);
        if (!shouldShowPagination) {
            if (this.dom.paginationWrapper) {
                this.dom.paginationWrapper.classList.add("d-none");
            }
            this.#updateTotalRecordsInHeader(totalRecords);
            return;
        }
        if (this.dom.paginationWrapper) {
            this.dom.paginationWrapper.classList.remove("d-none");
        }
        const from = (paginationInfo.display_from || 1).toLocaleString();
        const to = (paginationInfo.display_to || 0).toLocaleString();
        const total = totalRecords.toLocaleString();
        const recordsFromEl = this.#qId("recordsFrom");
        const recordsToEl = this.#qId("recordsTo");
        const totalRecordsEl = this.#qId("totalRecords");
        if (recordsFromEl) recordsFromEl.textContent = from;
        if (recordsToEl) recordsToEl.textContent = to;
        if (totalRecordsEl) totalRecordsEl.textContent = total;
        this.#updateTotalRecordsInHeader(totalRecords);
        const prevBtn = this.#qId("prevPageBtn");
        const nextBtn = this.#qId("nextPageBtn");
        if (prevBtn) prevBtn.disabled = !paginationInfo.has_prev;
        if (nextBtn) nextBtn.disabled = !hasNext;
        this.#buildPageNumbers();
    }
    #shouldShowPagination(currentPage, hasNext) {
        if (currentPage === 1 && !hasNext) {
            return false;
        }
        if (currentPage === 1 && hasNext) {
            return true;
        }
        if (currentPage > 1) {
            return true;
        }
        return false;
    }
    #setupInfiniteScroll() {
        const paginationInfo = this.state.currentPaginationInfo;
        if (!paginationInfo || !paginationInfo.has_next) {
            this.#destroyInfiniteScrollObserver();
            return;
        }
        if (this.state.infiniteScrollObserver) return;
        let sentinel = this.#qId("infiniteScrollSentinel");
        if (!sentinel) {
            sentinel = document.createElement("div");
            sentinel.id = "infiniteScrollSentinel";
            sentinel.className = "text-center py-3";
            if (this.dom.cryptoTable) {
                this.dom.cryptoTable.after(sentinel);
            }
        }
        this.state.infiniteScrollObserver = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting && !this.state.isLoadingMore) {
                    this.#loadMoreRows();
                }
            });
        }, {
            root: null,
            rootMargin: "100px",
            threshold: 0
        });
        this.state.infiniteScrollObserver.observe(sentinel);
    }
    async #loadMoreRows() {
        const paginationInfo = this.state.currentPaginationInfo;
        if (!paginationInfo || !paginationInfo.has_next) return;
        this.state.isLoadingMore = true;
        this.#showLoadingSkeletonRows();
        try {
            this.state.currentPage = paginationInfo.next_page;
            const postData = this.#preparePostData();
            const data = await sendPostRequest(`${API_ENDPOINT}${this.CONFIG.API_BASE_PATH}/advance/`, postData);
            this.#removeLoadingSkeletonRows();
            if (data.status && data.response && data.response.length > 0) {
                if (data.info && data.info.pagination) {
                    this.state.currentPaginationInfo = data.info.pagination;
                }
                if (this.state.cachedApiData && this.state.cachedApiData.response) {
                    this.state.cachedApiData.response = [ ...this.state.cachedApiData.response, ...data.response ];
                }
                this.#appendRows(data.response);
                if (!data.info?.pagination?.has_next) {
                    this.#destroyInfiniteScrollObserver();
                }
            }
        } catch (error) {
            console.error("Infinite scroll load error:", error);
            this.#removeLoadingSkeletonRows();
        } finally {
            this.state.isLoadingMore = false;
        }
    }
    #showLoadingSkeletonRows() {
        if (!this.dom.tableBody) return;
        const columnCount = this.state.selectedMetrics.length + 2;
        const skeletonHTML = Array(3).fill(0).map(() => `\n            <tr class="screener-row skeleton-loading-row">\n                ${this.CONFIG.SHOW_FAVORITES_ADD_REMOVE ? '<td class="fav-cell"><div class="skeleton skeleton-circle" style="width:18px;height:18px;"></div></td>' : ""}\n                <td class="name-cell row-sticky">\n                    <div class="coin-info">\n                        <div class="skeleton skeleton-circle" style="width:32px;height:32px;"></div>\n                        <div class="coin-details">\n                            <div class="skeleton skeleton-text" style="width:60px;height:14px;"></div>\n                            <div class="skeleton skeleton-text" style="width:100px;height:12px;margin-top:4px;"></div>\n                        </div>\n                    </div>\n                </td>\n                ${Array(Math.min(columnCount - 2, 6)).fill('<td><div class="skeleton skeleton-text" style="width:70px;height:14px;"></div></td>').join("")}\n            </tr>\n        `).join("");
        this.dom.tableBody.insertAdjacentHTML("beforeend", skeletonHTML);
    }
    #removeLoadingSkeletonRows() {
        if (!this.dom.tableBody) return;
        this.dom.tableBody.querySelectorAll(".skeleton-loading-row").forEach(row => row.remove());
    }
    #destroyInfiniteScrollObserver() {
        if (this.state.infiniteScrollObserver) {
            this.state.infiniteScrollObserver.disconnect();
            this.state.infiniteScrollObserver = null;
        }
        const sentinel = this.#qId("infiniteScrollSentinel");
        if (sentinel) sentinel.remove();
    }
    #appendRows(data) {
        if (!this.dom.tableBody || !data || data.length === 0) return;
        data.forEach((item, index) => {
            const currentRowCount = this.dom.tableBody.querySelectorAll(".screener-row").length;
            const rowHtml = this.#buildTableRow(item, currentRowCount + index + 1);
            this.dom.tableBody.insertAdjacentHTML("beforeend", rowHtml);
        });
        this.#applyStoredFavorites();
    }
    #updateTotalRecordsInHeader(totalRecords) {
        if (!this.dom.tableHead) return;
        const totalRecordsSpan = this.dom.tableHead.querySelector(".total-records-count");
        if (totalRecords > 0) {
            const formattedTotal = totalRecords.toLocaleString();
            if (totalRecordsSpan) {
                totalRecordsSpan.textContent = `(${formattedTotal})`;
            } else {
                const nameHeader = this.dom.tableHead.querySelector(".col-name span");
                if (nameHeader) {
                    const span = document.createElement("span");
                    span.className = "total-records-count";
                    span.textContent = `(${formattedTotal})`;
                    nameHeader.appendChild(document.createTextNode(" "));
                    nameHeader.appendChild(span);
                }
            }
        } else {
            if (totalRecordsSpan) totalRecordsSpan.remove();
        }
    }
    #buildPageNumbers() {
        if (!this.state.currentPaginationInfo) {
            return;
        }
        const current = this.state.currentPaginationInfo.page || 1;
        const total = this.state.currentPaginationInfo.total_pages || 1;
        if (total <= 1) {
            if (this.dom.paginationNumbers) {
                this.dom.paginationNumbers.innerHTML = "";
            }
            return;
        }
        const pages = [];
        if (current > 3) {
            pages.push(1);
            if (current > 4) pages.push("...");
        }
        for (let i = Math.max(1, current - 2); i <= Math.min(total, current + 2); i++) {
            pages.push(i);
        }
        if (current < total - 2) {
            if (current < total - 3) pages.push("...");
            pages.push(total);
        }
        const paginationHTML = pages.map(page => {
            if (page === "...") {
                return '<span class="page-num dots">...</span>';
            }
            const activeClass = page === current ? " active" : "";
            return `<button class="page-num${activeClass}" data-page="${page}">${page}</button>`;
        }).join("");
        if (this.dom.paginationNumbers) {
            this.dom.paginationNumbers.innerHTML = paginationHTML;
        }
        if (this.dom.paginationWrapper) {
            this.dom.paginationWrapper.classList.remove("d-none");
        }
    }
    #scrollToTable() {
        if (this.dom.cryptoTable) {
            const rect = this.dom.cryptoTable.getBoundingClientRect();
            const offset = rect.top + window.pageYOffset - this.CONFIG.SCROLL_OFFSET;
            window.scrollTo({
                top: offset,
                behavior: "smooth"
            });
        }
    }
    #bindFavoriteEvents() {
        if (this.dom.cryptoTable) {
            this.dom.cryptoTable.addEventListener("click", e => {
                const btn = e.target.closest(".star-btn");
                if (!btn) return;
                const isHeader = btn.closest("td.col-fav") !== null;
                const row = btn.closest(".screener-row");
                const btnId = isHeader ? "header" : row ? row.dataset.ticker : null;
                const lastId = this.dom.colorPicker ? this.dom.colorPicker.dataset.clickedId : null;
                if (isHeader) {
                    if (this.dom.colorPicker && this.dom.colorPicker.classList.contains("show") && lastId === btnId) {
                        this.#hideColorPicker();
                    } else {
                        if (this.dom.colorPicker) this.dom.colorPicker.dataset.clickedId = btnId;
                        this.#handleHeaderStarClick(btn);
                    }
                } else {
                    const ticker = row ? row.dataset.ticker : null;
                    if (!this.CONFIG.ENABLE_COLOR_GROUPS) {
                        if (ticker && this.state.favorites[ticker]) {
                            this.#removeFavorite(ticker);
                        } else if (ticker) {
                            this.state.currentSymbol = ticker;
                            this.#handleRowColorSelection("orange");
                        }
                    } else if (ticker && this.state.favorites[ticker]) {
                        this.#removeFavorite(ticker);
                    } else if (ticker) {
                        this.state.currentSymbol = ticker;
                        this.#handleRowColorSelection("orange");
                        if (this.dom.colorPicker) this.dom.colorPicker.dataset.clickedId = btnId;
                        this.#showColorPickerAt(btn);
                    }
                }
            });
        }
        this.rootNode.addEventListener("click", e => {
            const colorOption = e.target.closest("#dynamicColorPicker .color-option");
            if (colorOption) {
                const color = colorOption.dataset.color;
                if (this.state.currentSymbol === "header") {
                    this.#handleHeaderColorSelection(color);
                } else if (this.state.currentSymbol) {
                    this.#handleRowColorSelection(color);
                }
                this.#hideColorPicker();
            }
        });
        this.rootNode.addEventListener("click", e => {
            const dropdownItem = e.target.closest(".watchlist-controls .dropdown-item");
            if (dropdownItem) {
                const color = dropdownItem.dataset.color || "all";
                if (typeof CustomSelect !== "undefined" && CustomSelect.closeAll) {
                    CustomSelect.closeAll(this.rootNode);
                }
                this.#handleWatchlistFilter(color);
            }
        });
        const selectTriggers = this.#qAll(".select-trigger");
        selectTriggers.forEach(trigger => {
            trigger.addEventListener("click", () => {
                this.#hideColorPicker();
            });
        });
        this.rootNode.addEventListener("click", e => {
            const isStarBtn = e.target.closest(".star-btn");
            const isColorPicker = e.target.closest("#dynamicColorPicker");
            if (!isStarBtn && !isColorPicker) {
                this.#hideColorPicker();
            }
        });
        if (this.rootNode !== document) {
            document.addEventListener("click", e => {
                const host = this.rootNode.host;
                if (host && !host.contains(e.target)) {
                    this.#hideColorPicker();
                }
            });
        }
    }
    #handleHeaderStarClick(starBtn) {
        if (!this.state.headerFilterActive) {
            if (!this.CONFIG.ENABLE_COLOR_GROUPS) {
                this.#handleHeaderColorSelection("orange");
            } else {
                this.#showColorPickerAt(starBtn);
                this.state.currentSymbol = "header";
            }
        } else {
            this.#resetHeaderFilter();
            this.#loadApiData();
        }
    }
    #handleHeaderColorSelection(color) {
        this.state.headerFilterActive = true;
        this.state.headerCurrentColor = color;
        this.state.currentFilter = color;
        const headerBtn = this.dom.cryptoTable ? this.dom.cryptoTable.querySelector(".col-fav .star-btn") : null;
        if (headerBtn) {
            const colorCode = this.CONFIG.COLORS[color];
            headerBtn.classList.add("active");
            const svg = headerBtn.querySelector("svg");
            if (svg) {
                svg.setAttribute("fill", colorCode);
                svg.setAttribute("stroke", colorCode);
            }
        }
        this.#loadWatchlistData(color);
    }
    #handleRowColorSelection(color) {
        const ticker = this.state.currentSymbol;
        this.state.favorites[ticker] = {
            color: color,
            type: this.CONFIG.CURRENT_ASSET_TYPE
        };
        const row = this.#q(`.screener-row[data-ticker="${ticker}"]`);
        if (row) {
            const colorCode = this.CONFIG.COLORS[color];
            const starBtn = row.querySelector(".star-btn");
            if (starBtn) {
                starBtn.classList.add("active");
                const svg = starBtn.querySelector("svg");
                if (svg) {
                    svg.setAttribute("fill", colorCode);
                    svg.setAttribute("stroke", colorCode);
                }
            }
        }
        this.#saveFavorites();
        this.#updateWatchlistDropdown();
    }
    #handleWatchlistFilter(color) {
        this.state.currentFilter = color;
        const headerBtn = this.dom.cryptoTable ? this.dom.cryptoTable.querySelector(".col-fav .star-btn") : null;
        if (color === "all") {
            this.#resetHeaderFilter();
            this.#loadApiData();
        } else {
            this.state.headerFilterActive = true;
            this.state.headerCurrentColor = color;
            if (headerBtn) {
                const colorCode = this.CONFIG.COLORS[color];
                headerBtn.classList.add("active");
                const svg = headerBtn.querySelector("svg");
                if (svg) {
                    svg.setAttribute("fill", colorCode);
                    svg.setAttribute("stroke", colorCode);
                }
            }
            this.#loadWatchlistData(color);
        }
    }
    #resetHeaderFilter() {
        this.state.headerFilterActive = false;
        this.state.headerCurrentColor = null;
        this.state.currentFilter = "all";
        requestAnimationFrame(() => {
            const headerBtn = this.#q(".col-fav .star-btn");
            if (headerBtn) {
                headerBtn.classList.remove("active");
                const svg = headerBtn.querySelector("svg");
                if (svg) {
                    svg.setAttribute("fill", "none");
                    svg.setAttribute("stroke", "currentColor");
                }
            }
        });
    }
    #loadWatchlistData(color) {
        const currentType = this.CONFIG.CURRENT_ASSET_TYPE;
        const tickersForColor = Object.keys(this.state.favorites).filter(ticker => {
            const fav = this.state.favorites[ticker];
            const favColor = typeof fav === "string" ? fav : fav?.color;
            const favType = typeof fav === "string" ? null : fav?.type;
            return favColor === color && (!favType || favType === currentType);
        });
        if (tickersForColor.length === 0) {
            if (this.dom.tableBody) {
                this.dom.tableBody.innerHTML = "";
            }
            this.#showErrorState();
            if (this.dom.paginationWrapper) {
                this.dom.paginationWrapper.classList.add("d-none");
            }
            this.#updateTotalRecordsInHeader(0);
            return;
        }
        this.state.symbol = tickersForColor.join(",");
        this.#loadApiData();
    }
    #applyStoredFavorites() {
        const favoriteKeys = Object.keys(this.state.favorites);
        if (favoriteKeys.length === 0) return;
        for (let i = 0; i < favoriteKeys.length; i++) {
            const ticker = favoriteKeys[i];
            const fav = this.state.favorites[ticker];
            const color = typeof fav === "string" ? fav : fav?.color;
            const colorCode = this.CONFIG.COLORS[color];
            const row = this.#q(`.screener-row[data-ticker="${ticker}"]`);
            if (row) {
                const starBtn = row.querySelector(".star-btn");
                if (starBtn) {
                    starBtn.classList.add("active");
                    const svg = starBtn.querySelector("svg");
                    if (svg) {
                        svg.setAttribute("fill", colorCode);
                        svg.setAttribute("stroke", colorCode);
                    }
                }
            }
        }
        this.#updateWatchlistDropdown();
    }
    #removeFavorite(ticker) {
        delete this.state.favorites[ticker];
        const row = this.#q(`.screener-row[data-ticker="${ticker}"]`);
        if (row) {
            const starBtn = row.querySelector(".star-btn");
            if (starBtn) {
                starBtn.classList.remove("active");
                const svg = starBtn.querySelector("svg");
                if (svg) {
                    svg.setAttribute("fill", "none");
                    svg.setAttribute("stroke", "currentColor");
                }
            }
        }
        this.#deleteFavoriteFromStorage(ticker);
        this.#updateWatchlistDropdown();
    }
    #updateWatchlistDropdown() {
        const flaggedSection = this.#qId("flaggedSection");
        if (!flaggedSection) return;
        const currentType = this.CONFIG.CURRENT_ASSET_TYPE;
        const usedColors = new Set;
        Object.entries(this.state.favorites).forEach(([_ticker, fav]) => {
            const color = typeof fav === "string" ? fav : fav?.color;
            const type = typeof fav === "string" ? null : fav?.type;
            if (!type || type === currentType) {
                usedColors.add(color);
            }
        });
        const existingItems = flaggedSection.querySelectorAll(".dropdown-item");
        existingItems.forEach(item => item.remove());
        const itemsHTML = [ ...usedColors ].map(color => {
            const colorCode = this.CONFIG.COLORS[color];
            const colorName = this.#t(`color_${color}`, color.charAt(0).toUpperCase() + color.slice(1));
            const listText = this.#t("list", "list");
            return `<div class="dropdown-item" data-color="${color}">\n                <div class="color-${color}">\n                    <svg width="18" height="18" fill="${colorCode}" stroke="${colorCode}">\n                        <use href="#star"></use>\n                    </svg>\n                </div>\n                ${colorName} ${listText}\n            </div>`;
        }).join("");
        flaggedSection.insertAdjacentHTML("beforeend", itemsHTML);
    }
    #showColorPickerAt(trigger) {
        if (!this.dom.colorPicker || !trigger) return;
        const isWidget = this.rootNode !== document;
        const rect = trigger.getBoundingClientRect();
        const dirEl = isWidget ? this.rootNode.querySelector("[dir]") || document.documentElement : document.documentElement;
        const isRTL = dirEl.dir === "rtl";
        this.dom.colorPicker.style.position = "absolute";
        if (isWidget) {
            const wrapper = this.rootNode.querySelector(".vnx-widget-root") || this.rootNode.host;
            const wrapperRect = wrapper.getBoundingClientRect();
            const top = rect.top - wrapperRect.top + wrapper.scrollTop + trigger.offsetHeight + 5;
            const left = rect.left - wrapperRect.left + wrapper.scrollLeft;
            this.dom.colorPicker.style.top = top + "px";
            if (isRTL) {
                const right = wrapperRect.right - rect.right;
                this.dom.colorPicker.style.left = "auto";
                this.dom.colorPicker.style.right = right + "px";
            } else {
                this.dom.colorPicker.style.right = "auto";
                this.dom.colorPicker.style.left = left + "px";
            }
        } else {
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
            const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
            this.dom.colorPicker.style.top = rect.top + scrollTop + trigger.offsetHeight + 5 + "px";
            if (isRTL) {
                const rightPos = window.innerWidth - rect.right + scrollLeft;
                this.dom.colorPicker.style.left = "auto";
                this.dom.colorPicker.style.right = rightPos + "px";
            } else {
                this.dom.colorPicker.style.right = "auto";
                this.dom.colorPicker.style.left = rect.left + scrollLeft + "px";
            }
        }
        if (isWidget) {
            const wrapper = this.rootNode.querySelector(".vnx-widget-root") || this.rootNode.host;
            const pickerWidth = this.dom.colorPicker.offsetWidth || 220;
            const wrapperWidth = wrapper.offsetWidth;
            const currentLeft = parseInt(this.dom.colorPicker.style.left) || 0;
            if (!isRTL && currentLeft + pickerWidth > wrapperWidth) {
                this.dom.colorPicker.style.left = wrapperWidth - pickerWidth - 5 + "px";
            }
        }
        this.dom.colorPicker.classList.add("show");
    }
    #hideColorPicker() {
        if (this.dom.colorPicker) {
            this.dom.colorPicker.classList.remove("show");
        }
        this.state.currentSymbol = null;
    }
    #loadFavorites() {
        const stored = this.#getStorage(this.state.favStorageKey);
        if (stored && typeof stored === "object") {
            this.state.favorites = stored;
        }
    }
    #saveFavorites() {
        const freshStored = this.#getStorage(this.state.favStorageKey) || {};
        const mergedFavorites = {
            ...freshStored,
            ...this.state.favorites
        };
        this.state.favorites = mergedFavorites;
        this.#setStorage(this.state.favStorageKey, mergedFavorites);
    }
    #deleteFavoriteFromStorage(ticker) {
        const freshStored = this.#getStorage(this.state.favStorageKey) || {};
        delete freshStored[ticker];
        this.#setStorage(this.state.favStorageKey, freshStored);
    }
    #bindColumnEvents() {
        const modalElement = this.#qId("columnsModal");
        if (modalElement) {
            modalElement.addEventListener("shown.cm.modal", () => {
                this.#renderAvailableMetrics();
                this.#updateSelectedMetricsDisplay();
                this.#updateAvailableMetrics();
            });
            modalElement.addEventListener("click", e => {
                const metricTag = e.target.closest(".metric-tag");
                if (metricTag) {
                    const metric = metricTag.dataset.metric;
                    const isInUse = metricTag.classList.contains("in-use");
                    if (isInUse) {
                        if (!this.#validateMetricLimits("remove")) return;
                        this.state.selectedMetrics = this.state.selectedMetrics.filter(m => m !== metric);
                        metricTag.classList.remove("in-use", "active");
                    } else {
                        if (!this.#validateMetricLimits("add")) return;
                        this.state.selectedMetrics.push(metric);
                        metricTag.classList.add("in-use", "active");
                    }
                    this.state.isUserCustomizing = true;
                    this.#setCustomPreset();
                    this.#updateSelectedMetricsDisplay();
                    this.#updateAvailableMetrics();
                }
                if (e.target.closest(".btn-apply-changes")) {
                    const selectedOption = this.#q("#columnPresetSelect .select-option.selected");
                    const preset = selectedOption ? selectedOption.dataset.value : null;
                    this.#applyColumnChanges();
                    this.dom.controlBtns.forEach(btn => {
                        if (btn.dataset.coloumnSet) {
                            btn.classList.remove("active");
                            if (btn.dataset.coloumnSet === preset) {
                                btn.classList.add("active");
                            }
                        }
                    });
                    const modalInstance = CustomModal.getInstance(this.dom.columnsModal);
                    if (modalInstance) {
                        modalInstance.hide();
                    }
                    showNotification(this.#t("column_layout_updated", "Column layout updated successfully"), "success");
                }
                if (e.target.closest(".btn-restart")) {
                    this.#applyColumnPreset("overview");
                    const overviewOption = this.#q('#columnPresetSelect .select-option[data-value="overview"]');
                    if (overviewOption) {
                        const allOptions = this.#qAll("#columnPresetSelect .select-option");
                        allOptions.forEach(opt => opt.classList.remove("selected"));
                        overviewOption.classList.add("selected");
                        const optionHTML = overviewOption.getAttribute("data-html") || overviewOption.innerHTML;
                        const triggerText = this.#q("#columnPresetSelect .select-trigger-text");
                        if (triggerText) triggerText.innerHTML = optionHTML;
                    }
                    this.state.isUserCustomizing = false;
                }
            });
        }
        this.rootNode.addEventListener("click", e => {
            const removeBtn = e.target.closest(".remove-metric");
            if (removeBtn) {
                e.stopPropagation();
                const metric = removeBtn.dataset.metric;
                if (!this.#validateMetricLimits("remove")) return;
                this.state.selectedMetrics = this.state.selectedMetrics.filter(m => m !== metric);
                this.state.isUserCustomizing = true;
                this.#setCustomPreset();
                this.#updateSelectedMetricsDisplay();
                this.#updateAvailableMetrics();
            }
        });
        this.#initializeMetricDragDrop();
        const presetOptions = this.#qAll("#columnPresetSelect .select-option");
        presetOptions.forEach(option => {
            option.addEventListener("click", () => {
                this.#applyColumnPreset(option.dataset.value);
            });
        });
    }
    #applyColumnChanges() {
        if (this.state.isUserCustomizing) {
            this.#setCustomPreset();
            this.#setStorage(`coloumsFixSelect_${this.state.storageKey}`, "custom");
            this.dom.controlBtns.forEach(btn => {
                if (btn.dataset.coloumnSet) {
                    btn.classList.remove("active");
                    if (btn.dataset.coloumnSet === "custom") {
                        btn.classList.add("active");
                    }
                }
            });
            this.state.isUserCustomizing = false;
        } else {
            const selectedOption = this.#q("#columnPresetSelect .select-option.selected");
            const preset = selectedOption ? selectedOption.dataset.value : null;
            if (preset && preset !== "custom") {
                this.#setStorage(`coloumsFixSelect_${this.state.storageKey}`, preset);
                this.dom.controlBtns.forEach(btn => {
                    if (btn.dataset.coloumnSet) {
                        btn.classList.remove("active");
                        if (btn.dataset.coloumnSet === preset) {
                            btn.classList.add("active");
                        }
                    }
                });
            }
        }
        this.#handleColumnChange();
    }
    #validateMetricLimits(action) {
        if (action === "remove" && this.state.selectedMetrics.length <= this.CONFIG.MIN_COLUMNS) {
            showNotification(this.#t("min_metrics_required", `Minimum ${this.CONFIG.MIN_COLUMNS} metrics required`, {
                count: this.CONFIG.MIN_COLUMNS
            }), "warning");
            return false;
        }
        if (action === "add" && this.state.selectedMetrics.length >= this.CONFIG.MAX_COLUMNS) {
            showNotification(this.#t("max_metrics_allowed", `Maximum ${this.CONFIG.MAX_COLUMNS} metrics allowed`, {
                count: this.CONFIG.MAX_COLUMNS
            }), "warning");
            return false;
        }
        return true;
    }
    #updateSelectedMetricsDisplay() {
        if (!this.dom.selectedMetricsContainer) return;
        if (this.state.selectedMetrics.length === 0) {
            this.dom.selectedMetricsContainer.classList.add("empty");
            this.dom.selectedMetricsContainer.innerHTML = "";
            return;
        }
        this.dom.selectedMetricsContainer.classList.remove("empty");
        const metricsHTML = this.state.selectedMetrics.map((metric, i) => {
            const metricConfig = this.METRICS[metric];
            const metricName = metricConfig ? metricConfig.label : metric;
            return `<div class="selected-metric-item label-tag-blue" data-metric="${metric}" draggable="true">\n                <span class="metric-position">${i + 1}</span>\n                <span class="metric-name">${metricName}</span>\n                <button class="remove-metric" data-metric="${metric}">\n                    <svg width="18" height="18" fill="none" stroke="currentColor">\n                        <use href="#x"></use>\n                    </svg>\n                </button>\n            </div>`;
        }).join("");
        this.dom.selectedMetricsContainer.innerHTML = metricsHTML;
        const limitBadge = this.#q(".limit-badge");
        if (limitBadge) {
            limitBadge.textContent = `${this.state.selectedMetrics.length}/${this.CONFIG.MAX_COLUMNS}`;
        }
    }
    #updateAvailableMetrics() {
        const metricTags = this.#qAll(".metric-tag");
        const selectedSet = new Set(this.state.selectedMetrics);
        for (let i = 0; i < metricTags.length; i++) {
            const tag = metricTags[i];
            const metric = tag.getAttribute("data-metric");
            tag.classList.remove("active", "in-use", "disabled");
            if (selectedSet.has(metric)) {
                tag.classList.add("in-use");
            }
        }
    }
    #renderAvailableMetrics() {
        const availableSection = this.#q(".available-metrics-section");
        if (!availableSection) return;
        const metricCategories = availableSection.querySelectorAll(".metric-category");
        if (metricCategories.length > 0) {
            this.dom.categoryMetrics = this.#qAll(".category-metrics");
            return;
        }
        const metricsHTML = this.#generateColumnsHTML();
        availableSection.insertAdjacentHTML("beforeend", metricsHTML);
        this.dom.categoryMetrics = this.#qAll(".category-metrics");
    }
    #initializeMetricDragDrop() {
        if (!this.dom.selectedMetricsContainer) return;
        let draggedMetric = null;
        let draggedIndex = null;
        let isDragging = false;
        let draggedElement = null;
        let cloneElement = null;
        const container = this.dom.selectedMetricsContainer;
        container.addEventListener("dragstart", e => {
            const item = e.target.closest(".selected-metric-item");
            if (!item) return;
            draggedMetric = item.dataset.metric;
            draggedIndex = this.state.selectedMetrics.indexOf(draggedMetric);
            item.classList.add("dragging");
            e.dataTransfer.effectAllowed = "move";
            container.classList.add("drag-active");
        });
        container.addEventListener("dragend", e => {
            const item = e.target.closest(".selected-metric-item");
            if (item) item.classList.remove("dragging");
            container.classList.remove("drag-active");
            this.#qAll(".selected-metric-item").forEach(el => {
                el.classList.remove("drop-target", "drop-before", "drop-after");
            });
            draggedMetric = null;
            draggedIndex = null;
        });
        container.addEventListener("dragover", e => {
            const item = e.target.closest(".selected-metric-item");
            if (!item) return;
            e.preventDefault();
            e.dataTransfer.dropEffect = "move";
            if (draggedMetric && item.dataset.metric !== draggedMetric) {
                const rect = item.getBoundingClientRect();
                const midX = rect.left + rect.width / 2;
                const mouseX = e.clientX;
                this.#qAll(".selected-metric-item").forEach(el => {
                    el.classList.remove("drop-before", "drop-after");
                });
                if (mouseX < midX) {
                    item.classList.add("drop-before");
                } else {
                    item.classList.add("drop-after");
                }
            }
        });
        container.addEventListener("drop", e => {
            const item = e.target.closest(".selected-metric-item");
            if (!item) return;
            e.preventDefault();
            this.#qAll(".selected-metric-item").forEach(el => {
                el.classList.remove("drop-target", "drop-before", "drop-after");
            });
            if (draggedMetric && draggedIndex !== null) {
                const targetMetric = item.dataset.metric;
                const targetIndex = this.state.selectedMetrics.indexOf(targetMetric);
                if (draggedIndex !== targetIndex) {
                    const rect = item.getBoundingClientRect();
                    const midX = rect.left + rect.width / 2;
                    const mouseX = e.clientX;
                    let insertIndex = targetIndex;
                    if (mouseX > midX) insertIndex = targetIndex + 1;
                    if (draggedIndex < insertIndex) insertIndex--;
                    this.state.selectedMetrics.splice(draggedIndex, 1);
                    this.state.selectedMetrics.splice(insertIndex, 0, draggedMetric);
                    this.state.isUserCustomizing = true;
                    this.#setCustomPreset();
                    this.#updateSelectedMetricsDisplay();
                }
            }
        });
        container.addEventListener("touchstart", e => {
            const item = e.target.closest(".selected-metric-item");
            if (!item) return;
            const touch = e.touches[0];
            draggedElement = item;
            draggedMetric = item.dataset.metric;
            draggedIndex = this.state.selectedMetrics.indexOf(draggedMetric);
            setTimeout(() => {
                if (!isDragging && draggedElement) {
                    isDragging = true;
                    draggedElement.classList.add("dragging");
                    container.classList.add("drag-active");
                    cloneElement = draggedElement.cloneNode(true);
                    cloneElement.style.position = "fixed";
                    cloneElement.style.zIndex = "9999";
                    cloneElement.style.pointerEvents = "none";
                    cloneElement.style.opacity = "0.8";
                    cloneElement.style.left = touch.clientX - draggedElement.offsetWidth / 2 + "px";
                    cloneElement.style.top = touch.clientY - draggedElement.offsetHeight / 2 + "px";
                    (this.rootNode || document.body).appendChild(cloneElement);
                }
            }, 150);
        });
        container.addEventListener("touchmove", e => {
            if (!isDragging) return;
            e.preventDefault();
            const touch = e.touches[0];
            if (cloneElement && draggedElement) {
                cloneElement.style.left = touch.clientX - draggedElement.offsetWidth / 2 + "px";
                cloneElement.style.top = touch.clientY - draggedElement.offsetHeight / 2 + "px";
            }
            const root = this.rootNode && this.rootNode.elementFromPoint ? this.rootNode : document;
            const elementBelow = root.elementFromPoint(touch.clientX, touch.clientY);
            const targetItem = elementBelow ? elementBelow.closest(".selected-metric-item") : null;
            if (targetItem && targetItem.dataset.metric !== draggedMetric) {
                const rect = targetItem.getBoundingClientRect();
                const midX = rect.left + rect.width / 2;
                this.#qAll(".selected-metric-item").forEach(el => {
                    el.classList.remove("drop-before", "drop-after");
                });
                if (touch.clientX < midX) {
                    targetItem.classList.add("drop-before");
                } else {
                    targetItem.classList.add("drop-after");
                }
            }
        });
        const handleTouchEnd = e => {
            if (!isDragging) {
                draggedElement = null;
                draggedMetric = null;
                draggedIndex = null;
                return;
            }
            const touch = e.changedTouches[0];
            const root = this.rootNode && this.rootNode.elementFromPoint ? this.rootNode : document;
            const elementBelow = root.elementFromPoint(touch.clientX, touch.clientY);
            const targetItem = elementBelow ? elementBelow.closest(".selected-metric-item") : null;
            if (cloneElement) {
                cloneElement.remove();
                cloneElement = null;
            }
            if (draggedElement) {
                draggedElement.classList.remove("dragging");
            }
            container.classList.remove("drag-active");
            this.#qAll(".selected-metric-item").forEach(el => {
                el.classList.remove("drop-target", "drop-before", "drop-after");
            });
            if (draggedMetric && draggedIndex !== null && targetItem) {
                const targetMetric = targetItem.dataset.metric;
                const targetIndex = this.state.selectedMetrics.indexOf(targetMetric);
                if (draggedIndex !== targetIndex) {
                    const rect = targetItem.getBoundingClientRect();
                    const midX = rect.left + rect.width / 2;
                    let insertIndex = targetIndex;
                    if (touch.clientX > midX) insertIndex = targetIndex + 1;
                    if (draggedIndex < insertIndex) insertIndex--;
                    this.state.selectedMetrics.splice(draggedIndex, 1);
                    this.state.selectedMetrics.splice(insertIndex, 0, draggedMetric);
                    this.state.isUserCustomizing = true;
                    this.#setCustomPreset();
                    this.#updateSelectedMetricsDisplay();
                }
            }
            isDragging = false;
            draggedElement = null;
            draggedMetric = null;
            draggedIndex = null;
        };
        container.addEventListener("touchend", handleTouchEnd);
        container.addEventListener("touchcancel", handleTouchEnd);
    }
    #applyColumnPreset(preset) {
        this.state.isUserCustomizing = false;
        const presetMetrics = this.#getPresetMetrics(preset);
        if (presetMetrics && presetMetrics.length > 0) {
            this.state.selectedMetrics = [ ...presetMetrics ];
            this.#updateSelectedMetricsDisplay();
            this.#updateAvailableMetrics();
        }
    }
    #setCustomPreset() {
        const customOption = this.#q('#columnPresetSelect .select-option[data-value="custom"]');
        if (customOption) {
            const allOptions = this.#qAll("#columnPresetSelect .select-option");
            allOptions.forEach(opt => opt.classList.remove("selected"));
            customOption.classList.add("selected");
            const optionHTML = customOption.getAttribute("data-html") || customOption.innerHTML;
            const triggerText = this.#q("#columnPresetSelect .select-trigger-text");
            if (triggerText) triggerText.innerHTML = optionHTML;
        }
    }
    #initializeTableSorting() {
        if (!this.dom.cryptoTable) return;
        if (!this.CONFIG.ENABLE_SORT) return;
        const sortableHeaders = this.dom.cryptoTable.querySelectorAll("th.is-soring");
        sortableHeaders.forEach(th => {
            delete th.dataset.sortDirection;
        });
        sortableHeaders.forEach(th => {
            const newTh = th.cloneNode(true);
            th.parentNode.replaceChild(newTh, th);
            newTh.addEventListener("click", e => {
                if (e.target.classList.contains("fa-info-circle") || e.target.closest(".star-btn")) return;
                const apiColumn = newTh.dataset.apiColumn;
                if (!apiColumn) return;
                const currentDirection = newTh.dataset.sortDirection || (this.state.currentSort.column === apiColumn ? this.state.currentSort.direction : null) || "none";
                const newDirection = currentDirection === "none" || currentDirection === "desc" ? "asc" : "desc";
                this.dom.cryptoTable.querySelectorAll("th.is-soring").forEach(otherTh => {
                    if (otherTh !== newTh) {
                        otherTh.classList.remove("sort-asc", "sort-desc");
                        delete otherTh.dataset.sortDirection;
                    }
                });
                newTh.classList.remove("sort-asc", "sort-desc");
                newTh.classList.add("sort-" + newDirection);
                newTh.dataset.sortDirection = newDirection;
                this.state.currentSort.column = apiColumn;
                this.state.currentSort.direction = newDirection;
                this.#smartReload();
            });
        });
    }
    #bindFilterEvents() {
        const timePeriodOptions = this.#qAll("#timePeriodSelect .select-option");
        timePeriodOptions.forEach(option => {
            option.addEventListener("click", () => {
                this.state.currentTimePeriod = option.dataset.value;
                this.#setStorage(`timePeriod_${this.state.storageKey}`, this.state.currentTimePeriod);
                this.#smartReload();
            });
        });
        const shareBtn = this.#q(".share-filters-btn");
        if (shareBtn) {
            shareBtn.addEventListener("click", e => {
                e.preventDefault();
                this.#handleShareFiltersClick();
            });
        }
    }
    #parseUrlSharedFilters() {
        try {
            const params = new URLSearchParams(window.location.search);
            const raw = params.get("sf");
            if (!raw) return null;
            const decoded = JSON.parse(raw);
            if (decoded && typeof decoded === "object" && !Array.isArray(decoded) && Object.keys(decoded).length > 0) {
                return decoded;
            }
        } catch (e) {}
        return null;
    }
    #cleanUrlSharedParam() {
        try {
            const url = new URL(window.location.href);
            if (url.searchParams.has("sf")) {
                url.searchParams.delete("sf");
                window.history.replaceState({}, "", url.toString());
            }
        } catch (e) {}
    }
    #handleShareFiltersClick() {
        if (!this.filterManager || typeof this.filterManager.getFiltersForAPI !== "function") return;
        const apiData = this.filterManager.getFiltersForAPI();
        const filters = {
            ...apiData.filters || {}
        };
        if (Object.keys(filters).length === 0) {
            if (typeof showNotification === "function") {
                showNotification(this.#t("no_filters_to_share", "No filters applied to share"), "warning");
            }
            return;
        }
        let shareUrl;
        try {
            const url = new URL(window.location.href);
            url.searchParams.set("sf", JSON.stringify(filters));
            shareUrl = url.toString();
        } catch (e) {
            return;
        }
        this.#copyTextToClipboard(shareUrl).then(() => {
            if (typeof showNotification === "function") {
                showNotification(this.#t("share_link_copied", "Share link copied to clipboard"), "success");
            }
        }).catch(() => {
            if (typeof showNotification === "function") {
                showNotification(this.#t("share_link_failed", "Failed to copy share link"), "error");
            }
        });
    }
    #copyTextToClipboard(text) {
        if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
            return navigator.clipboard.writeText(text);
        }
        return new Promise((resolve, reject) => {
            try {
                const ta = document.createElement("textarea");
                ta.value = text;
                ta.setAttribute("readonly", "");
                ta.style.position = "fixed";
                ta.style.top = "-9999px";
                ta.style.opacity = "0";
                document.body.appendChild(ta);
                ta.select();
                const ok = document.execCommand("copy");
                document.body.removeChild(ta);
                ok ? resolve() : reject(new Error("copy failed"));
            } catch (e) {
                reject(e);
            }
        });
    }
    #bindControlButtons() {
        const lastColumns = this.#getStorage(`coloumsFixSelect_${this.state.storageKey}`) || this.CONFIG.DEFAULT_PRESET || "overview";
        let activeBtn = null;
        this.dom.controlBtns.forEach(btn => {
            if (btn.dataset.coloumnSet) {
                btn.classList.remove("active");
                if (btn.dataset.coloumnSet === lastColumns) {
                    btn.classList.add("active");
                    activeBtn = btn;
                }
            }
        });
        if (activeBtn) {
            const scrollContainer = this.#q(".control-buttons-scroll");
            if (scrollContainer) {
                scrollToCenter(activeBtn, scrollContainer);
            }
        }
        this.dom.controlBtns.forEach(btn => {
            if (!btn.dataset.coloumnSet) return;
            btn.addEventListener("click", e => {
                e.preventDefault();
                const scrollContainer = this.#q(".control-buttons-scroll");
                if (scrollContainer) {
                    scrollToCenter(btn, scrollContainer);
                }
                const preset = btn.dataset.coloumnSet;
                if (preset === "custom") return;
                const dataControlsLeft = this.#q(".data-controls-left");
                if (dataControlsLeft) {
                    scrollToCenter(btn, dataControlsLeft);
                }
                this.dom.controlBtns.forEach(b => {
                    if (b.dataset.coloumnSet) b.classList.remove("active");
                });
                btn.classList.add("active");
                const savedOption = this.#q(`#columnPresetSelect .select-option[data-value="${preset}"]`);
                if (savedOption) {
                    this.#qAll("#columnPresetSelect .select-option").forEach(opt => {
                        opt.classList.remove("selected");
                    });
                    savedOption.classList.add("selected");
                    const optionHTML = savedOption.getAttribute("data-html") || savedOption.innerHTML;
                    const triggerText = this.#q("#columnPresetSelect .select-trigger-text");
                    if (triggerText) triggerText.innerHTML = optionHTML;
                }
                this.#setStorage(`coloumsFixSelect_${this.state.storageKey}`, preset);
                this.#applyColumnPreset(preset);
                this.#handleColumnChange();
            });
        });
    }
    #handleColumnChange() {
        this.#setStorage(`columns_${this.state.storageKey}`, this.state.selectedMetrics);
        this.#loadApiData();
    }
    #bindRefreshButton() {
        this.rootNode.addEventListener("click", e => {
            if (e.target.closest("#refreshBtn")) {
                const refreshBtn = this.#qId("refreshBtn");
                if (refreshBtn) {
                    refreshBtn.classList.add("refresh-spinning");
                    refreshBtn.disabled = true;
                }
                this.#smartReload();
            }
        });
    }
    #resetRefreshButton() {
        const refreshBtn = this.#qId("refreshBtn");
        if (refreshBtn) {
            refreshBtn.classList.remove("refresh-spinning");
            refreshBtn.disabled = false;
        }
    }
    #bindPriceAnimationEvents() {
        const settingsContainer = this.#q(".screener-setting");
        if (!settingsContainer) return;
        const animationOptions = settingsContainer.querySelectorAll(".price-animation-options .select-option");
        const tableViewOptions = settingsContainer.querySelectorAll(".radio-options:not(.price-animation-options) .select-option");
        const checkboxOptions = settingsContainer.querySelectorAll('.select-option[data-value="screener-logo"], .select-option[data-value="screener-currency"]');
        const storedAnimation = this.#getStorage(`priceAnimation_${this.state.storageKey}`) || "animation-full";
        const defaultTableView = this.CONFIG.SHOW_TICKER && this.CONFIG.SHOW_DESCRIPTION ? "screener-ticker-description" : this.CONFIG.SHOW_TICKER ? "screener-ticker" : "screener-description";
        const storedTableView = this.#getStorage(`tableView_${this.state.storageKey}`) || defaultTableView;
        const storedColumnSettings = this.#getStorage(`columnSettings_${this.state.storageKey}`) || {
            "screener-logo": this.CONFIG.SHOW_LOGO !== false,
            "screener-currency": this.CONFIG.SHOW_CURRENCY !== false
        };
        animationOptions.forEach(opt => {
            opt.classList.remove("selected");
            if (opt.dataset.value === storedAnimation) {
                opt.classList.add("selected");
            }
        });
        this.#applyPriceAnimationSetting(storedAnimation);
        tableViewOptions.forEach(opt => {
            opt.classList.remove("selected");
            if (opt.dataset.value === storedTableView) {
                opt.classList.add("selected");
            }
        });
        this.#applyTableViewSetting(storedTableView);
        checkboxOptions.forEach(el => {
            const value = el.dataset.value;
            const isEnabled = storedColumnSettings[value] !== false;
            if (isEnabled) {
                el.classList.add("selected");
            } else {
                el.classList.remove("selected");
            }
        });
        this.#applyCheckboxSettings(storedColumnSettings);
        animationOptions.forEach(opt => {
            opt.addEventListener("click", e => {
                e.stopPropagation();
                const value = opt.dataset.value;
                animationOptions.forEach(o => o.classList.remove("selected"));
                opt.classList.add("selected");
                this.#setStorage(`priceAnimation_${this.state.storageKey}`, value);
                this.#applyPriceAnimationSetting(value);
            });
        });
        tableViewOptions.forEach(opt => {
            opt.addEventListener("click", e => {
                e.stopPropagation();
                const value = opt.dataset.value;
                tableViewOptions.forEach(o => o.classList.remove("selected"));
                opt.classList.add("selected");
                this.#setStorage(`tableView_${this.state.storageKey}`, value);
                this.#applyTableViewSetting(value);
                this.#renderTable(true);
                if (typeof webSocketService !== "undefined" && typeof webSocketService.subscribeAllSymbols === "function") {
                    webSocketService.subscribeAllSymbols();
                }
            });
        });
        checkboxOptions.forEach(opt => {
            opt.addEventListener("click", e => {
                e.stopPropagation();
                const value = opt.dataset.value;
                opt.classList.toggle("selected");
                const currentSettings = this.#getStorage(`columnSettings_${this.state.storageKey}`) || {
                    "screener-logo": true,
                    "screener-currency": true
                };
                currentSettings[value] = opt.classList.contains("selected");
                this.#setStorage(`columnSettings_${this.state.storageKey}`, currentSettings);
                this.#applyCheckboxSettings(currentSettings);
                this.#renderTable(true);
                if (typeof webSocketService !== "undefined" && typeof webSocketService.subscribeAllSymbols === "function") {
                    webSocketService.subscribeAllSymbols();
                }
            });
        });
    }
    #applyTableViewSetting(setting) {
        switch (setting) {
          case "screener-ticker":
            this.CONFIG.SHOW_TICKER = true;
            this.CONFIG.SHOW_DESCRIPTION = false;
            break;

          case "screener-description":
            this.CONFIG.SHOW_TICKER = false;
            this.CONFIG.SHOW_DESCRIPTION = true;
            break;

          case "screener-ticker-description":
          default:
            this.CONFIG.SHOW_TICKER = true;
            this.CONFIG.SHOW_DESCRIPTION = true;
            break;
        }
    }
    #applyCheckboxSettings(settings) {
        this.CONFIG.SHOW_LOGO = settings["screener-logo"] !== false;
        this.CONFIG.SHOW_CURRENCY = settings["screener-currency"] !== false;
    }
    #applyPriceAnimationSetting(setting) {
        if (typeof webSocketService === "undefined") return;
        this.#cleanAnimationClasses();
        switch (setting) {
          case "animation-full":
            webSocketService.config.animateFullValue = true;
            webSocketService.config.animateBgFlash = false;
            webSocketService.config.animationDuration = 1500;
            break;

          case "animation-bg":
            webSocketService.config.animateFullValue = true;
            webSocketService.config.animateBgFlash = true;
            webSocketService.config.animationDuration = 1500;
            break;

          case "animation-none":
            webSocketService.config.animationDuration = 0;
            webSocketService.config.animateBgFlash = false;
            break;

          case "animation-partial":
          default:
            webSocketService.config.animateFullValue = false;
            webSocketService.config.animateBgFlash = false;
            webSocketService.config.animationDuration = 1500;
            break;
        }
    }
    #cleanAnimationClasses() {
        if (!this.dom.tableBody) return;
        const bgElements = this.dom.tableBody.querySelectorAll(".fx-positive-bg, .fx-negative-bg");
        bgElements.forEach(el => {
            el.classList.remove("fx-positive-bg", "fx-negative-bg");
        });
        const textElements = this.dom.tableBody.querySelectorAll(".fx-positive, .fx-negative");
        textElements.forEach(el => {
            el.classList.remove("fx-positive", "fx-negative");
        });
    }
    #updateSelectUI(selectId, storedValue) {
        if (!storedValue) return;
        const selectEl = this.#q("#" + selectId);
        if (!selectEl) return;
        const option = selectEl.querySelector(`.select-option[data-value="${storedValue}"]`);
        if (option) {
            selectEl.querySelectorAll(".select-option").forEach(opt => {
                opt.classList.remove("selected");
            });
            option.classList.add("selected");
            const optionHTML = option.getAttribute("data-html") || option.innerHTML;
            const triggerText = selectEl.querySelector(".select-trigger-text");
            if (triggerText) triggerText.innerHTML = optionHTML;
        }
    }
    #showErrorState(customMessage = null) {
        const container = this.dom.tableContainer;
        const searchTerm = this.state.currentSearchTerm;
        let message = this.#t("no_match", "No symbols match your criteria");
        if (customMessage) {
            message = customMessage;
        } else if (searchTerm) {
            message = this.#t("no_results_for", 'No results found for "{term}"', {
                term: searchTerm
            });
        } else if (this.state.headerFilterActive) {
            message = this.#t("no_watchlist", "No symbols in your watchlist");
        }
        const existingError = this.#q(".empty-error-message");
        if (existingError) existingError.remove();
        const errorHTML = `\n            <div class="empty-error-message text-center text-muted py-5">\n                <div class="mb-3">\n                    <svg width="200" height="200" fill="none" stroke="none">\n                        <use href="#magnifier-char"></use>\n                    </svg>\n                </div>\n                <div class="fw-medium mb-2">${message}</div>\n            </div>\n        `;
        if (container) {
            container.insertAdjacentHTML("afterend", errorHTML);
        }
        if (this.dom.tableBody) {
            this.dom.tableBody.innerHTML = "";
        }
    }
    #t(key, fallback = "", replacements = {}) {
        let text = getLang(`screener_ui.${key}`, fallback);
        Object.keys(replacements).forEach(placeholder => {
            text = text.replace(`{${placeholder}}`, replacements[placeholder]);
        });
        return text;
    }
    #getPresetMetrics(presetName) {
        if (presetName === "custom") {
            return this.state.selectedMetrics;
        }
        if (this.PRESETS[presetName]) {
            return this.PRESETS[presetName].filter(metric => {
                const metricConfig = this.METRICS[metric];
                if (!metricConfig) {
                    return false;
                }
                if (metricConfig.exclude_from_column === true) {
                    return false;
                }
                if (metricConfig.assets && !metricConfig.assets.includes(this.CONFIG.CURRENT_ASSET_TYPE)) {
                    return false;
                }
                return true;
            });
        }
        const metrics = [];
        const className = `preset-${presetName}`;
        const tags = this.dom.metricTags;
        const len = tags.length;
        for (let i = 0; i < len; i++) {
            const tag = tags[i];
            if (tag.classList.contains(className)) {
                metrics.push(tag.getAttribute("data-metric"));
            }
        }
        return metrics;
    }
    reload() {
        this.#loadApiData();
    }
    search(term) {
        this.state.currentSearchTerm = term;
        if (this.dom.searchInput) {
            this.dom.searchInput.value = term;
        }
        this.#smartReload();
    }
    applyFilters(filtersObject) {
        this.state.activeFilters = filtersObject || {};
        this.reload();
    }
    addFavorite(ticker, color = "orange") {
        this.state.favorites[ticker] = color;
        this.#saveFavorites();
        this.#applyStoredFavorites();
    }
    removeFavoritePublic(ticker) {
        this.#removeFavorite(ticker);
    }
    setPreset(preset) {
        this.#applyColumnPreset(preset);
        this.#applyColumnChanges();
    }
    getState() {
        return {
            ...this.state,
            config: this.CONFIG,
            assetType: this.CONFIG.CURRENT_ASSET_TYPE
        };
    }
}

export { Screener };

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