import { computed, ref, watch } from 'vue' import { getAllPrices } from '@/api/home' import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog' import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price' export type ChartMetalType = 'katod' | 'maftol' | 'shamsh' export type ChartTimeline = 'DAILY' | 'WEEKLY' | 'MONTHLY' type ChartPoint = [number, number] type PricesMap = Record> /** * مارک‌آپ ثابت روی قیمت بورس کالا برای نمودار: * کاتد +۳٪ ، مفتول +۶٪ ، شمش +۱۲٪ */ export const CHART_METAL_MARKUP_RATIO: Record = { katod: 0.03, maftol: 0.06, shamsh: 0.12, } /** 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 cachedPrices = ref(null) const chartSelection = ref('DAILY') const chartLoading = ref(false) const selectedChartMetal = ref('katod') const catalogProducts = getProductsCatalogRef() const maftolChartRatio = computed(() => CHART_METAL_MARKUP_RATIO.maftol) const shamshChartRatio = computed(() => CHART_METAL_MARKUP_RATIO.shamsh) const selectedMetalChartRatio = computed( () => CHART_METAL_MARKUP_RATIO[selectedChartMetal.value], ) let chartFetchPromise: Promise | null = null let chartInitialized = false let metalOffsetPromise: Promise | null = null let metalOffsetLoaded = false function applyChartRatio(basePrice: number, ratio: number) { if (!ratio) { return basePrice } return Math.round(basePrice + basePrice * ratio) } /** بالاترین/پایین‌ترین امروز — همان مارک‌آپ ثابت فلز روی قیمت بورس کالا */ 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]) const ratio = options.metalRatio || CHART_METAL_MARKUP_RATIO[options.metal] return { high: applyChartRatio(baseHigh, ratio), low: applyChartRatio(baseLow, ratio), } } function normalizeChartTimestamp(timestamp: number) { return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp } function resolvePricesMap(response: unknown): PricesMap | 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: PricesMap }).prices } if ('prices' in body) { return body.prices as PricesMap } 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 // New array so Vue invalidates parent computeds (nested .data mutation did not). priceChartSeries.value = [ { name: 'قیمت', data: rawChartPoints.value.map( ([timestamp, value]) => [timestamp, applyChartRatio(value, ratio)] as ChartPoint, ), }, ] } function applyTimelineFromCache(timeline: ChartTimeline = chartSelection.value) { const selectedData = cachedPrices.value?.[timeline] ?? [] rawChartPoints.value = buildChartPoints(selectedData) syncChartSeries() } watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => { syncChartSeries() }) async function fetchPrices(force = false) { if (!force && chartInitialized && cachedPrices.value) { applyTimelineFromCache() return } if (!force && chartFetchPromise) { return chartFetchPromise } chartFetchPromise = (async () => { try { chartLoading.value = true const response = await getAllPrices() const data = resolvePricesMap(response) cachedPrices.value = data applyTimelineFromCache() chartInitialized = Boolean(data) } 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 as ChartTimeline if (cachedPrices.value) { applyTimelineFromCache(chartSelection.value) return } void fetchPrices(true) } function setChartMetal(metal: ChartMetalType) { selectedChartMetal.value = metal syncChartSeries() } 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 = [] cachedPrices.value = null chartSelection.value = 'DAILY' selectedChartMetal.value = 'katod' chartInitialized = false chartFetchPromise = null metalOffsetLoaded = false metalOffsetPromise = null }