import { computed, ref, watch } from 'vue' import { getAllPrices } from '@/api/home' import type { Product } from '@/models/products' import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog' import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice' import { isKatodMesGroup, isMaftolMesGroup, isShamshMesGroup } from '@/utils/productDealMethod' import { getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@/utils/price' export type ChartMetalType = 'katod' | 'maftol' | 'shamsh' type ChartPoint = [number, number] /** Module-level chart state — one dataset shared by mobile and desktop PriceChart instances. */ const priceChartSeries = ref<{ name: string; data: ChartPoint[] }[]>([ { name: 'قیمت', data: [] }, ]) const rawChartPoints = ref([]) const chartSelection = ref('DAILY') const chartLoading = ref(false) const selectedChartMetal = ref('katod') const catalogProducts = getProductsCatalogRef() function resolveChartProducts(products: Product[]) { return { katod: findKatodProduct(products), maftol: findMaftolProduct(products), shamsh: findShamshProduct(products), } } /** نسبت مفتول به کاتد — reactive به قیمت لحظه‌ای و کاتالوگ محصولات */ const maftolChartRatio = computed(() => { void lastPrice.value?.price_last const { katod, maftol } = resolveChartProducts(catalogProducts.value) return deriveMetalChartRatio(maftol, katod) }) /** نسبت شمش مس به کاتد */ const shamshChartRatio = computed(() => { void lastPrice.value?.price_last const { katod, shamsh } = resolveChartProducts(catalogProducts.value) return deriveMetalChartRatio(shamsh, katod) }) const selectedMetalChartRatio = computed(() => { if (selectedChartMetal.value === 'maftol') { return maftolChartRatio.value } if (selectedChartMetal.value === 'shamsh') { return shamshChartRatio.value } return 0 }) let chartFetchPromise: Promise | null = null let chartInitialized = false let metalOffsetPromise: Promise | null = null let metalOffsetLoaded = false function findKatodProduct(products: Product[]) { return ( products.find((product) => isKatodMesGroup(product.group_type)) ?? products.find((product) => (product.title ?? '').includes('کاتد')) ) } function findMaftolProduct(products: Product[]) { return ( products.find((product) => isMaftolMesGroup(product.group_type)) ?? products.find((product) => (product.title ?? '').includes('مفتول')) ) } function findShamshProduct(products: Product[]) { return products.find((product) => isShamshMesGroup(product.group_type, product.title) ) } function applyChartRatio(basePrice: number, ratio: number) { if (!ratio) { return basePrice } return Math.round(basePrice + basePrice * ratio) } /** بالاترین/پایین‌ترین امروز — برای boursecu با نسبت مفتول/شمش نسبت به کاتد */ export function resolveMetalDayRangePrices(options: { price: { up?: { buy?: number; sell?: number }; buy?: number; sell?: number; price_last?: number } | null | undefined metal: ChartMetalType metalRatio: number }) { const baseHigh = options.price?.up?.buy ?? getTradeBuyPrice(options.price as Parameters[0]) const baseLow = options.price?.up?.sell ?? getTradeSellPrice(options.price as Parameters[0]) if (options.metal === 'katod') { return { high: baseHigh, low: baseLow } } return { high: applyChartRatio(baseHigh, options.metalRatio), low: applyChartRatio(baseLow, options.metalRatio), } } function deriveMetalChartRatio(metalProduct?: Product, katodProduct?: Product) { const metalOffset = resolvePriceOffsetRatio(metalProduct?.price_offset_percent) if (metalOffset) { return metalOffset } if (!metalProduct) { return 0 } const liveBuy = getTradeBuyPrice(lastPrice.value) const katodLive = katodProduct ? getProductBuyPrice(katodProduct) : liveBuy const metalLive = getProductBuyPrice(metalProduct) const katodStatic = Number(katodProduct?.price ?? 0) + Number(katodProduct?.price_profit ?? 0) const metalStatic = Number(metalProduct.price ?? 0) + Number(metalProduct.price_profit ?? 0) const referencePrice = katodLive || liveBuy || katodStatic const derivedMetalPrice = metalLive || metalStatic if (!referencePrice || !derivedMetalPrice || referencePrice === derivedMetalPrice) { return 0 } return (derivedMetalPrice - referencePrice) / referencePrice } function normalizeChartTimestamp(timestamp: number) { return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp } function resolvePricesMap(response: unknown): Record> | null { if (!response || typeof response !== 'object') { return null } const body = response as Record const nested = body.data if (nested && typeof nested === 'object' && 'prices' in nested) { return (nested as { prices: Record> }).prices } if ('prices' in body) { return body.prices as Record> } return null } function buildChartPoints(items: Array<{ timestamp: number; value: number }>): ChartPoint[] { return items.map((item) => [ normalizeChartTimestamp(item.timestamp), item.value - 1, ]) } function syncChartSeries() { const ratio = selectedMetalChartRatio.value const data = selectedChartMetal.value !== 'katod' ? rawChartPoints.value.map( ([timestamp, value]) => [timestamp, applyChartRatio(value, ratio)] as ChartPoint ) : rawChartPoints.value priceChartSeries.value[0].data = data } watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => { syncChartSeries() }) async function fetchPrices(force = false) { if (!force && chartInitialized && rawChartPoints.value.length > 0) { syncChartSeries() return } if (!force && chartFetchPromise) { return chartFetchPromise } chartFetchPromise = (async () => { try { chartLoading.value = true const response = await getAllPrices() const data = resolvePricesMap(response) const selectedData = data?.[chartSelection.value] ?? [] rawChartPoints.value = buildChartPoints(selectedData) syncChartSeries() chartInitialized = true } catch (error) { console.error('Error fetching prices:', error) } finally { chartLoading.value = false chartFetchPromise = null } })() return chartFetchPromise } async function loadMetalChartOffsets(force = false) { if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) { return } if (!force && metalOffsetPromise) { return metalOffsetPromise } metalOffsetPromise = (async () => { try { await getProductsCatalog(force) syncChartSeries() metalOffsetLoaded = true } catch (error) { console.error('Error loading boursecu chart metal ratios:', error) syncChartSeries() } finally { metalOffsetPromise = null } })() return metalOffsetPromise } function updateChartData(timeline: string) { chartSelection.value = timeline void fetchPrices(true) } function setChartMetal(metal: ChartMetalType) { selectedChartMetal.value = metal if (metal !== 'katod' && !catalogProducts.value.length) { void loadMetalChartOffsets(true) } } export function usePriceChart() { return { priceChartSeries, chartSelection, chartLoading, selectedChartMetal, maftolChartRatio, shamshChartRatio, selectedMetalChartRatio, updateChartData, fetchPrices, loadMaftolOffset: loadMetalChartOffsets, loadMetalChartOffsets, setChartMetal, } } export function fetchHomeChartPrices(force = false) { return fetchPrices(force) } export function loadHomeMaftolOffset(force = false) { return loadMetalChartOffsets(force) } export function resetHomePriceChart() { priceChartSeries.value = [{ name: 'قیمت', data: [] }] rawChartPoints.value = [] chartSelection.value = 'DAILY' selectedChartMetal.value = 'katod' chartInitialized = false chartFetchPromise = null metalOffsetLoaded = false metalOffsetPromise = null }