/* RFC-0220 Material Credits helpers shared by UI and Site OS commands. Parses sidecar YAML records, resolves localized/default-language fallback, and builds simple schema.org CreativeWork-style payloads from the same structured source. Do not scan the filesystem; callers supply raw sidecar maps. Do not render UI; @warpgogol/werkstatt-site/ui owns presentation. RFC-0220: initial helper surface for material credit sidecars. RFC-0227: buildMaterialCreditJsonLd emits @id for page-graph linking. */ import { parse as parseYaml } from "yaml"; import { type MaterialCredit, type MaterialCreditLabels, type MaterialKind, type MaterialTarget, materialCreditSchema, materialTargetKey, formatMaterialCreditLine, } from "../schemas/material-credit.ts"; // RFC-0488: re-export label mapping helpers for @warpgogol/werkstatt-shared/attribution/material-credits consumers. export { labelForSourceType, labelForStatus, labelForUsageBasis, } from "../schemas/material-credit.ts"; export type { MaterialSourceType, MaterialCreditStatus, UsageBasisType, } from "../schemas/material-credit.ts"; export interface MaterialCreditRecord { file: string; lang?: string; credit: MaterialCredit; } export type RawMaterialCreditMap = Record; function rawToString(raw: string | { default?: string }): string { return typeof raw === "string" ? raw : (raw.default ?? ""); } export function langFromCreditPath(path: string): string | undefined { const match = path.match(/\/src\/content\/[^/]+\/([^/]+)\//); if (!match) return undefined; const segment = match[1]; // RFC-0220: language-agnostic asset folders (surface/assets/, shared/assets/) // carry no lang segment; the sidecar itself is the canonical record. return segment === "assets" || segment === "media" ? undefined : segment; } export function parseMaterialCreditMap(rawMap: RawMaterialCreditMap): MaterialCreditRecord[] { const records: MaterialCreditRecord[] = []; for (const [file, raw] of Object.entries(rawMap)) { const parsed = parseYaml(rawToString(raw)); const credit = materialCreditSchema.parse(parsed); records.push({ file, lang: langFromCreditPath(file), credit }); } return records; } export function findMaterialCredit( records: MaterialCreditRecord[], target: MaterialTarget, lang: string, defaultLang: string, ): MaterialCredit | null { const candidates = lang === defaultLang ? [lang, undefined] : [lang, defaultLang, undefined]; for (const candidateLang of candidates) { const key = materialTargetKey(target, candidateLang); const record = records.find((item) => { const recordLang = item.credit.target.lang ?? item.lang; return materialTargetKey(item.credit.target, recordLang) === key; }); if (record) return record.credit; } return null; } export function creditByTarget( rawMap: RawMaterialCreditMap, target: MaterialTarget, lang: string, defaultLang: string, ): MaterialCredit | null { return findMaterialCredit(parseMaterialCreditMap(rawMap), target, lang, defaultLang); } export function schemaTypeForMaterial( kind: MaterialKind, ): "ImageObject" | "VideoObject" | "CreativeWork" { if (kind === "image") return "ImageObject"; if (kind === "video") return "VideoObject"; return "CreativeWork"; } /** * RFC-0227: stable @id for a material credit node, derived from the content URL. * Used by both the disclosure