import { createContext, useContext } from "react"; import { coerceTagModes, coerceUntagged, type FeedFilters, type SavedView } from "../lib/api"; // The saved-views context + hook (and the feed blob coercion its consumers share), split out of // SavedViewsProvider so that file exports only components (Fast Refresh). // A saved view's `filters` is an opaque JSON blob from the DB; coerce it to the module's shape before it // reaches app state or a serializer (an older build's dropped field just falls away — NOT a migration). export const asFilters = (raw: FeedFilters): FeedFilters => ({ ...raw, tags: raw.tags ?? [], channelIds: raw.channelIds ?? [], tagModes: coerceTagModes(raw), untagged: coerceUntagged(raw), }); export type ViewSource = "feed" | "plex"; export interface SavedViewsContext { /** Which module these views belong to — the shared UI branches its filter-describer on it. */ source: ViewSource; views: SavedView[]; loading: boolean; activeViewId: number | null; displayView: SavedView | null; isDirty: boolean; modalOpen: boolean; openModal: () => void; closeModal: () => void; apply: (v: SavedView) => void; clearActive: () => void; create: (name: string) => Promise; rename: (id: number, name: string) => Promise; /** Overwrite a view's captured filters (used by the modal's removable chips). */ updateFilters: (id: number, filters: Record) => Promise; /** Save the module's CURRENTLY-applied filters into a view (the live→stored inverse of `apply`); * resolves to the saved snapshot on success (so the caller can resync its editor) or null. */ saveCurrent: (id: number) => Promise | null>; toggleDefault: (v: SavedView) => Promise; remove: (v: SavedView) => Promise; reorder: (nextIds: number[]) => Promise; } export const SavedViewsCtx = createContext(null); export function useSavedViews(): SavedViewsContext { const ctx = useContext(SavedViewsCtx); if (!ctx) throw new Error("useSavedViews must be used within a SavedViewsProvider"); return ctx; }