// src/player/resolvePreset.ts
import type { Preset } from '~/engine/types';
import { BUILT_IN_PRESETS } from '~/presets/builtIn';
import { PresetParamsSchema, PresetSchema } from '~/presets/schema';
import { decodePreset } from '~/share/encodePreset';
export type SourceKind = 'token' | 'builtin' | 'url' | 'json';
export interface ParsedSource {
kind: SourceKind;
value: string;
}
const PREFIXES: readonly SourceKind[] = ['token', 'builtin', 'url', 'json'];
function isBuiltinId(id: string): boolean {
return BUILT_IN_PRESETS.some((p) => p.id === id);
}
/** Splits an explicit `kind:value` prefix, or auto-detects when none: a known
* built-in id wins, otherwise the string is treated as a share token. */
export function parseSource(raw: string): ParsedSource {
const trimmed = raw.trim();
const colon = trimmed.indexOf(':');
if (colon > 0) {
const maybe = trimmed.slice(0, colon) as SourceKind;
if (PREFIXES.includes(maybe)) return { kind: maybe, value: trimmed.slice(colon + 1) };
}
return isBuiltinId(trimmed)
? { kind: 'builtin', value: trimmed }
: { kind: 'token', value: trimmed };
}
/** Validates arbitrary data as a full Preset, or as bare PresetParams which get
* wrapped in a minimal envelope. Returns null on failure. */
function coerceToPreset(data: unknown): Preset | null {
const asPreset = PresetSchema.safeParse(data);
if (asPreset.success) return { ...(asPreset.data as Preset), builtIn: false };
const asParams = PresetParamsSchema.safeParse(data);
if (asParams.success) {
return {
id: 'custom',
name: 'Custom',
builtIn: false,
tags: [],
tagSignal: 'pearl',
params: asParams.data,
} as Preset;
}
return null;
}
export interface ResolveOptions {
fetchFn?: typeof fetch;
}
export async function resolvePreset(
source: string,
opts: ResolveOptions = {},
): Promise<Preset | null> {
const { kind, value } = parseSource(source);
try {
switch (kind) {
case 'builtin': {
const hit = BUILT_IN_PRESETS.find((p) => p.id === value);
return hit ? { ...hit } : null;
}
case 'token':
return decodePreset(value);
case 'json':
return coerceToPreset(JSON.parse(value));
case 'url': {
const f = opts.fetchFn ?? fetch;
const res = await f(value);
if (!res.ok) return null;
return coerceToPreset(await res.json());
}
}
} catch {
return null;
}
}