"use client" import { createContext, useContext, useEffect, useState, useCallback } from "react" import { configuredBackendUrl, defaultDataSourceMode, isBackendUrlLocked, isMockDataSourceAvailable, LOCAL_DEFAULT_BACKEND_URL, resolveStoredBackendUrl, } from "@/lib/backend-url" // ── types ───────────────────────────────────────────────────────────────────── export type DataSourceMode = "mock" | "live" /** Режим «Живые» в настройках: моковые данные не подставлять. */ export function isLiveDataSourceMode(mode: DataSourceMode): boolean { return mode === "live" } interface DataSourceContextValue { mode: DataSourceMode setMode: (m: DataSourceMode) => void backendUrl: string setBackendUrl: (url: string) => void backendUrlLocked: boolean mockModeAvailable: boolean /** localStorage для mode/backendUrl применён на клиенте (после первого paint). */ prefsHydrated: boolean /** undefined = не проверялось, true = OK, false = недоступен */ backendStatus: boolean | undefined checkBackend: () => Promise } // ── context ─────────────────────────────────────────────────────────────────── const DataSourceContext = createContext(null) const LS_MODE = "routerlists:data-source" const LS_BACKEND = "routerlists:backend-url" function readStoredMode(): DataSourceMode { if (!isMockDataSourceAvailable()) return "live" if (typeof window === "undefined") return defaultDataSourceMode() const stored = localStorage.getItem(LS_MODE) as DataSourceMode | null if (stored === "live") return "live" if (stored === "mock") return "mock" return defaultDataSourceMode() } function readStoredBackendUrl(): string { if (typeof window === "undefined") return LOCAL_DEFAULT_BACKEND_URL return resolveStoredBackendUrl(localStorage.getItem(LS_BACKEND)) } function normalizeBackendUrl(url: string): string { return url.trim().replace(/\/$/, "") } export function DataSourceProvider({ children }: { children: React.ReactNode }) { const [mode, setModeState] = useState(defaultDataSourceMode) const [backendUrl, setBackendUrlState] = useState(LOCAL_DEFAULT_BACKEND_URL) const [prefsHydrated, setPrefsHydrated] = useState(false) const [backendStatus, setBackendStatus] = useState(undefined) const backendUrlLocked = isBackendUrlLocked() const mockModeAvailable = isMockDataSourceAvailable() useEffect(() => { const storedMode = readStoredMode() let url = readStoredBackendUrl() if (configuredBackendUrl().kind === "same-origin") { url = window.location.origin } setModeState(storedMode) setBackendUrlState(url) setPrefsHydrated(true) }, []) const setMode = useCallback((m: DataSourceMode) => { if (!mockModeAvailable && m === "mock") return setModeState(m) localStorage.setItem(LS_MODE, m) }, [mockModeAvailable]) const setBackendUrl = useCallback((url: string) => { if (backendUrlLocked) return const normalized = normalizeBackendUrl(url) setBackendUrlState(normalized) localStorage.setItem(LS_BACKEND, normalized) }, [backendUrlLocked]) const checkBackend = useCallback(async () => { const url = normalizeBackendUrl(backendUrl) try { const res = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3000) }) setBackendStatus(res.ok) } catch { setBackendStatus(false) } }, [backendUrl]) useEffect(() => { if (!prefsHydrated || mode !== "live") return queueMicrotask(() => { void checkBackend() }) }, [prefsHydrated, mode, checkBackend]) return ( {children} ) } export function useDataSource(): DataSourceContextValue { const ctx = useContext(DataSourceContext) if (!ctx) throw new Error("useDataSource must be used inside ") return ctx }