import { Injectable, signal, computed } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Region, GeoIpResponse } from '../models/location.model'; import { ApiConfigService } from '../core/config/api-config.service'; const STORAGE_KEY = 'selected_region'; @Injectable({ providedIn: 'root' }) export class LocationService { private regionSignal = signal(null); private regionsSignal = signal([]); private loadingSignal = signal(false); private detectedSignal = signal(false); /** Current selected region (null = global / all regions) */ readonly region = this.regionSignal.asReadonly(); /** All available regions */ readonly regions = this.regionsSignal.asReadonly(); /** Whether geo-detection is in progress */ readonly detecting = this.loadingSignal.asReadonly(); /** Whether region was auto-detected */ readonly autoDetected = this.detectedSignal.asReadonly(); /** Computed region id for API calls — empty string means global */ readonly regionId = computed(() => this.regionSignal()?.id ?? ''); constructor( private readonly http: HttpClient, private readonly apiConfig: ApiConfigService ) { this.loadRegions(); this.restoreFromStorage(); } /** Fetch available regions from backend */ loadRegions(): void { this.http.get(`${this.apiConfig.getBaseUrl()}/regions`).subscribe({ next: (regions) => { this.regionsSignal.set(regions); // If we have a stored region, validate it still exists const stored = this.regionSignal(); if (stored && !regions.find(r => r.id === stored.id)) { this.clearRegion(); } }, error: () => { // Fallback: hardcoded popular regions this.regionsSignal.set(this.getFallbackRegions()); } }); } /** Set region by user choice */ setRegion(region: Region): void { this.regionSignal.set(region); localStorage.setItem(STORAGE_KEY, JSON.stringify(region)); } /** Clear region (go global) */ clearRegion(): void { this.regionSignal.set(null); localStorage.removeItem(STORAGE_KEY); } /** Auto-detect user location via IP geolocation */ detectLocation(): void { if (this.detectedSignal()) return; // already tried this.loadingSignal.set(true); // Using free ip-api.com — no key required, 45 req/min this.http.get('http://ip-api.com/json/?fields=city,country,countryCode,region,timezone,lat,lon') .subscribe({ next: (geo) => { this.detectedSignal.set(true); this.loadingSignal.set(false); // Only auto-set if user hasn't manually chosen a region if (!this.regionSignal()) { const matchedRegion = this.findRegionByGeo(geo); if (matchedRegion) { this.setRegion(matchedRegion); } } }, error: () => { this.detectedSignal.set(true); this.loadingSignal.set(false); } }); } /** Try to match detected geo data to an available region */ private findRegionByGeo(geo: GeoIpResponse): Region | null { const regions = this.regionsSignal(); if (!regions.length) return null; // Exact city match const cityMatch = regions.find(r => r.city.toLowerCase() === geo.city?.toLowerCase() ); if (cityMatch) return cityMatch; // Country match — pick the first region for that country const countryMatch = regions.find(r => r.countryCode.toLowerCase() === geo.countryCode?.toLowerCase() ); return countryMatch || null; } /** Restore previously selected region from storage */ private restoreFromStorage(): void { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { const region: Region = JSON.parse(stored); this.regionSignal.set(region); } } catch { localStorage.removeItem(STORAGE_KEY); } } /** Fallback regions if backend /regions endpoint is unavailable */ private getFallbackRegions(): Region[] { return [ { id: 'moscow', city: 'Москва', country: 'Россия', countryCode: 'RU', timezone: 'Europe/Moscow' }, { id: 'spb', city: 'Санкт-Петербург', country: 'Россия', countryCode: 'RU', timezone: 'Europe/Moscow' }, { id: 'yerevan', city: 'Ереван', country: 'Армения', countryCode: 'AM', timezone: 'Asia/Yerevan' }, { id: 'minsk', city: 'Минск', country: 'Беларусь', countryCode: 'BY', timezone: 'Europe/Minsk' }, { id: 'almaty', city: 'Алматы', country: 'Казахстан', countryCode: 'KZ', timezone: 'Asia/Almaty' }, { id: 'tbilisi', city: 'Тбилиси', country: 'Грузия', countryCode: 'GE', timezone: 'Asia/Tbilisi' }, ]; } }