• Renders individual matching settings sections. Supports one-shots, adult content, blur adult, comick, mangadex, and custom rules.

    Parameters

    Returns null | Element

    The rendered matching settings section.

    export function MatchingSettingsSection({
    sectionId,
    matchConfig,
    searchQuery,
    highlightedSectionId,
    onMatchConfigChange,
    }: Readonly<MatchingSettingsSectionProps>) {
    const sectionVisuals: Record<
    string,
    {
    icon: LucideIcon;
    iconWrapperClass: string;
    accentBarClass: string;
    }
    > = {
    "matching-one-shots": {
    icon: Sparkles,
    iconWrapperClass:
    "bg-emerald-500/15 text-emerald-600 shadow-inner shadow-emerald-500/20 dark:bg-emerald-500/15 dark:text-emerald-200",
    accentBarClass:
    "from-emerald-400/80 via-emerald-500/70 to-emerald-400/80",
    },
    "matching-adult-content": {
    icon: ShieldBan,
    iconWrapperClass:
    "bg-rose-500/15 text-rose-600 shadow-inner shadow-rose-500/20 dark:bg-rose-500/15 dark:text-rose-200",
    accentBarClass: "from-rose-400/80 via-rose-500/70 to-rose-400/80",
    },
    "matching-blur-adult": {
    icon: EyeOff,
    iconWrapperClass:
    "bg-purple-500/15 text-purple-600 shadow-inner shadow-purple-500/20 dark:bg-purple-500/15 dark:text-purple-200",
    accentBarClass: "from-purple-400/80 via-purple-500/70 to-purple-400/80",
    },
    "matching-mangadex": {
    icon: Compass,
    iconWrapperClass:
    "bg-sky-500/15 text-sky-600 shadow-inner shadow-sky-500/20 dark:bg-sky-500/15 dark:text-sky-200",
    accentBarClass: "from-sky-400/80 via-sky-500/70 to-sky-400/80",
    },
    "matching-extra-searches": {
    icon: Search,
    iconWrapperClass:
    "bg-orange-500/15 text-orange-600 shadow-inner shadow-orange-500/20 dark:bg-orange-500/15 dark:text-orange-200",
    accentBarClass: "from-orange-400/80 via-orange-500/70 to-orange-400/80",
    },
    "matching-comick": {
    icon: Globe2,
    iconWrapperClass:
    "bg-amber-500/15 text-amber-600 shadow-inner shadow-amber-500/20 dark:bg-amber-500/15 dark:text-amber-200",
    accentBarClass: "from-amber-400/80 via-amber-500/70 to-amber-400/80",
    },
    "matching-custom-rules": {
    icon: Wand2,
    iconWrapperClass:
    "bg-indigo-500/15 text-indigo-600 shadow-inner shadow-indigo-500/20 dark:bg-indigo-500/15 dark:text-indigo-200",
    accentBarClass: "from-indigo-400/80 via-blue-500/70 to-sky-400/80",
    },
    };

    const renderToggleSection = (opts: {
    id: string;
    title: string;
    description: string;
    checked?: boolean;
    field?: string;
    disabled?: boolean;
    badge?: string;
    icon?: LucideIcon;
    iconWrapperClass?: string;
    accentBarClass?: string;
    }) => (
    <motion.div
    id={opts.id}
    initial={{ opacity: 0, y: 8 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.3 }}
    >
    <Card
    className={cn(
    "relative overflow-hidden transition-all duration-200 hover:shadow-md",
    highlightedSectionId === opts.id &&
    "ring-2 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-blue-400 dark:ring-offset-slate-950",
    )}
    >
    {opts.accentBarClass ? (
    <span
    aria-hidden="true"
    className={cn(
    "bg-linear-to-r pointer-events-none absolute inset-x-0 top-0 h-1 opacity-80",
    opts.accentBarClass,
    )}
    />
    ) : null}
    <CardHeader className="flex flex-row items-start gap-4 space-y-0 pb-6">
    {opts.icon ? (
    <div
    className={cn(
    "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl",
    "shadow-inner shadow-black/5 dark:shadow-black/40",
    opts.iconWrapperClass,
    )}
    >
    <opts.icon className="h-5 w-5" />
    </div>
    ) : null}
    <div className="flex-1 space-y-1">
    <div className="flex items-center justify-between">
    <CardTitle className="text-base">
    {searchQuery
    ? highlightText(opts.title, searchQuery)
    : opts.title}
    </CardTitle>
    <Switch
    id={opts.id}
    checked={!!opts.checked}
    disabled={!!opts.disabled}
    onCheckedChange={(checked) => {
    if (!opts.field || opts.disabled) return;
    const updated = {
    ...matchConfig,
    ...(opts.field ? { [opts.field]: checked } : {}),
    } as MatchConfig;
    onMatchConfigChange(updated, opts.field);
    }}
    />
    </div>
    <CardDescription>
    {searchQuery
    ? highlightText(opts.description, searchQuery)
    : opts.description}
    </CardDescription>
    {opts.badge ? (
    <Badge
    variant="secondary"
    className="mt-2 inline-flex items-center gap-1 rounded-full bg-slate-900/5 px-2.5 py-1 text-[10px] font-medium uppercase tracking-wide text-slate-500 dark:bg-white/10 dark:text-slate-200"
    >
    {opts.badge}
    </Badge>
    ) : null}
    </div>
    </CardHeader>
    </Card>
    </motion.div>
    );

    // Map sectionId to a small descriptor used by the generic renderer.
    const sectionMap: Record<
    string,
    {
    id: string;
    title: string;
    description: string;
    field?: string;
    disabled?: boolean;
    badge?: string;
    checked?: boolean;
    icon?: LucideIcon;
    iconWrapperClass?: string;
    accentBarClass?: string;
    }
    > = {
    "matching-one-shots": {
    id: "matching-one-shots",
    title: "Ignore one shots in automatic matching",
    description:
    "Skip one-shot manga during automatic matching. Useful for reducing noise in match results.",
    field: "shouldIgnoreOneShots",
    checked: matchConfig.shouldIgnoreOneShots,
    },
    "matching-adult-content": {
    id: "matching-adult-content",
    title: "Ignore adult content in automatic matching",
    description:
    "Skip manga marked as adult content during automatic matching. Helps filter NSFW titles.",
    field: "shouldIgnoreAdultContent",
    checked: matchConfig.shouldIgnoreAdultContent,
    },
    "matching-blur-adult": {
    id: "matching-blur-adult",
    title: "Blur adult content images",
    description:
    "Automatically blur cover images for manga marked as adult content for privacy.",
    field: "blurAdultContent",
    checked: matchConfig.blurAdultContent,
    },
    "matching-mangadex": {
    id: "matching-mangadex",
    title: "Enable MangaDex alternative search",
    description:
    "Use MangaDex as a fallback search source when AniList doesn't have enough results.",
    field: "enableMangaDexSearch",
    checked: matchConfig.enableMangaDexSearch,
    },
    "matching-extra-searches": {
    id: "matching-extra-searches",
    title: "Enable extra title searches",
    description:
    "Perform additional searches using title segments split on subtitles, colons, ellipses, dashes, slashes, parentheses, and other punctuation when the main query returns no results.",
    field: "enableExtraTitleSearches",
    checked: matchConfig.enableExtraTitleSearches,
    },
    "matching-comick": {
    id: "matching-comick",
    title: "Enable Comick alternative search",
    description:
    "Use Comick as a fallback search source when AniList doesn't have enough results. Currently unavailable due to API downtime.",
    disabled: true,
    badge: "Unavailable",
    checked: false,
    },
    };

    if (sectionId === "matching-custom-rules") {
    return (
    <motion.div
    id="matching-custom-rules"
    className={cn(
    "group relative overflow-hidden rounded-lg border border-slate-200/70 bg-white/95 p-0 shadow-[0_28px_90px_-60px_rgba(15,23,42,0.45)] dark:border-white/10 dark:bg-slate-950/45 dark:shadow-[0_40px_110px_-65px_rgba(15,23,42,0.85)]",
    highlightedSectionId === "matching-custom-rules" &&
    "ring-2 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-blue-400 dark:ring-offset-slate-950",
    )}
    initial={{ opacity: 0, y: 8 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.3 }}
    >
    <CustomRulesManager />
    </motion.div>
    );
    }

    if (sectionId === "matching-blacklist") {
    return (
    <motion.div
    id="matching-blacklist"
    className={cn(
    "group relative overflow-hidden rounded-lg border border-slate-200/70 bg-white/95 p-0 shadow-[0_28px_90px_-60px_rgba(15,23,42,0.45)] dark:border-white/10 dark:bg-slate-950/45 dark:shadow-[0_40px_110px_-65px_rgba(15,23,42,0.85)]",
    highlightedSectionId === "matching-blacklist" &&
    "ring-2 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-blue-400 dark:ring-offset-slate-950",
    )}
    initial={{ opacity: 0, y: 8 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.3 }}
    >
    <BlacklistManager />
    </motion.div>
    );
    }

    const descriptor = sectionMap[sectionId];
    if (descriptor) {
    const visual = sectionVisuals[sectionId];
    return renderToggleSection({ ...descriptor, ...visual });
    }

    return null;
    }