RematchOptions React component for configuring and triggering rematch operations for manga entries by status.

export const RematchOptions: React.FC<RematchOptionsProps> = ({
selectedStatuses,
onChangeSelectedStatuses,
matchResults,
rematchWarning,
onRematchByStatus,
onCloseOptions,
}) => {
const toggleStatus = (status: keyof StatusFilterOptions) => {
onChangeSelectedStatuses({
...selectedStatuses,
[status]: !selectedStatuses[status],
});
};

const resetToDefault = () => {
onChangeSelectedStatuses({
...selectedStatuses,
pending: true,
skipped: true,
matched: false,
manual: false,
});
};

// Calculate the total count of manga to be rematched
const calculateTotalCount = () => {
return Object.entries(selectedStatuses)
.filter(([, selected]) => selected)
.reduce((count, [status]) => {
return count + matchResults.filter((m) => m.status === status).length;
}, 0);
};

// Calculate individual counts for status badges
const statusCounts = {
pending: matchResults.filter((m) => m.status === "pending").length,
skipped: matchResults.filter((m) => m.status === "skipped").length,
matched: matchResults.filter((m) => m.status === "matched").length,
manual: matchResults.filter((m) => m.status === "manual").length,
};

type RematchStatusKey = Extract<
keyof StatusFilterOptions,
"pending" | "matched" | "manual" | "skipped"
>;

const statusOptions: Array<{
key: RematchStatusKey;
label: string;
description: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
accentClass: string;
badgeClass: string;
}> = [
{
key: "pending",
label: "Pending",
description: "Queued for a fresh AniList lookup",
icon: Clock3,
accentClass:
"bg-gradient-to-br from-blue-500/20 via-blue-400/15 to-cyan-400/10 text-blue-600 dark:from-blue-500/25 dark:via-blue-500/15 dark:to-cyan-500/10 dark:text-blue-200",
badgeClass:
"bg-blue-100/80 text-blue-700 dark:bg-blue-900/30 dark:text-blue-200",
},
{
key: "matched",
label: "Matched",
description: "Already aligned titles you may want to re-evaluate",
icon: Sparkles,
accentClass:
"bg-gradient-to-br from-emerald-500/20 via-emerald-400/15 to-teal-400/10 text-emerald-600 dark:from-emerald-500/25 dark:via-emerald-500/15 dark:to-teal-500/10 dark:text-emerald-200",
badgeClass:
"bg-emerald-100/80 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-200",
},
{
key: "manual",
label: "Manual",
description: "Titles waiting for your curated matches",
icon: Hand,
accentClass:
"bg-gradient-to-br from-purple-500/20 via-indigo-400/15 to-blue-400/10 text-purple-600 dark:from-purple-500/25 dark:via-indigo-500/15 dark:to-blue-500/10 dark:text-purple-200",
badgeClass:
"bg-purple-100/80 text-purple-700 dark:bg-purple-900/30 dark:text-purple-200",
},
{
key: "skipped",
label: "Skipped",
description: "Entries previously skipped during matching",
icon: Slash,
accentClass:
"bg-gradient-to-br from-rose-500/20 via-orange-400/15 to-amber-400/10 text-rose-600 dark:from-rose-500/25 dark:via-orange-500/15 dark:to-amber-500/10 dark:text-rose-200",
badgeClass:
"bg-rose-100/80 text-rose-700 dark:bg-rose-900/30 dark:text-rose-200",
},
];

return (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
<Card className="relative mb-6 flex flex-col overflow-hidden border border-blue-200/70 bg-gradient-to-br from-white/92 via-white/85 to-blue-50/75 py-0 shadow-xl shadow-blue-500/10 supports-[backdrop-filter]:backdrop-blur-md dark:border-blue-900/60 dark:from-slate-950/70 dark:via-slate-950/55 dark:to-blue-950/45">
<div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-blue-200/18 via-indigo-200/12 to-transparent dark:from-blue-900/25 dark:via-indigo-900/15 dark:to-transparent" />
<CardHeader className="relative border-b border-blue-100/60 bg-gradient-to-r from-blue-50/80 via-indigo-50/70 to-purple-50/65 py-3 dark:border-blue-900/50 dark:from-blue-950/40 dark:via-indigo-950/35 dark:to-purple-950/30">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2">
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-blue-500/15 text-blue-600 shadow-sm shadow-blue-500/25 dark:bg-blue-500/12 dark:text-blue-200">
<RefreshCw className="h-4 w-4" />
</span>
<div>
<CardTitle className="text-lg font-semibold text-slate-900 dark:text-slate-100">
Rematch Options
</CardTitle>
<p className="text-xs text-slate-500 dark:text-slate-400">
Choose which states should get a fresh search run
</p>
</div>
</div>
<Button
variant="ghost"
size="icon"
onClick={onCloseOptions}
className="h-8 w-8 rounded-full border border-transparent bg-white/70 text-slate-600 shadow-sm transition hover:border-blue-200 hover:bg-blue-50 hover:text-slate-800 dark:bg-slate-900/60 dark:text-slate-300 dark:hover:border-blue-800 dark:hover:bg-slate-900/80"
>
<X className="h-4 w-4" />
</Button>
</div>
</CardHeader>

<CardContent className="relative space-y-5 p-6 pb-4">
{rematchWarning && (
<Alert
variant="default"
className="border-yellow-200 bg-yellow-50 dark:border-yellow-900/50 dark:bg-yellow-900/20"
>
<AlertTriangle className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
<AlertTitle className="text-yellow-800 dark:text-yellow-400">
Warning
</AlertTitle>
<AlertDescription className="text-yellow-700 dark:text-yellow-300">
{rematchWarning}
</AlertDescription>
</Alert>
)}

<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold tracking-[0.18em] text-slate-500 uppercase dark:text-slate-400">
Status Filters
</h3>
<Button
variant="ghost"
size="sm"
onClick={resetToDefault}
className="h-8 gap-1 rounded-full border border-blue-100/60 bg-white/70 px-3 text-xs font-semibold tracking-[0.18em] text-blue-600 uppercase shadow-sm hover:border-blue-200 hover:bg-blue-50 dark:border-blue-900/40 dark:bg-slate-950/60 dark:text-blue-200 dark:hover:border-blue-800"
>
<RotateCcw className="h-3.5 w-3.5" /> Reset
</Button>
</div>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
{statusOptions.map(
({
key,
label,
description,
icon: Icon,
accentClass,
badgeClass,
}) => {
const checkboxId = `status-${key}`;
const isSelected = selectedStatuses[key];

return (
<label
key={key}
htmlFor={checkboxId}
className="group relative overflow-hidden rounded-2xl border border-slate-200/70 bg-white/80 p-4 shadow-sm shadow-blue-500/10 transition duration-300 ease-out hover:-translate-y-1 hover:shadow-xl dark:border-slate-800/60 dark:bg-slate-950/65"
>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/70 via-transparent to-transparent opacity-70 dark:from-slate-900/50" />
<div className="relative flex items-start gap-3">
<Checkbox
id={checkboxId}
checked={isSelected}
onCheckedChange={() => toggleStatus(key)}
className="mt-1 border-blue-300 data-[state=checked]:bg-blue-500 data-[state=checked]:text-white dark:border-blue-800 dark:data-[state=checked]:text-slate-900"
/>
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<span
className={`flex h-10 w-10 items-center justify-center rounded-xl shadow-inner ${accentClass}`}
>
<Icon className="h-5 w-5" />
</span>
<div>
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-slate-800 dark:text-slate-100">
{label}
</span>
<Badge
variant="secondary"
className={`px-2 py-0.5 text-xs font-semibold ${badgeClass}`}
>
{statusCounts[key]}
</Badge>
</div>
<p className="text-xs text-slate-500 dark:text-slate-400">
{description}
</p>
</div>
</div>
</div>
<div className="flex items-center justify-between text-xs font-medium tracking-[0.18em] text-slate-400 uppercase dark:text-slate-500">
<span>{isSelected ? "Included" : "Excluded"}</span>
<span className="flex items-center gap-1 text-slate-500 dark:text-slate-400">
<Layers className="h-3.5 w-3.5" /> Queue position
</span>
</div>
</div>
</div>
</label>
);
},
)}
</div>
</div>

<div className="relative flex flex-col gap-3 rounded-2xl border border-blue-200/60 bg-gradient-to-r from-blue-500/10 via-indigo-500/10 to-cyan-500/10 p-5 shadow-inner sm:flex-row sm:items-center sm:justify-between dark:border-blue-500/25 dark:from-blue-500/12 dark:via-indigo-500/12 dark:to-cyan-500/12">
<div className="flex items-center gap-3 text-sm font-medium text-blue-700 dark:text-blue-200">
<span className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-500/15 text-blue-600 shadow-sm shadow-blue-500/20 dark:bg-blue-500/12 dark:text-blue-200">
<RefreshCw className="h-5 w-5" />
</span>
<div>
<span className="block text-xs tracking-[0.24em] text-blue-500/80 uppercase dark:text-blue-300/80">
Queue Summary
</span>
<span className="text-base font-semibold text-blue-700 dark:text-blue-100">
{calculateTotalCount()} manga selected for rematch
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={resetToDefault}
className="hidden h-9 gap-1 rounded-full border border-blue-200/70 bg-white/70 px-3 text-xs font-semibold tracking-[0.18em] text-blue-600 uppercase shadow-sm hover:border-blue-300 hover:bg-blue-50 sm:flex dark:border-blue-800/60 dark:bg-slate-950/70 dark:text-blue-200 dark:hover:border-blue-700"
>
<RotateCcw className="h-3.5 w-3.5" /> Reset filters
</Button>
</div>
</CardContent>

<CardFooter className="relative flex flex-col gap-3 border-t border-blue-100/70 bg-gradient-to-r from-blue-50/80 via-indigo-50/70 to-purple-50/70 p-4 sm:flex-row sm:items-center sm:justify-between dark:border-blue-900/50 dark:from-blue-950/45 dark:via-indigo-950/35 dark:to-purple-950/30">
<div className="text-xs text-slate-500 dark:text-slate-400">
{calculateTotalCount() === 0
? "Select at least one status to enable rematching."
: "We'll run a fresh search for every selected status group."}
</div>
<Button
variant="default"
onClick={onRematchByStatus}
className="w-full rounded-2xl bg-gradient-to-r from-blue-600 via-indigo-600 to-purple-600 text-base font-semibold shadow-lg shadow-blue-500/25 transition hover:from-blue-700 hover:via-indigo-700 hover:to-purple-700 focus-visible:ring-blue-400 sm:w-auto"
disabled={calculateTotalCount() === 0}
>
<RefreshCw className="mr-2 h-4 w-4" />
Fresh Search Selected ({calculateTotalCount()})
</Button>
</CardFooter>
</Card>
</motion.div>
);
};