import { ThemeCoats, ThemeObject, ThemeValueLike } from '@o/css' import { isPlainObj } from '../helpers/helpers' import { ThemeValue } from './ThemeValue' export type CompiledTheme = any> = { [key in keyof A]: A[key] extends ThemeCoats | string | number ? A[key] : ThemeValueLike } let id = 0 const ThemeMap = new WeakMap() export function createTheme>( theme: A, prefixName: string = '', renameKeys = true, ): CompiledTheme { if (ThemeMap.has(theme)) { return theme } const name = `${prefixName ? prefixName + '-' : ''}${theme.name || ''}` || `theme-${id++}` const res = Object.keys(theme).reduce((acc, key) => { let val = theme[key] const cssVariableName = `${key}` if (key === 'coats' && val) { const coats = val val = {} for (const coatKey in coats) { const coat = coats[coatKey] val[coatKey] = typeof coat === 'function' ? coat : createTheme({ ...coat, name: coatKey, coats: undefined }, `${name}-coat`) } } else if (val && typeof val.setCSSVariable === 'function') { if (renameKeys) { const res = val.setCSSVariable(cssVariableName) if (res) { val = res } } } else { if (key !== 'parent' && key !== 'name' && key[0] !== '_') { // recurse into sub-themes if (isPlainObj(val)) { val = createTheme(val, `${name}-sub-${key}`) } else if (!(val instanceof ThemeValue)) { val = new ThemeValue(cssVariableName, val) } } } acc[key] = val return acc }, {}) as any res.name = name ThemeMap.set(res, true) return res } const ThemesMap = new WeakMap() export function createThemes>(themes: { [key: string]: A }): { [key: string]: CompiledTheme } { if (ThemesMap.has(themes)) { return ThemesMap.get(themes) } // ensure we don't have name collisions const existing = new Set() const duplicates = new Set>() const next = Object.freeze( Object.keys(themes).reduce((acc, key) => { let theme = themes[key] if (typeof theme !== 'function') { if (duplicates.has(theme)) { acc[key] = theme! return acc } else { theme.name = key } } const name = theme.name! if (process.env.NODE_ENV === 'development') { if (existing.has(name)) { console.warn( 'Themes error on themes:', themes, Object.keys(themes).map(k => `key: ${k}, name: ${themes[k].name}`), ) throw new Error(`Name collision on theme: ${name}`) } existing.add(name) } duplicates.add(theme) if (name) { acc[key] = createTheme(theme) } else { throw new Error(`Unreachable`) } return acc }, {}), ) ThemesMap.set(themes, next) return next }