class InfiniteMarquee {
    constructor(config = {}) {
        this._deps = {
            sendPostRequest: config.sendPostRequest || (typeof sendPostRequest !== "undefined" ? sendPostRequest : null),
            addThousandSeparator: config.addThousandSeparator || (typeof addThousandSeparator !== "undefined" ? addThousandSeparator : v => v),
            getValueStyle: config.getValueStyle || (typeof getValueStyle !== "undefined" ? getValueStyle : () => ""),
            buildDynamicUrl: config.buildDynamicUrl || (typeof buildDynamicUrl !== "undefined" ? buildDynamicUrl : () => "#"),
            generateCoinLogoHtml: config.generateCoinLogoHtml || (typeof generateCoinLogoHtml !== "undefined" ? generateCoinLogoHtml : null),
            convertNumberToShortForm: config.convertNumberToShortForm || (typeof convertNumberToShortForm !== "undefined" ? convertNumberToShortForm : v => v),
            formatDateTime: config.formatDateTime || (typeof formatDateTime !== "undefined" ? formatDateTime : v => v),
            getLang: config.getLang || (typeof getLang !== "undefined" ? getLang : (k, fb) => fb)
        };
        this.rootNode = config.rootNode || document;
        this._webSocketInstance = config.webSocketInstance || null;
        this.apiEndpoint = config.apiEndpoint || (typeof API_ENDPOINT !== "undefined" ? API_ENDPOINT : "");
        this.config = {
            containerId: config.containerId || "marquee-container",
            symbols: config.symbols || [ "BINANCE:BTCUSD", "BINANCE:ETHUSD" ],
            metrics: (config.metrics || [ "active.c", "active.ch", "active.chp", "spread" ]).slice(0, 4),
            showSymbol: config.showSymbol !== false,
            showExchange: config.showExchange || false,
            showFlag: config.showFlag !== false,
            showFullName: config.showFullName !== false,
            showMetricLabel: config.showMetricLabel !== false,
            maxSymbols: config.maxSymbols || 10
        };
        if (!this.config.showSymbol && !this.config.showFullName) {
            this.config.showSymbol = true;
        }
        const dirEl = this.rootNode === document ? document.documentElement : this.rootNode.querySelector("[dir]") || document.documentElement;
        const isPageRTL = dirEl.getAttribute("dir") === "rtl";
        this._isLayoutRTL = isPageRTL;
        const defaultDirection = isPageRTL ? "ltr" : "rtl";
        Object.assign(this.config, {
            timePeriod: config.timePeriod || "1D",
            speed: config.speed || 30,
            direction: config.direction || defaultDirection,
            pauseOnHover: config.pauseOnHover !== false,
            autoStart: config.autoStart !== false,
            stopMarquee: config.stopMarquee || false,
            uiTemplate: config.uiTemplate || "ticker-block",
            cardStyle: config.cardStyle || "card",
            itemSpacing: config.itemSpacing || 10,
            enableLinks: config.enableLinks !== false,
            linkTarget: config.linkTarget || "_self",
            linkUrl: config.linkUrl || "",
            observerThreshold: config.observerThreshold || .1,
            observerRootMargin: config.observerRootMargin || "100px"
        });
        this.container = null;
        this.marqueeContent = null;
        this.data = [];
        this.animationId = null;
        this.currentPosition = 0;
        this.isAnimating = false;
        this.isPaused = false;
        this.updateTimer = null;
        this.observer = null;
        this.cachedContentWidth = 0;
        this.#setupObserver();
    }
    #setupObserver() {
        const container = this.rootNode.querySelector(`#${this.config.containerId}`);
        if (!container) {
            console.warn("InfiniteMarquee: container not found:", this.config.containerId);
            return;
        }
        this.observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.#init();
                    this.observer.disconnect();
                }
            });
        }, {
            threshold: this.config.observerThreshold,
            rootMargin: this.config.observerRootMargin
        });
        this.observer.observe(container);
    }
    async #init() {
        try {
            this.container = this.rootNode.querySelector(`#${this.config.containerId}`);
            if (!this.container) {
                throw new Error(`Container with id "${this.config.containerId}" not found`);
            }
            this.#setupContainer();
            await this.#loadData();
            this.#render();
            const wsInstance = this._webSocketInstance || (typeof webSocketService !== "undefined" ? webSocketService : null);
            if (wsInstance && typeof wsInstance.subscribeAllSymbols === "function") {
                wsInstance.subscribeAllSymbols();
            }
        } catch (error) {
            console.error("InfiniteMarquee init error:", error);
        }
    }
    #setupContainer() {
        this.container.classList.add("infinite-marquee-wrapper");
        const marqueeContainer = document.createElement("div");
        marqueeContainer.className = "marquee-container";
        marqueeContainer.innerHTML = '<div class="marquee-content"></div>';
        this.container.appendChild(marqueeContainer);
        this.marqueeContent = this.container.querySelector(".marquee-content");
        if (this.config.pauseOnHover) {
            this.container.addEventListener("mouseenter", () => this.#pause());
            this.container.addEventListener("mouseleave", () => this.#resume());
        }
    }
    #hideSkeleton() {
        this.container.parentElement?.removeAttribute("aria-busy");
        const skeleton = this.container.parentElement?.querySelector(".marquee-skeleton-wrapper");
        if (skeleton) {
            skeleton.remove();
        }
    }
    async #loadData() {
        try {
            const postData = {
                symbol: this.config.symbols.join(","),
                type: "global",
                columns: "perf.market_cap,profile.currency,active.c,active.ch,active.chp,active.v,active.a,active.b,perf.24h_vol_cmc,active.o,active.h,active.l,profile,ticker,meta.blockchain_id",
                period: this.config.timePeriod,
                per_page: this.config.maxSymbols || 10
            };
            const result = await this._deps.sendPostRequest(`${this.apiEndpoint}forex/advance/`, postData);
            if (result.status && result.response) {
                const symbolOrder = this.config.symbols.map(s => s.toUpperCase());
                this.data = result.response.sort((a, b) => {
                    const indexA = symbolOrder.indexOf(a.ticker?.toUpperCase() || "");
                    const indexB = symbolOrder.indexOf(b.ticker?.toUpperCase() || "");
                    return indexA - indexB;
                });
            } else {
                throw new Error("Invalid API response format");
            }
        } catch (error) {
            console.error("InfiniteMarquee loadData error:", error);
        }
    }
    #render() {
        this.#hideSkeleton();
        if (!this.data || this.data.length === 0) {
            this.marqueeContent.innerHTML = '<div class="no-data">No data available</div>';
            return;
        }
        this.marqueeContent.innerHTML = "";
        if (this.config.itemSpacing > 0) {
            this.marqueeContent.style.setProperty("--item-spacing", `${this.config.itemSpacing}px`);
        }
        if (this.config.stopMarquee) {
            this.#renderStaticItems();
        } else {
            if (this._isLayoutRTL) {
                const mc = this.marqueeContent.parentElement;
                if (mc) mc.style.direction = "ltr";
                this.marqueeContent.style.direction = "ltr";
            }
            const fragment = document.createDocumentFragment();
            const firstSet = [];
            this.data.forEach(item => {
                const element = this.#createMarqueeItem(item);
                firstSet.push(element);
                fragment.appendChild(element);
            });
            this.marqueeContent.appendChild(fragment);
            requestAnimationFrame(() => {
                const containerWidth = this.container.offsetWidth;
                const itemWidth = (firstSet[0]?.offsetWidth || 100) + this.config.itemSpacing * 2;
                const totalItemsWidth = this.data.length * itemWidth;
                const duplicateCount = Math.max(3, Math.ceil(containerWidth * 3 / totalItemsWidth));
                const dupFragment = document.createDocumentFragment();
                for (let i = 1; i < duplicateCount; i++) {
                    this.data.forEach(item => {
                        dupFragment.appendChild(this.#createMarqueeItem(item));
                    });
                }
                this.marqueeContent.appendChild(dupFragment);
                this.duplicateCount = duplicateCount;
                const scrollWidth = this.marqueeContent.scrollWidth;
                const cachedWidth = scrollWidth / duplicateCount;
                const position = this.config.direction === "ltr" ? -cachedWidth : 0;
                this.cachedContentWidth = cachedWidth;
                this.currentPosition = position;
                this.marqueeContent.style.transform = `translateX(${position}px)`;
                if (this.config.autoStart && !this.config.stopMarquee && !this.isAnimating) {
                    this.#start();
                }
            });
        }
        if (this.config.stopMarquee) {
            this.currentPosition = 0;
            if (this.marqueeContent) {
                this.marqueeContent.style.transform = `translateX(0px)`;
            }
        }
    }
    #renderStaticItems() {
        const fragment = document.createDocumentFragment();
        let firstItem = null;
        this.data.forEach((item, index) => {
            const element = this.#createMarqueeItem(item);
            if (index === 0) firstItem = element;
            fragment.appendChild(element);
        });
        this.marqueeContent.appendChild(fragment);
        requestAnimationFrame(() => {
            const containerWidth = this.container.offsetWidth;
            const contentWidth = this.marqueeContent.scrollWidth;
            const marqueeContainer = this.container.querySelector(".marquee-container");
            if (contentWidth <= containerWidth) {
                this.marqueeContent.classList.add("bordered-spacing");
            } else if (marqueeContainer) {
                marqueeContainer.classList.add("scrollable-content");
            }
            if (this.config.cardStyle === "bordered" && firstItem) {
                firstItem.classList.add("first-item-no-border");
            }
        });
    }
    #createMarqueeItem(item) {
        const element = this.config.enableLinks ? document.createElement("a") : document.createElement("div");
        element.classList.add("market-item");
        if (item.ticker) {
            Object.assign(element.dataset, {
                ticker: item.ticker,
                tickerPersistent: "1",
                timeperiod: this.config.timePeriod
            });
        }
        if (this.config.enableLinks) {
            const symbol = item.ticker ? item.ticker.split(":")[1] || item.ticker : "";
            const exchange = item.profile?.exchange || (item.ticker ? item.ticker.split(":")[0] : "");
            const type = item.profile?.type || "";
            const name = item.profile?.name || symbol;
            const url = this._deps.buildDynamicUrl(this.config.linkUrl || null, {
                symbol: symbol,
                exchange: exchange,
                type: type,
                name: name
            });
            element.href = url;
            if (this.config.linkTarget === "_blank") {
                element.target = "_blank";
                element.rel = "noopener noreferrer";
            }
        }
        switch (this.config.uiTemplate) {
          case "inline":
            element.innerHTML = this.#templateInline(item);
            element.classList.add("ticker-block-wrapper", "ticker-inline-mode");
            if (this.config.cardStyle === "bordered") {
                element.classList.add("ticker-bordered-style");
            }
            break;

          case "ticker-block":
            element.innerHTML = this.#templateTickerBlock(item);
            element.classList.add("ticker-block-wrapper");
            if (this.config.cardStyle === "bordered") {
                element.classList.add("ticker-bordered-style");
            }
            break;

          default:
            break;
        }
        return element;
    }
    #templateTickerBlock(item) {
        const exchange = this.config.showExchange ? item.profile?.exchange || "" : "";
        const symbol = this.config.showSymbol ? this.#getSymbolName(item) : "";
        const fullName = this.config.showFullName ? item.profile?.name || "" : "";
        let displaySymbol = "";
        let displayName = fullName;
        if (this.config.showSymbol && this.config.showExchange && exchange) {
            displaySymbol = `${exchange}:${symbol}`;
        } else if (this.config.showSymbol) {
            displaySymbol = symbol;
        } else if (this.config.showExchange && exchange) {
            displaySymbol = exchange;
        } else if (!this.config.showSymbol && !this.config.showExchange && fullName) {
            displaySymbol = fullName;
            displayName = "";
        }
        let logoHtml = "";
        if (this.config.showFlag) {
            if (this._deps.generateCoinLogoHtml && item.profile) {
                logoHtml = "<div class='search-result-icon-container'>" + this._deps.generateCoinLogoHtml(item.profile) + "</div>";
            }
        }
        let topPriceHtml = "";
        const metricsToShow = this.config.metrics.length === 1 ? [] : this.config.metrics.length === 2 ? this.config.metrics : this.config.metrics.slice(1);
        if (this.config.metrics.length === 1 || this.config.metrics.length >= 3) {
            const firstMetric = this.config.metrics[0];
            const firstValue = this.#getMetricValue(item, firstMetric);
            topPriceHtml = `\n                <div class="ticker-price-main">\n                    <span>${this.#formatMetric(firstMetric, firstValue, item)}</span>\n                </div>\n            `;
        }
        let metricsGroupHtml = "";
        metricsToShow.forEach(metric => {
            if (metric.toLowerCase().includes("name")) {
                return;
            }
            const value = this.#getMetricValue(item, metric);
            const formatted = this.#formatMetric(metric, value, item);
            const label = this.config.showMetricLabel ? this.#getMetricLabel(metric) : "";
            metricsGroupHtml += `\n                <div class="ticker-metric-item">\n                    ${this.config.showMetricLabel ? `<small>${label}</small>` : ""}\n                    <span>${formatted}</span>\n                </div>\n            `;
        });
        const titleHtml = displaySymbol || displayName ? `<div class="ticker-title">\n            ${displaySymbol ? `<strong>${displaySymbol}</strong>` : ""}\n            ${displayName ? `<small>${displayName}</small>` : ""}\n        </div>` : "";
        return `\n            <div class="ticker-block-card">\n                <div class="ticker-top">\n                    <div class="ticker-heading">\n                        ${logoHtml}\n                        ${titleHtml}\n                    </div>\n                    ${topPriceHtml}\n                </div>\n                <div class="ticker-metrics-row">${metricsGroupHtml}</div>\n            </div>\n        `;
    }
    #templateInline(item) {
        const exchange = this.config.showExchange ? item.profile?.exchange || "" : "";
        const symbol = this.config.showSymbol ? this.#getSymbolName(item) : "";
        const fullName = this.config.showFullName ? item.profile?.name || "" : "";
        let displaySymbol = "";
        let displayName = fullName;
        if (this.config.showSymbol && this.config.showExchange && exchange) {
            displaySymbol = `${exchange}:${symbol}`;
        } else if (this.config.showSymbol) {
            displaySymbol = symbol;
        } else if (this.config.showExchange && exchange) {
            displaySymbol = exchange;
        } else if (!this.config.showSymbol && !this.config.showExchange && fullName) {
            displaySymbol = fullName;
            displayName = "";
        }
        let logoHtml = "";
        if (this.config.showFlag && this._deps.generateCoinLogoHtml && item.profile) {
            logoHtml = "<div class='search-result-icon-container'>" + this._deps.generateCoinLogoHtml(item.profile) + "</div>";
        }
        const metrics = this.config.metrics.slice(0, 3);
        let priceHtml = "";
        let changeHtml = "";
        if (metrics.length > 0) {
            const firstValue = this.#getMetricValue(item, metrics[0]);
            priceHtml = `<div class="ticker-price-main"><span>${this.#formatMetric(metrics[0], firstValue, item)}</span></div>`;
        }
        if (metrics.length > 1) {
            const parts = metrics.slice(1).map(m => {
                const val = this.#getMetricValue(item, m);
                return this.#formatMetric(m, val, item);
            });
            if (parts.length > 1) {
                const last = parts.pop();
                changeHtml = `<div class="ticker-change-inline"><span>${parts.join(" ")} (${last})</span></div>`;
            } else {
                changeHtml = `<div class="ticker-change-inline"><span>${parts[0]}</span></div>`;
            }
        }
        const titleHtml = displaySymbol || displayName ? `<div class="ticker-title">\n            ${displaySymbol ? `<strong>${displaySymbol}</strong>` : ""}\n            ${displayName ? `<small>${displayName}</small>` : ""}\n        </div>` : "";
        return `\n            <div class="ticker-block-card">\n                <div class="ticker-top">\n                    <div class="ticker-heading">\n                        ${logoHtml}\n                        ${titleHtml}\n                    </div>\n                    ${priceHtml}\n                    ${changeHtml}\n                </div>\n            </div>\n        `;
    }
    #getSymbolName(item) {
        if (item.profile && item.profile.symbol) {
            return item.profile.symbol || "—";
        }
        return item.ticker ? item.ticker.split(":")[1] || item.ticker : "—";
    }
    #getMetricValue(item, path) {
        if (path === "active.v") {
            const cmcVol = parseFloat(item.perf?.["24h_vol_cmc"]);
            if (!isNaN(cmcVol) && cmcVol > 0) return cmcVol;
        }
        if (path === "spread" || path === "active.spread") {
            const ask = parseFloat(item.active?.a);
            const bid = parseFloat(item.active?.b);
            if (!isNaN(ask) && !isNaN(bid)) {
                return Math.abs(ask - bid);
            }
            return "—";
        }
        const keys = path.split(".");
        let value = item;
        for (const key of keys) {
            value = value?.[key];
            if (value === undefined || value === null) return "—";
        }
        return value;
    }
    #formatMetric(metric, value, item = null) {
        if (value === "N/A" || value === null || value === undefined) return "—";
        const getDynamicClass = metricKey => {
            if (!item || !item.ticker) return "";
            const metricLetter = metricKey.toUpperCase();
            const tickerClass = item.ticker.replace(/:/g, "_").replace(/\./g, "_");
            return `${tickerClass}_${metricLetter}_${this.config.timePeriod}`.toUpperCase();
        };
        if (metric === "update" || metric.includes("update")) {
            const formatted = this._deps.formatDateTime(value);
            const dynamicClass = getDynamicClass("UPDATE");
            return dynamicClass ? `<span class="${dynamicClass}">${formatted}</span>` : formatted;
        }
        if (metric === "spread" || metric.includes("spread")) {
            const num = parseFloat(value);
            if (isNaN(num)) return "—";
            return num.toFixed(4);
        }
        if (metric.includes("ch") || metric.includes("chp")) {
            const colorClass = this._deps.getValueStyle(value, "class");
            const sign = value > 0 ? "+" : value < 0 ? "−" : "";
            const absValue = Math.abs(value);
            const formattedValue = metric.includes("chp") ? convertNumberToShortForm(absValue, true, 2) + "%" : convertNumberToShortForm(absValue);
            const dynamicClass = getDynamicClass(metric.includes("chp") ? "CHP" : "CH");
            const combinedClasses = dynamicClass ? `${colorClass} ${dynamicClass}` : colorClass;
            return `<span class="${combinedClasses}"><span class="sign">${sign}</span>${formattedValue}</span>`;
        }
        if (metric.includes("percent")) {
            return this._deps.addThousandSeparator(value) + "%";
        }
        const askBidMetrics = [ "active.a", "active.b" ];
        if (askBidMetrics.includes(metric)) {
            const formattedValue = this._deps.addThousandSeparator(value);
            const metricKey = metric.split(".").pop().toUpperCase();
            const dynamicClass = getDynamicClass(metricKey);
            return dynamicClass ? `<span class="fx-socket-field ${dynamicClass}">${formattedValue}</span>` : formattedValue;
        }
        if (metric === "active.v") {
            const numValue = parseFloat(value);
            if (isNaN(numValue)) return "—";
            const formattedValue = this._deps.convertNumberToShortForm(value);
            const dynamicClass = getDynamicClass("v");
            return dynamicClass ? `<span class="fx-socket-field ${dynamicClass}">${formattedValue}</span>` : formattedValue;
        }
        const priceMetrics = [ "active.o", "active.h", "active.l", "active.c" ];
        if (priceMetrics.includes(metric)) {
            const numValue = parseFloat(value);
            if (isNaN(numValue)) return "—";
            const formattedValue = numValue < 8e5 ? this._deps.addThousandSeparator(value) : this._deps.convertNumberToShortForm(value);
            if (numValue < 8e5) {
                const metricLetter = metric.split(".").pop().toUpperCase();
                const dynamicClass = getDynamicClass(metricLetter);
                return dynamicClass ? `<span class="fx-socket-field ${dynamicClass}">${formattedValue}</span>` : formattedValue;
            }
            return formattedValue;
        }
        return this._deps.addThousandSeparator(value);
    }
    #getMetricLabel(metric) {
        const gl = this._deps.getLang;
        const labels = {
            "update": gl("marquee.updated", "Updated"),
            "spread": gl("marquee.spread", "Spread"),
            "active.spread": gl("marquee.spread", "Spread"),
            "active.c": gl("marquee.price", "Price"),
            "active.o": gl("marquee.open", "Open"),
            "active.h": gl("marquee.high", "High"),
            "active.l": gl("marquee.low", "Low"),
            "active.ch": gl("marquee.change", "Change"),
            "active.chp": gl("marquee.change_percent", "Change%"),
            "active.v": gl("marquee.volume", "Vol"),
            "active.a": gl("marquee.ask", "Ask"),
            "active.b": gl("marquee.bid", "Bid"),
            "previous.c": gl("marquee.prev_close", "Prev"),
            "previous.o": gl("marquee.prev_open", "P.Open"),
            "previous.h": gl("marquee.prev_high", "P.High"),
            "previous.l": gl("marquee.prev_low", "P.Low"),
            "previous.chp": gl("marquee.prev_change_percent", "P.Change%")
        };
        return labels[metric] || metric.split(".").pop().toUpperCase();
    }
    #start() {
        if (this.config.stopMarquee) return;
        this.isAnimating = true;
        this.isPaused = false;
        this.#animate();
    }
    #stop() {
        this.isAnimating = false;
        this.isPaused = false;
        if (this.animationId) {
            cancelAnimationFrame(this.animationId);
            this.animationId = null;
        }
    }
    #pause() {
        if (!this.config.pauseOnHover) return;
        this.isPaused = true;
    }
    #resume() {
        if (!this.config.pauseOnHover) return;
        this.isPaused = false;
    }
    #animate() {
        if (!this.isAnimating) return;
        if (!this.isPaused) {
            const contentWidth = this.cachedContentWidth;
            if (this.config.direction === "rtl") {
                this.currentPosition -= this.config.speed / 60;
                if (Math.abs(this.currentPosition) >= contentWidth) {
                    this.currentPosition += contentWidth;
                }
            } else {
                this.currentPosition += this.config.speed / 60;
                if (this.currentPosition >= 0) {
                    this.currentPosition = -contentWidth;
                }
            }
            this.marqueeContent.style.transform = `translateX(${this.currentPosition}px)`;
        }
        this.animationId = requestAnimationFrame(() => this.#animate());
    }
    updateConfig(newConfig) {
        Object.assign(this.config, newConfig);
        this.#render();
        if (this.isAnimating) {
            this.#stop();
            if (!this.config.stopMarquee && this.config.autoStart) {
                this.#start();
            }
        }
    }
    destroy() {
        this.#stop();
        if (this.observer) {
            this.observer.disconnect();
            this.observer = null;
        }
        if (this.container) {
            this.container.innerHTML = "";
        }
        this.data = [];
        this.container = null;
        this.marqueeContent = null;
    }
}

export { InfiniteMarquee };

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