"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwelveDataClient = void 0;
const HttpClient_js_1 = require("./HttpClient.js");
function normalizeCountryName(name) {
    return name
        .trim()
        .toLowerCase()
        .replace(/\s+/g, " ")
        .replace(/\b\w/g, (char) => char.toUpperCase());
}
const BASE_URL = "https://api.twelvedata.com";
const CMC_BASE_URL = "https://pro-api.coinmarketcap.com";
const CACHE_TTL_MS = 5 * 60 * 1000;
// Twelve Data's free plan allows 8 API credits per minute. The /quote endpoint consumes
// one credit per symbol in a batch, so we must cap the batch size to avoid rate-limit errors.
const MAX_QUOTE_SYMBOLS = 8;
const FOREX_PAIRS = [
    { symbol: "EUR/USD", base: "EUR", quote: "USD" },
    { symbol: "GBP/USD", base: "GBP", quote: "USD" },
    { symbol: "USD/JPY", base: "USD", quote: "JPY" },
    { symbol: "USD/CHF", base: "USD", quote: "CHF" },
    { symbol: "AUD/USD", base: "AUD", quote: "USD" },
    { symbol: "USD/CAD", base: "USD", quote: "CAD" },
    { symbol: "NZD/USD", base: "NZD", quote: "USD" },
    { symbol: "EUR/GBP", base: "EUR", quote: "GBP" }
];
const CRYPTO_PAIRS = [
    { symbol: "BTC/USD", base: "BTC", quote: "USD" },
    { symbol: "ETH/USD", base: "ETH", quote: "USD" },
    { symbol: "BNB/USD", base: "BNB", quote: "USD" },
    { symbol: "SOL/USD", base: "SOL", quote: "USD" },
    { symbol: "XRP/USD", base: "XRP", quote: "USD" },
    { symbol: "ADA/USD", base: "ADA", quote: "USD" },
    { symbol: "DOGE/USD", base: "DOGE", quote: "USD" },
    { symbol: "MATIC/USD", base: "MATIC", quote: "USD" },
    { symbol: "LTC/USD", base: "LTC", quote: "USD" },
    { symbol: "DOT/USD", base: "DOT", quote: "USD" }
];
const STOCK_COUNTRY_LISTS = {
    "United States": ["AAPL", "MSFT", "GOOGL", "AMZN", "JPM", "JNJ", "PG", "V", "WMT", "UNH", "HD", "MA", "BAC", "PFE", "KO"],
    "United Kingdom": ["SHEL", "AZN", "HSBA", "ULVR", "BP", "RIO", "DGE", "GSK", "REL", "LSEG", "BARC", "NWG", "VOD", "CNA", "SGE"],
    Germany: ["SAP", "SIE", "ALV", "BAS", "DTE", "MUV2", "HEN3", "BMW", "MBG", "ADS", "IFX", "FRE", "MRK", "SY1", "ZAL"],
    France: ["MC", "OR", "SAN", "AIR", "BNP", "ACA", "CS", "CAP", "DSY", "EL", "KER", "RI", "STMPA", "SU", "VIE"],
    Japan: ["7203", "6758", "6861", "8306", "9984", "9432", "9433", "9434", "7267", "7951", "8001", "8058", "8308", "8411", "8766"],
    Canada: ["RY", "TD", "SHOP", "ENB", "BN", "CP", "CNQ", "BMO", "CM", "BNS", "MFC", "TRI", "CSU", "ATD", "WN"],
    Australia: ["CBA", "BHP", "CSL", "NAB", "WBC", "ANZ", "WDS", "MQG", "FMG", "RIO", "TLS", "WOW", "GMG", "ALL", "SUN"],
    "Hong Kong": ["0700", "9988", "3690", "2318", "1299", "0005", "0388", "0883", "0981", "1211", "2269", "2313", "2331", "2382", "3692"],
    India: ["RELIANCE", "TCS", "HDFCBANK", "INFY", "ICICIBANK", "HINDUNILVR", "SBIN", "BHARTIARTL", "ITC", "KOTAKBANK", "LT", "BAJFINANCE", "ASIANPAINT", "AXISBANK", "MARUTI"],
    Brazil: ["PETR4", "VALE3", "ITUB4", "BBDC4", "ABEV3", "WEGE3", "RENT3", "BBAS3", "SUZB3", "BPAC11", "RADL3", "TOTS3", "LREN3", "SBSP3", "GGBR4"],
    "South Africa": ["SHP", "FSR", "NED", "SBK", "ABG", "INL", "MTN", "VOD", "SLM", "REM", "GFI", "NPN", "BHG", "IMP", "S32"],
    Mexico: ["AMXL", "FEMSAUBD", "GMEXICOB", "WALMEX", "TLEVISACPO", "GFNORTEO", "CEMEXCPO", "KIMBERA", "GAPB", "BIMBOA", "ASURB", "OMAB", "PINFRA", "GRUMAB", "ALPEKA"],
    Singapore: ["D05", "O39", "U11", "Z74", "C38U", "A17U", "G13", "S63", "Y92", "M44U", "C07", "H78", "T39", "BS6", "S58"],
    Switzerland: ["NESN", "ROG", "ZURN", "ABBN", "UBSG", "CSGN", "SREN", "SCMN", "GIVN", "PGHN", "LONN", "SIKA", "TEMN", "UHR", "STMN"],
    // Twelve Data does not cover most frontier/emerging African markets, so we keep curated local blue-chip lists.
    Nigeria: ["DANGCEM", "MTNN", "GTCO", "ZENITHBANK", "ACCESSCORP", "UBA", "FBNH", "BUACEMENT", "TRANSCORP", "NESTLE", "NB", "WAPCO", "OANDO", "FIDELITYBK", "SEPLAT"],
    Ghana: ["MTNGH", "EGH", "GCB", "SCB", "CAL", "GOIL", "TOTAL", "UNIL", "GGBL", "SIC", "EGL", "PBC", "FML", "BOPP", "ACCESS"],
    Kenya: ["SAFARICOM", "KCB", "EQUITY", "COOP", "ABSA", "EABL", "BAT", "BAMBURI", "KPLC", "KENGEN", "JUBILEE", "CIC", "ARM", "NBK", "SCANGROUP"],
    Egypt: ["COMI", "HRHO", "EAST", "SWDY", "ETEL", "ORAS", "PHDC", "HELI", "ISPH", "CIRA", "EFID", "MNHD", "AMER", "TMGH", "CIEB"],
    Morocco: ["ATW", "BCP", "IAM", "LAF", "LYDEC", "MNG", "MRSA", "ONA", "SMI", "TQM", "ATL", "CCB", "ADI", "LHM", "WAA"],
    "Other": ["NOK", "ERIC", "SAND", "AZA", "FORTUM", "NDA-FI", "ORNBV", "KBC", "ING", "ASML", "ADYEN", "PRX", "DSM", "AKZA", "HEIA"]
};
const COUNTRY_MARKET_INFO = {
    Nigeria: { exchange: "NGX", currency: "NGN" },
    Ghana: { exchange: "GSE", currency: "GHS" },
    Kenya: { exchange: "NSE", currency: "KES" },
    Egypt: { exchange: "EGX", currency: "EGP" },
    Morocco: { exchange: "CSE", currency: "MAD" }
};
class TwelveDataClient {
    apiKey;
    coinMarketCapApiKey;
    http = new HttpClient_js_1.HttpClient({ baseUrl: BASE_URL });
    forexCache;
    marketPairsCache = new Map();
    stocksCache = new Map();
    countrySymbolsCache = new Map();
    constructor(apiKey, coinMarketCapApiKey) {
        this.apiKey = apiKey;
        this.coinMarketCapApiKey = coinMarketCapApiKey;
    }
    async getForexPairs() {
        if (this.forexCache && this.forexCache.expiresAt > Date.now()) {
            return this.forexCache.pairs;
        }
        const pairs = this.apiKey ? await this.fetchForexPairs() : this.fallbackForexPairs();
        this.forexCache = {
            expiresAt: Date.now() + CACHE_TTL_MS,
            pairs
        };
        return pairs;
    }
    async getMarketPairs(type) {
        const cached = this.marketPairsCache.get(type);
        if (cached && cached.expiresAt > Date.now()) {
            return cached.pairs;
        }
        const definitions = type === "crypto" ? CRYPTO_PAIRS : FOREX_PAIRS;
        const pairs = type === "crypto"
            ? await this.fetchCryptoPairs(definitions)
            : this.apiKey
                ? await this.fetchForexExchangeRates(definitions)
                : this.fallbackMarketPairs(definitions);
        this.marketPairsCache.set(type, {
            expiresAt: Date.now() + CACHE_TTL_MS,
            pairs
        });
        return pairs;
    }
    async getStocks(scope) {
        const normalizedCountry = normalizeCountryName(scope.country);
        const cacheKey = `${normalizedCountry}:${scope.international}`;
        const cached = this.stocksCache.get(cacheKey);
        if (cached && cached.expiresAt > Date.now()) {
            return cached.quotes;
        }
        const quotes = this.apiKey ? await this.fetchStocks(scope) : await this.fallbackStocks(scope);
        this.stocksCache.set(cacheKey, {
            expiresAt: Date.now() + CACHE_TTL_MS,
            quotes
        });
        return quotes;
    }
    async fetchForexPairs() {
        if (!this.apiKey)
            return this.fallbackForexPairs();
        try {
            const symbols = FOREX_PAIRS.map((p) => p.symbol).join(",");
            const response = await this.http.get(`/exchange_rate?symbol=${encodeURIComponent(symbols)}&apikey=${this.apiKey}`);
            return FOREX_PAIRS.map((pair) => {
                const data = response[pair.symbol];
                const rate = data?.rate ? Number(data.rate) : undefined;
                return {
                    symbol: pair.symbol,
                    base: pair.base,
                    quote: pair.quote,
                    price: rate && Number.isFinite(rate) ? rate : this.simulateRate(pair.symbol)
                };
            });
        }
        catch {
            return this.fallbackForexPairs();
        }
    }
    fallbackForexPairs() {
        return FOREX_PAIRS.map((pair) => ({
            symbol: pair.symbol,
            base: pair.base,
            quote: pair.quote,
            price: this.simulateRate(pair.symbol)
        }));
    }
    async fetchForexExchangeRates(definitions) {
        if (!this.apiKey)
            return this.fallbackMarketPairs(definitions);
        try {
            const symbols = definitions.map((p) => p.symbol).join(",");
            const response = await this.http.get(`/exchange_rate?symbol=${encodeURIComponent(symbols)}&apikey=${this.apiKey}`);
            return definitions.map((pair) => {
                const data = response[pair.symbol];
                const rate = data?.rate ? Number(data.rate) : undefined;
                return {
                    symbol: pair.symbol,
                    base: pair.base,
                    quote: pair.quote,
                    price: rate && Number.isFinite(rate) ? rate : this.simulateRate(pair.symbol)
                };
            });
        }
        catch {
            return this.fallbackMarketPairs(definitions);
        }
    }
    async fetchCryptoPairs(definitions) {
        // Prefer CoinMarketCap for crypto because the project already has a CMC key.
        if (this.coinMarketCapApiKey) {
            try {
                const symbols = definitions.map((p) => p.base).join(",");
                const response = await fetch(`${CMC_BASE_URL}/v2/cryptocurrency/quotes/latest?symbol=${encodeURIComponent(symbols)}`, {
                    headers: {
                        "X-CMC_PRO_API_KEY": this.coinMarketCapApiKey,
                        Accept: "application/json"
                    }
                });
                if (response.ok) {
                    const json = (await response.json());
                    const data = json.data ?? {};
                    const mapped = definitions.map((pair) => {
                        const entry = data[pair.base]?.[0];
                        const price = entry?.quote?.USD?.price;
                        return {
                            symbol: pair.symbol,
                            base: pair.base,
                            quote: pair.quote,
                            price: price && Number.isFinite(price) ? price : this.simulateRate(pair.symbol),
                            change24h: entry?.quote?.USD?.percent_change_24h
                        };
                    });
                    if (mapped.every((p) => Number.isFinite(p.price))) {
                        return mapped;
                    }
                }
            }
            catch {
                // Fall through to Twelve Data or simulated fallback.
            }
        }
        if (!this.apiKey)
            return this.fallbackMarketPairs(definitions);
        // Twelve Data quote endpoint supports crypto pairs.
        try {
            const symbols = definitions.map((p) => p.symbol).join(",");
            const response = await this.http.get(`/quote?symbol=${encodeURIComponent(symbols)}&apikey=${this.apiKey}`);
            return definitions.map((pair) => {
                const data = response[pair.symbol];
                const price = data?.price ? Number(data.price) : undefined;
                return {
                    symbol: pair.symbol,
                    base: pair.base,
                    quote: pair.quote,
                    price: price && Number.isFinite(price) ? price : this.simulateRate(pair.symbol),
                    change24h: data?.percent_change ? Number(data.percent_change) : undefined
                };
            });
        }
        catch {
            return this.fallbackMarketPairs(definitions);
        }
    }
    fallbackMarketPairs(definitions) {
        return definitions.map((pair) => ({
            symbol: pair.symbol,
            base: pair.base,
            quote: pair.quote,
            price: this.simulateRate(pair.symbol)
        }));
    }
    simulateRate(symbol) {
        // Deterministic but realistic-looking rate based on symbol hash.
        const hash = Array.from(symbol).reduce((acc, char) => acc + char.charCodeAt(0), 0);
        const base = 0.8 + (hash % 100) / 100;
        return Number((base + Math.random() * 0.4).toFixed(5));
    }
    async fetchStocks(scope) {
        if (!this.apiKey)
            return this.fallbackStocks(scope);
        const symbols = await this.resolveStockSymbols(scope);
        if (symbols.length === 0)
            return this.fallbackStocks(scope);
        try {
            const quoteSymbols = symbols.slice(0, MAX_QUOTE_SYMBOLS);
            const response = await this.http.get(`/quote?symbol=${encodeURIComponent(quoteSymbols.join(","))}&apikey=${this.apiKey}`);
            const quotes = [];
            for (const symbol of quoteSymbols) {
                const data = response[symbol];
                if (!data)
                    continue;
                const price = data.price ? Number(data.price) : undefined;
                if (!price || !Number.isFinite(price))
                    continue;
                quotes.push({
                    symbol,
                    name: data.name ?? symbol,
                    price,
                    currency: data.currency ?? "USD",
                    exchange: data.exchange,
                    sector: this.inferSector(symbol),
                    changePercent: data.percent_change ? Number(data.percent_change) : undefined,
                    marketCap: this.estimateMarketCap(symbol, price)
                });
            }
            return quotes.length > 0 ? quotes : this.fallbackStocks(scope);
        }
        catch {
            return this.fallbackStocks(scope);
        }
    }
    async fallbackStocks(scope) {
        const symbols = await this.resolveStockSymbols(scope);
        const normalizedCountry = normalizeCountryName(scope.country);
        const marketInfo = COUNTRY_MARKET_INFO[normalizedCountry];
        return symbols.map((symbol) => ({
            symbol,
            name: symbol,
            price: this.simulateStockPrice(symbol),
            currency: marketInfo?.currency ?? "USD",
            exchange: marketInfo?.exchange ?? normalizedCountry,
            sector: this.inferSector(symbol),
            changePercent: Number((Math.random() * 6 - 2).toFixed(2)),
            marketCap: this.estimateMarketCap(symbol, this.simulateStockPrice(symbol)),
            isSimulated: true
        }));
    }
    async resolveStockSymbols(scope) {
        const normalizedCountry = normalizeCountryName(scope.country);
        const allCountries = Object.keys(STOCK_COUNTRY_LISTS);
        let sources;
        if (scope.international) {
            sources = allCountries.filter((c) => normalizeCountryName(c) !== normalizedCountry);
        }
        else {
            sources = [normalizedCountry];
        }
        const knownSymbols = sources.flatMap((country) => STOCK_COUNTRY_LISTS[country] ?? []);
        // For local scope with an unknown country, try to fetch symbols dynamically from Twelve Data.
        // We do NOT fall back to the generic "Other" list here, because a local request must return
        // stocks from the requested country/exchange. Returning unrelated tickers is misleading.
        if (!scope.international && knownSymbols.length === 0) {
            return this.fetchSymbolsForCountry(normalizedCountry);
        }
        // For international scope with an unknown country, include the generic "Other" list.
        if (scope.international && !allCountries.some((c) => normalizeCountryName(c) === normalizedCountry)) {
            const otherSymbols = STOCK_COUNTRY_LISTS["Other"] ?? [];
            return [...new Set([...knownSymbols, ...otherSymbols])];
        }
        return [...new Set(knownSymbols)];
    }
    async fetchSymbolsForCountry(country) {
        if (!this.apiKey)
            return [];
        const normalizedCountry = normalizeCountryName(country);
        const cached = this.countrySymbolsCache.get(normalizedCountry);
        if (cached && cached.expiresAt > Date.now())
            return cached.symbols;
        try {
            // Try a country-level lookup first.
            let symbols = await this.fetchSymbolsByCountry(normalizedCountry);
            // If the country lookup is empty, discover exchanges for that country and query each one.
            if (symbols.length === 0) {
                const exchangeCodes = await this.fetchExchangeCodesForCountry(normalizedCountry);
                for (const code of exchangeCodes) {
                    const exchangeSymbols = await this.fetchSymbolsByExchange(code);
                    symbols = [...symbols, ...exchangeSymbols];
                    if (symbols.length >= MAX_QUOTE_SYMBOLS)
                        break;
                }
                symbols = symbols.slice(0, MAX_QUOTE_SYMBOLS);
            }
            this.countrySymbolsCache.set(normalizedCountry, {
                expiresAt: Date.now() + CACHE_TTL_MS,
                symbols
            });
            return symbols;
        }
        catch {
            return [];
        }
    }
    async fetchSymbolsByCountry(country) {
        if (!this.apiKey)
            return [];
        try {
            const response = await this.http.get(`/stocks?country=${encodeURIComponent(country)}&apikey=${this.apiKey}`);
            return (response.data ?? [])
                .filter((item) => item.symbol)
                .slice(0, MAX_QUOTE_SYMBOLS)
                .map((item) => item.symbol);
        }
        catch {
            return [];
        }
    }
    async fetchSymbolsByExchange(exchange) {
        if (!this.apiKey)
            return [];
        const cacheKey = `exchange:${exchange}`;
        const cached = this.countrySymbolsCache.get(cacheKey);
        if (cached && cached.expiresAt > Date.now())
            return cached.symbols;
        try {
            const response = await this.http.get(`/stocks?exchange=${encodeURIComponent(exchange)}&apikey=${this.apiKey}`);
            const symbols = (response.data ?? [])
                .filter((item) => item.symbol)
                .slice(0, MAX_QUOTE_SYMBOLS)
                .map((item) => item.symbol);
            this.countrySymbolsCache.set(cacheKey, {
                expiresAt: Date.now() + CACHE_TTL_MS,
                symbols
            });
            return symbols;
        }
        catch {
            return [];
        }
    }
    async fetchExchangeCodesForCountry(country) {
        if (!this.apiKey)
            return [];
        const cacheKey = `exchanges:${country}`;
        const cached = this.countrySymbolsCache.get(cacheKey);
        if (cached && cached.expiresAt > Date.now())
            return cached.symbols;
        try {
            const response = await this.http.get(`/exchanges?country=${encodeURIComponent(country)}&apikey=${this.apiKey}`);
            const codes = (response.data ?? [])
                .filter((item) => item.code)
                .map((item) => item.code);
            this.countrySymbolsCache.set(cacheKey, {
                expiresAt: Date.now() + CACHE_TTL_MS,
                symbols: codes
            });
            return codes;
        }
        catch {
            return [];
        }
    }
    simulateStockPrice(symbol) {
        const hash = Array.from(symbol).reduce((acc, char) => acc + char.charCodeAt(0), 0);
        return Number(((hash % 500) + 10 + Math.random() * 100).toFixed(2));
    }
    inferSector(symbol) {
        const tech = ["AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA", "NVDA", "ADBE", "CRM", "ORCL", "SAP", "SIE", "IFX", "ASML", "ADYEN", "ZAL", "INFY", "TCS", "RELIANCE"];
        const finance = ["JPM", "V", "MA", "BAC", "HSBA", "BARC", "NWG", "BNP", "ACA", "8306", "RY", "TD", "BMO", "HDFCBANK", "ICICIBANK", "KOTAKBANK", "UBSG", "CSGN", "ING", "KBC"];
        const healthcare = ["JNJ", "PFE", "UNH", "AZN", "GSK", "SAN", "MRK", "FRE", "RELIANCE"];
        const energy = ["SHEL", "BP", "XOM", "ENB", "CNQ", "WDS", "PETR4", "VALE3"];
        const consumer = ["PG", "WMT", "KO", "ULVR", "DGE", "NESN", "MCD", "HD", "WMT", "WOW", "FEMSAUBD", "WALMEX"];
        const upper = symbol.toUpperCase();
        if (tech.includes(upper))
            return "Technology";
        if (finance.includes(upper))
            return "Financials";
        if (healthcare.includes(upper))
            return "Healthcare";
        if (energy.includes(upper))
            return "Energy";
        if (consumer.includes(upper))
            return "Consumer";
        return undefined;
    }
    estimateMarketCap(symbol, price) {
        const hash = Array.from(symbol).reduce((acc, char) => acc + char.charCodeAt(0), 0);
        const shares = 1_000_000_000 + (hash % 5_000_000_000);
        return Math.round(shares * price);
    }
}
exports.TwelveDataClient = TwelveDataClient;
