import { useMemo, useState } from 'react';
import type { MissionInfo } from '@reforger-panel/shared';
import { useConfiguration, useMissions, useSetPerformanceSettings } from '../api/hooks.js';
import { Badge, Button, Card, EmptyState, Notice, SearchInput, Spinner, useToast } from './ui.js';
import { Icon } from './icons.js';
const SCENARIO_PATTERN = /^\{[0-9A-Fa-f]{16}\}[^\0\r\n]+\.conf$/;
/** Display form of a scenario id: just the file name, e.g. "23_Campaign.conf". */
export function shortScenario(scenarioId: string): string {
const slash = scenarioId.lastIndexOf('/');
return slash >= 0 ? scenarioId.slice(slash + 1) : scenarioId;
}
/**
* Mission picker.
*
* Scenario discovery is now reliable: the vanilla list is bundled and merged
* with whatever the server prints at boot, and modded scenarios come from the
* Workshop v2 `scenarios[].gameId` field rather than being scraped out of prose.
* The raw id input is kept, but demoted to a fallback.
*/
export function MissionCard({ slug, canEdit }: { slug: string; canEdit: boolean }) {
const toast = useToast();
const { data: config, refetch: refetchConfig } = useConfiguration(slug);
const {
data: missions,
isLoading: missionsLoading,
refetch: refetchMissions,
} = useMissions(slug);
const save = useSetPerformanceSettings(slug);
const [query, setQuery] = useState('');
const [manual, setManual] = useState('');
const [showManual, setShowManual] = useState(false);
const current = config?.config.scenarioId ?? '';
const groups = useMemo(() => {
if (!missions) return [];
const needle = query.trim().toLowerCase();
if (!needle) return missions.groups;
return missions.groups
.map((group) => ({
...group,
missions: group.missions.filter(
(mission) =>
mission.name.toLowerCase().includes(needle) ||
mission.scenarioId.toLowerCase().includes(needle) ||
(mission.gameMode ?? '').toLowerCase().includes(needle),
),
}))
.filter((group) => group.missions.length > 0);
}, [missions, query]);
const known = useMemo(
() =>
new Set(
(missions?.groups ?? []).flatMap((group) =>
group.missions.map((mission) => mission.scenarioId),
),
),
[missions],
);
const currentMission = useMemo(() => {
for (const group of missions?.groups ?? []) {
const match = group.missions.find((mission) => mission.scenarioId === current);
if (match) return { mission: match, groupLabel: group.label };
}
return null;
}, [missions, current]);
const apply = (scenarioId: string) => {
if (!SCENARIO_PATTERN.test(scenarioId)) {
toast('That does not look like a scenario id ({16 hex}Missions/….conf).', 'danger');
return;
}
save.mutate(
{
settings: { scenarioId },
expectedRevision: config?.revision,
writeStartupVars: true,
},
{
onSuccess: () => {
setManual('');
void refetchConfig();
toast('Mission saved to config.json. Restart the server to switch.', 'ok');
},
onError: (error) => toast(error.message, 'danger'),
},
);
};
if (!config) {
return (
{mission.name}
{active &&
{mission.scenarioId}