import { create } from 'zustand'; import { getTracksTimeRange } from '../lib/adapters'; import type { ProcessedTrack, TrackQueryContext } from '../model/track.types'; import { queryTrackByDateRange } from '../services/trackQueryService'; import { useTrackPlaybackStore } from './trackPlaybackStore'; export type TrackQueryStatus = 'idle' | 'loading' | 'ready' | 'error'; interface TrackQueryState { tracks: ProcessedTrack[]; disabledVesselIds: Set; highlightedVesselId: string | null; isLoading: boolean; error: string | null; queryState: TrackQueryStatus; renderEpoch: number; lastQueryKey: string | null; queryContext: TrackQueryContext | null; showPoints: boolean; showVirtualShip: boolean; showLabels: boolean; showTrail: boolean; hideLiveShips: boolean; beginQuery: (queryKey: string, context?: TrackQueryContext) => void; applyTracksSuccess: (tracks: ProcessedTrack[], queryKey?: string | null) => void; applyQueryError: (error: string, queryKey?: string | null) => void; closeQuery: () => void; requery: (startTimeIso: string, endTimeIso: string) => Promise; setTracks: (tracks: ProcessedTrack[]) => void; clearTracks: () => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; setHighlightedVesselId: (vesselId: string | null) => void; setShowPoints: (show: boolean) => void; setShowVirtualShip: (show: boolean) => void; setShowLabels: (show: boolean) => void; setShowTrail: (show: boolean) => void; setHideLiveShips: (hide: boolean) => void; toggleVesselEnabled: (vesselId: string) => void; getEnabledTracks: () => ProcessedTrack[]; reset: () => void; } export const useTrackQueryStore = create()((set, get) => ({ tracks: [], disabledVesselIds: new Set(), highlightedVesselId: null, isLoading: false, error: null, queryState: 'idle', renderEpoch: 0, lastQueryKey: null, queryContext: null, showPoints: true, showVirtualShip: true, showLabels: true, showTrail: true, hideLiveShips: false, beginQuery: (queryKey: string, context?: TrackQueryContext) => { useTrackPlaybackStore.getState().reset(); set((state) => ({ tracks: [], disabledVesselIds: new Set(), highlightedVesselId: null, isLoading: true, error: null, queryState: 'loading', renderEpoch: state.renderEpoch + 1, lastQueryKey: queryKey, hideLiveShips: false, queryContext: context ?? state.queryContext, })); }, applyTracksSuccess: (tracks: ProcessedTrack[], queryKey?: string | null) => { const currentQueryKey = get().lastQueryKey; if (queryKey != null && queryKey !== currentQueryKey) { return; } const range = getTracksTimeRange(tracks); const playback = useTrackPlaybackStore.getState(); if (range) { playback.setTimeRange(range.start, range.end); playback.syncToRangeStart(); playback.setPlaybackSpeed(100); } else { playback.reset(); } set((state) => ({ tracks, disabledVesselIds: new Set(), highlightedVesselId: null, isLoading: false, error: null, queryState: 'ready', renderEpoch: state.renderEpoch + 1, lastQueryKey: queryKey ?? state.lastQueryKey, })); if (range) { if (typeof window !== 'undefined') { window.requestAnimationFrame(() => { useTrackPlaybackStore.getState().play(); }); } else { useTrackPlaybackStore.getState().play(); } } }, applyQueryError: (error: string, queryKey?: string | null) => { const currentQueryKey = get().lastQueryKey; if (queryKey != null && queryKey !== currentQueryKey) { return; } useTrackPlaybackStore.getState().reset(); set((state) => ({ tracks: [], disabledVesselIds: new Set(), highlightedVesselId: null, isLoading: false, error, queryState: 'error', renderEpoch: state.renderEpoch + 1, lastQueryKey: queryKey ?? state.lastQueryKey, hideLiveShips: false, })); }, closeQuery: () => { useTrackPlaybackStore.getState().reset(); set((state) => ({ tracks: [], disabledVesselIds: new Set(), highlightedVesselId: null, isLoading: false, error: null, queryState: 'idle', renderEpoch: state.renderEpoch + 1, lastQueryKey: null, queryContext: null, hideLiveShips: false, })); }, requery: async (startTimeIso: string, endTimeIso: string) => { const ctx = get().queryContext; if (!ctx) return; const queryKey = `requery:${ctx.mmsi}:${Date.now()}`; const updatedContext: TrackQueryContext = { ...ctx, startTimeIso, endTimeIso }; get().beginQuery(queryKey, updatedContext); try { const tracks = await queryTrackByDateRange({ mmsi: updatedContext.mmsi, startTimeIso: updatedContext.startTimeIso, endTimeIso: updatedContext.endTimeIso, shipNameHint: updatedContext.shipNameHint, shipKindCodeHint: updatedContext.shipKindCodeHint, nationalCodeHint: updatedContext.nationalCodeHint, isPermitted: updatedContext.isPermitted, }); if (tracks.length > 0) { get().applyTracksSuccess(tracks, queryKey); } else { get().applyQueryError('항적 데이터가 없습니다.', queryKey); } } catch (e) { get().applyQueryError(e instanceof Error ? e.message : '항적 조회에 실패했습니다.', queryKey); } }, setTracks: (tracks: ProcessedTrack[]) => { get().applyTracksSuccess(tracks, get().lastQueryKey); }, clearTracks: () => { get().closeQuery(); }, setLoading: (loading: boolean) => set((state) => ({ isLoading: loading, queryState: loading ? 'loading' : state.error ? 'error' : state.tracks.length > 0 ? 'ready' : 'idle', })), setError: (error: string | null) => set((state) => ({ error, queryState: error ? 'error' : state.isLoading ? 'loading' : state.tracks.length > 0 ? 'ready' : 'idle', })), setHighlightedVesselId: (vesselId: string | null) => set({ highlightedVesselId: vesselId }), setShowPoints: (show: boolean) => set({ showPoints: show }), setShowVirtualShip: (show: boolean) => set({ showVirtualShip: show }), setShowLabels: (show: boolean) => set({ showLabels: show }), setShowTrail: (show: boolean) => set({ showTrail: show }), setHideLiveShips: (hide: boolean) => set({ hideLiveShips: hide }), toggleVesselEnabled: (vesselId: string) => { const next = new Set(get().disabledVesselIds); if (next.has(vesselId)) next.delete(vesselId); else next.add(vesselId); set((state) => ({ disabledVesselIds: next, renderEpoch: state.renderEpoch + 1, })); }, getEnabledTracks: () => { const { tracks, disabledVesselIds } = get(); if (disabledVesselIds.size === 0) return tracks; return tracks.filter((track) => !disabledVesselIds.has(track.vesselId)); }, reset: () => { useTrackPlaybackStore.getState().reset(); set((state) => ({ tracks: [], disabledVesselIds: new Set(), highlightedVesselId: null, isLoading: false, error: null, queryState: 'idle', renderEpoch: state.renderEpoch + 1, lastQueryKey: null, queryContext: null, showPoints: true, showVirtualShip: true, showLabels: true, showTrail: true, hideLiveShips: false, })); }, }));