Zum Inhalt

src/player/resolvePreset.ts

// 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;
  }
}