import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { EpisodeGroupList, EpisodeGroupSetResult, EpisodeMappingPreview, type EpisodeMappingPreviewInput, type EpisodeMappingRuleCreate, EpisodeMappingRuleList, type EpisodeMappingRuleUpdate, EpisodeMappingRuleWriteResult, EpisodeStructureSeasons, } from "@umt/contracts"; import { api } from "~/lib/api.ts"; import { decisionKeys } from "./decisions.ts"; import { getParsed, libraryKeys, parseOrThrow } from "./library.ts"; // 集数映射:作品的规则列表 / 增改停删(写入后服务端按作品重算)/ 模拟预览(纯读)。 export const episodeMappingKeys = { all: ["episode-mapping"] as const, rules: (entityId: string) => ["episode-mapping", "rules", entityId] as const, preview: (entityId: string, input: EpisodeMappingPreviewInput) => ["episode-mapping", "preview", entityId, input] as const, groups: (entityId: string) => ["episode-mapping", "groups", entityId] as const, structure: (entityId: string, groupId: string | null) => ["episode-mapping", "structure", entityId, groupId ?? "default"] as const, }; export function useEpisodeMappingRules(entityId: string | undefined) { return useQuery({ queryKey: episodeMappingKeys.rules(entityId ?? ""), enabled: !!entityId, queryFn: ({ signal }) => getParsed(EpisodeMappingRuleList, `/media-entities/${entityId}/episode-mapping-rules`, undefined, signal), }); } export function useEpisodeMappingPreview( entityId: string | null | undefined, input: EpisodeMappingPreviewInput, enabled = true, ) { return useQuery({ queryKey: episodeMappingKeys.preview(entityId ?? "", input), enabled: !!entityId && enabled, placeholderData: keepPreviousData, queryFn: async () => parseOrThrow( EpisodeMappingPreview, await api.post(`/media-entities/${entityId}/episode-mapping/preview`, input), "/media-entities/{id}/episode-mapping/preview", ), }); } type WriteVars = | { op: "create"; entityId: string; body: EpisodeMappingRuleCreate } | { op: "update"; id: string; body: EpisodeMappingRuleUpdate } | { op: "delete"; id: string }; /** 规则写入:成功后刷新规则、预览、待处理与作品详情(季集可能变了) */ export function useEpisodeMappingWrite(entityId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (v: WriteVars) => { const data = v.op === "create" ? await api.post(`/media-entities/${v.entityId}/episode-mapping-rules`, v.body) : v.op === "update" ? await api.patch(`/episode-mapping-rules/${v.id}`, v.body) : await api.del(`/episode-mapping-rules/${v.id}`); return parseOrThrow(EpisodeMappingRuleWriteResult, data, "/episode-mapping-rules"); }, onSuccess: () => { void qc.invalidateQueries({ queryKey: episodeMappingKeys.all }); void qc.invalidateQueries({ queryKey: decisionKeys.all }); void qc.invalidateQueries({ queryKey: libraryKeys.detail(entityId) }); void qc.invalidateQueries({ queryKey: libraryKeys.entity(entityId) }); }, }); } // ── 剧集组(季集结构) ── /** 作品可选的 TMDB 剧集组与当前选择 */ export function useEpisodeGroups(entityId: string | undefined, enabled = true) { return useQuery({ queryKey: episodeMappingKeys.groups(entityId ?? ""), enabled: !!entityId && enabled, staleTime: 5 * 60_000, queryFn: ({ signal }) => getParsed(EpisodeGroupList, `/media-entities/${entityId}/episode-groups`, undefined, signal), }); } /** 某套季集结构(null = TMDB 默认季)的季卡片与每集,带本地已入库 */ export function useEpisodeStructure(entityId: string | undefined, groupId: string | null, enabled = true) { return useQuery({ queryKey: episodeMappingKeys.structure(entityId ?? "", groupId), enabled: !!entityId && enabled, placeholderData: keepPreviousData, queryFn: ({ signal }) => getParsed( EpisodeStructureSeasons, `/media-entities/${entityId}/episode-structure`, groupId ? { groupId } : undefined, signal, ), }); } /** 切换作品的季集结构:成功后刷新剧集组、预览、规则、待处理与作品详情(季集都变了) */ export function useSetEpisodeGroup(entityId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (groupId: string | null) => parseOrThrow( EpisodeGroupSetResult, await api.put(`/media-entities/${entityId}/episode-group`, { groupId }), "/media-entities/{id}/episode-group", ), onSuccess: () => { void qc.invalidateQueries({ queryKey: episodeMappingKeys.all }); void qc.invalidateQueries({ queryKey: decisionKeys.all }); void qc.invalidateQueries({ queryKey: libraryKeys.all }); void qc.invalidateQueries({ queryKey: ["subscriptions"] }); }, }); }