import { useEffect, useState } from 'react' import { Save, X } from 'lucide-react' import toast from 'react-hot-toast' import { mediaAPI, type MediaMetadataUpdate } from '../api/library' import type { Media } from '../types' interface MetadataEditDialogProps { open: boolean media: Media | null mediaIds?: string[] mode?: 'media' | 'series' scopeLabel?: string onClose: () => void onSaved: (media: Media) => void | Promise } export function MetadataEditDialog({ open, media, mediaIds, mode = 'media', scopeLabel, onClose, onSaved, }: MetadataEditDialogProps) { const [form, setForm] = useState({ title: '', original_name: '', overview: '', poster_url: '', backdrop_url: '', year: '', release_date: '', rating: '', season_num: '', episode_num: '', tmdb_id: '', bangumi_id: '', douban_id: '', thetvdb_id: '', languages: '', countries: '', genres: '', nsfw: false, }) const [saving, setSaving] = useState(false) useEffect(() => { if (!open || !media) return setForm({ title: media.title || '', original_name: media.original_name || '', overview: media.overview || '', poster_url: media.poster_url || '', backdrop_url: media.backdrop_url || '', year: media.year > 0 ? String(media.year) : '', release_date: media.release_date || '', rating: media.rating > 0 ? String(media.rating) : '', season_num: media.season_num > 0 || media.episode_num > 0 ? String(media.season_num || 0) : '', episode_num: media.episode_num > 0 ? String(media.episode_num) : '', tmdb_id: media.tmdb_id > 0 ? String(media.tmdb_id) : '', bangumi_id: media.bangumi_id > 0 ? String(media.bangumi_id) : '', douban_id: media.douban_id || '', thetvdb_id: media.thetvdb_id || '', languages: media.languages || '', countries: media.countries || '', genres: media.genres || '', nsfw: !!media.nsfw, }) }, [open, media]) if (!open || !media) return null const targetIds = Array.from(new Set((mediaIds && mediaIds.length > 0 ? mediaIds : [media.id]).filter(Boolean))) const isSeries = mode === 'series' && targetIds.length > 1 const set = (key: keyof typeof form, value: string | boolean) => { setForm((prev) => ({ ...prev, [key]: value })) } const toNumber = (value: string) => { const trimmed = value.trim() if (!trimmed) return 0 const parsed = Number(trimmed) return Number.isFinite(parsed) ? parsed : 0 } const buildPayload = (): MediaMetadataUpdate => { const payload: MediaMetadataUpdate = { title: form.title, overview: form.overview, poster_url: form.poster_url, backdrop_url: form.backdrop_url, year: Math.trunc(toNumber(form.year)), release_date: form.release_date, rating: toNumber(form.rating), tmdb_id: Math.trunc(toNumber(form.tmdb_id)), bangumi_id: Math.trunc(toNumber(form.bangumi_id)), douban_id: form.douban_id, thetvdb_id: form.thetvdb_id, languages: form.languages, countries: form.countries, genres: form.genres, nsfw: form.nsfw, } if (!isSeries) { payload.original_name = form.original_name payload.season_num = Math.trunc(toNumber(form.season_num)) payload.episode_num = Math.trunc(toNumber(form.episode_num)) } return payload } const save = async () => { if (!form.title.trim()) { toast.error('标题不能为空') return } setSaving(true) try { const payload = buildPayload() let next: Media | null = null for (const id of targetIds) { const updated = await mediaAPI.updateMetadata(id, payload) if (!next || id === media.id) next = updated } if (!next) next = await mediaAPI.updateMetadata(media.id, payload) toast.success(isSeries ? `整剧元数据已保存:${targetIds.length} 集` : '元数据已保存') await onSaved(next) onClose() } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error || '保存失败' toast.error(msg) } finally { setSaving(false) } } return (

{isSeries ? '编辑整剧元数据' : '编辑元数据'}

{scopeLabel || '用于手动修正自采集或无法自动匹配的媒体。'}

set('title', value)} /> {!isSeries && set('original_name', value)} />} set('poster_url', value)} /> set('backdrop_url', value)} /> set('year', value)} inputMode="numeric" /> set('release_date', value)} type="date" /> set('rating', value)} inputMode="decimal" /> {!isSeries && set('season_num', value)} inputMode="numeric" />} {!isSeries && set('episode_num', value)} inputMode="numeric" />} set('tmdb_id', value)} inputMode="numeric" /> set('bangumi_id', value)} inputMode="numeric" /> set('douban_id', value)} /> set('thetvdb_id', value)} /> set('languages', value)} placeholder="zh,en" /> set('countries', value)} placeholder="CN,JP,US" /> set('genres', value)} placeholder="剧情,动画" />