import { serializeThemeOverrideDraft } from './draft'; import type { CandidateEvaluationResult, ExpandedThemeOverride, OverrideDiagnostic, ParsedThemeOverride, Preset, SelectionId, ThemeOverride, ThemeOverrideEditorDraft, ThemeOverrideEditorRuntime, } from './types'; export type EvaluateThemeOverrideDraftResult = | { readonly valid: true; readonly override: ThemeOverride; readonly parsed: ParsedThemeOverride; readonly expanded: ExpandedThemeOverride; readonly diagnostics: readonly OverrideDiagnostic[]; } | { readonly valid: false; readonly diagnostics: readonly OverrideDiagnostic[]; }; export type PreviewThemeOverrideDraftResult = | { readonly applied: true; readonly draft: ThemeOverrideEditorDraft; readonly session: TSession; readonly expanded: ExpandedThemeOverride; readonly override: ThemeOverride; } | { readonly applied: false; readonly draft: ThemeOverrideEditorDraft; readonly session: TSession; readonly diagnostics: readonly OverrideDiagnostic[]; }; function hasErrors(diagnostics: readonly OverrideDiagnostic[]): boolean { return diagnostics.some(({ severity }) => severity === 'error'); } function withDiagnostics( draft: ThemeOverrideEditorDraft, diagnostics: readonly OverrideDiagnostic[], artifactRevision = draft.artifactRevision, ): ThemeOverrideEditorDraft { return { ...draft, diagnostics, artifactRevision, }; } export function evaluateThemeOverrideDraft( draft: ThemeOverrideEditorDraft, runtime: ThemeOverrideEditorRuntime, ): EvaluateThemeOverrideDraftResult { const serialized = serializeThemeOverrideDraft(draft); if (!serialized.valid) return serialized; const parsed = runtime.parse(serialized.override); if (!parsed.valid) return parsed; const expanded = runtime.expand(parsed.parsed, draft.previewPlatform); if (!expanded.valid) { return { valid: false, diagnostics: expanded.diagnostics, }; } const diagnostics = [...parsed.diagnostics, ...expanded.expanded.diagnostics]; if (hasErrors(diagnostics)) { return { valid: false, diagnostics, }; } return { valid: true, override: serialized.override, parsed: parsed.parsed, expanded: expanded.expanded, diagnostics, }; } export function previewThemeOverrideDraft( draft: ThemeOverrideEditorDraft, session: TSession, runtime: ThemeOverrideEditorRuntime, ): PreviewThemeOverrideDraftResult { const evaluated = evaluateThemeOverrideDraft(draft, runtime); if (!evaluated.valid) { return { applied: false, draft: withDiagnostics(draft, evaluated.diagnostics), session, diagnostics: evaluated.diagnostics, }; } const applied = runtime.applyPreview(evaluated.expanded, session); if (!applied.applied) { const diagnostics = [...evaluated.diagnostics, ...applied.diagnostics]; return { applied: false, draft: withDiagnostics(draft, diagnostics, evaluated.expanded.revision.id), session, diagnostics, }; } return { applied: true, draft: withDiagnostics(draft, evaluated.diagnostics, evaluated.expanded.revision.id), session: applied.session, expanded: evaluated.expanded, override: evaluated.override, }; } export function evaluateThemeOverrideCandidate( draft: ThemeOverrideEditorDraft, selectionId: SelectionId, candidate: unknown, runtime: ThemeOverrideEditorRuntime, ): CandidateEvaluationResult { const serialized = serializeThemeOverrideDraft(draft); if (!serialized.valid) { return { accepted: false, diagnostics: serialized.diagnostics, }; } const parsed = runtime.parse(serialized.override); if (!parsed.valid) { return { accepted: false, diagnostics: parsed.diagnostics, }; } return runtime.evaluateCandidate({ parsed: parsed.parsed, platform: draft.previewPlatform, selectionId, candidate, }); } export function validateDisplayFontForPreset( draft: ThemeOverrideEditorDraft, allowedFonts: Readonly>, ): readonly OverrideDiagnostic[] { const preset = draft.basePreset.displayValue; const displayFont = draft.inputs.displayFont.displayValue; if (allowedFonts[preset].includes(displayFont)) return []; return [ { code: 'override.invalid_value', severity: 'error', phase: 'intent', message: `Display font "${displayFont}" is not available for preset "${preset}".`, intentPath: 'inputs.displayFont', }, ]; }