"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.modernTheme = void 0;
const postcss_1 = __importDefault(require("postcss"));
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = __importDefault(require("fs"));
const dlv_1 = __importDefault(require("dlv"));
const flat_1 = __importDefault(require("flat"));
const dset_1 = require("dset");
const localize_identifier_1 = __importDefault(require("../localize-identifier"));
const common_1 = require("../common");
/** Create a CSS variable override block for a given selector */
const createModernTheme = (selector, theme, transform) => {
    const rule = postcss_1.default.rule({ selector });
    const decls = Object.entries(flat_1.default(theme)).map(([prop, value]) => postcss_1.default.decl({
        prop: `--${transform(prop)}`,
        value: `${value}`,
    }));
    if (decls.length === 0) {
        return;
    }
    rule.append(decls);
    return rule;
};
/** Merge a given theme with a base theme */
const mergeConfigs = (theme, defaultTheme) => {
    const merged = defaultTheme;
    for (const [colorScheme, values] of Object.entries(theme)) {
        if (!values) {
            continue;
        }
        for (const [key, value] of Object.entries(values)) {
            merged[colorScheme][key] = value;
        }
    }
    return merged;
};
const defaultLocalizeFunction = (name, filePath, css) => {
    const hash = crypto_1.default.createHash('md5').update(css).digest('hex').slice(0, 6);
    return `${filePath || 'default'}-${name}-${hash}`;
};
const getLocalizeFunction = (modules, resourcePath) => {
    if (typeof modules === 'function' || modules === 'default') {
        let fileContents = '';
        if (resourcePath) {
            fileContents = fs_1.default.readFileSync(resourcePath, 'utf8');
        }
        const localize = typeof modules === 'function' ? modules : defaultLocalizeFunction;
        return (name) => {
            return localize(name, resourcePath || '', fileContents);
        };
    }
    return (name) => localize_identifier_1.default({ resourcePath }, modules || '[local]', name);
};
const declarationsAsString = ({ nodes }) => {
    if (!nodes) {
        return '';
    }
    return nodes
        .filter((node) => node.type === 'decl')
        .map((declaration) => `${declaration.prop}: ${declaration.value};`)
        .join('');
};
/** Accomplish theming by creating CSS variable overrides  */
const modernTheme = (root, componentConfig, options) => {
    var _a;
    const usage = new Map();
    const defaultTheme = options.defaultTheme || 'default';
    const singleTheme = options.forceSingleTheme || undefined;
    const optimizeSingleTheme = options.optimizeSingleTheme;
    const inlineRootThemeVariables = (_a = options.inlineRootThemeVariables) !== null && _a !== void 0 ? _a : true;
    const lightClass = options.lightClass || '.light';
    const darkClass = options.darkClass || '.dark';
    const resourcePath = root.source ? root.source.input.file : '';
    const localize = (name) => getLocalizeFunction(options.modules, resourcePath)(name.replace(/\./g, '-'));
    const defaultThemeConfig = Object.entries(componentConfig).find(([theme]) => theme === defaultTheme);
    const hasRootDarkMode = defaultThemeConfig && common_1.hasDarkMode(defaultThemeConfig[1]);
    // For single theme mode, we need to handle themes that may be incomplete
    // In that case, we merge the theme with default so all variables are present
    const singleThemeConfig = Object.entries(componentConfig).find(([theme]) => theme === singleTheme);
    let mergedSingleThemeConfig = defaultThemeConfig
        ? defaultThemeConfig[1]
        : { light: {}, dark: {} };
    if (defaultThemeConfig && singleThemeConfig && defaultTheme !== singleTheme) {
        mergedSingleThemeConfig = mergeConfigs(singleThemeConfig[1], defaultThemeConfig[1]);
    }
    const hasMergedDarkMode = mergedSingleThemeConfig && common_1.hasDarkMode(mergedSingleThemeConfig);
    // 1a. walk again to optimize inline default values
    root.walkRules((rule) => {
        rule.selector = common_1.replaceThemeRoot(rule.selector);
        rule.walkDecls((decl) => {
            decl.value.split(/(?=@theme)/g).forEach((chunk) => {
                const key = common_1.parseThemeKey(chunk);
                if (key) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const count = usage.has(key) ? usage.get(key) + 1 : 1;
                    usage.set(key, count);
                }
            });
        });
    });
    // 1b. Walk each declaration and replace theme vars with CSS vars
    root.walkRules((rule) => {
        rule.selector = common_1.replaceThemeRoot(rule.selector);
        rule.walkDecls((decl) => {
            let key = common_1.parseThemeKey(decl.value);
            while (key) {
                const themeValue = dlv_1.default(mergedSingleThemeConfig.light, key);
                if (singleTheme && !hasMergedDarkMode && optimizeSingleTheme) {
                    // If we are only building a single theme with light mode, we can optionally insert the value
                    if (themeValue) {
                        decl.value = common_1.replaceTheme(decl.value, themeValue);
                    }
                    else {
                        root.warn(root.toResult(), `Could not find key ${key} in theme configuration. Removing declaration.`, { node: decl });
                        decl.remove();
                        break;
                    }
                }
                else if (key && !themeValue) {
                    throw decl.error(`Could not find key ${key} in theme configuration.`, { word: decl.value });
                }
                else if (inlineRootThemeVariables &&
                    usage.has(key) &&
                    usage.get(key) === 1) {
                    decl.value = common_1.replaceTheme(decl.value, `var(--${localize(key)}, ${themeValue})`);
                }
                else if (key) {
                    decl.value = common_1.replaceTheme(decl.value, `var(--${localize(key)})`);
                }
                else {
                    throw decl.error(`Invalid theme usage: ${decl.value}`, {
                        word: decl.value,
                    });
                }
                key = common_1.parseThemeKey(decl.value);
            }
            if (decl.value.match(/@theme/g) || decl.value.match(/theme\s+\(['"]/g)) {
                throw decl.error(`Invalid theme usage: ${decl.value}`, {
                    word: decl.value,
                });
            }
        });
    });
    // 2. Create variable declaration blocks
    const filterUsed = (colorScheme, theme, filterFunction = (name) => usage.has(name)) => {
        const themeConfig = typeof theme === 'string' ? componentConfig[theme] : theme;
        const currentThemeConfig = themeConfig[colorScheme];
        const usedVariables = {};
        Array.from(usage.keys()).forEach((key) => {
            const value = dlv_1.default(currentThemeConfig, key);
            if (value && filterFunction(key) && typeof value !== 'object') {
                // If the dark and light theme have the same value don't include
                if (theme === defaultTheme) {
                    if (colorScheme === 'dark' && dlv_1.default(themeConfig.light, key) === value) {
                        return;
                    }
                }
                // If the theme value matches the base theme don't include
                if (defaultThemeConfig &&
                    typeof theme === 'string' &&
                    theme !== defaultTheme &&
                    dlv_1.default(defaultThemeConfig[1][colorScheme], key) === value) {
                    return;
                }
                dset_1.dset(usedVariables, key, value);
            }
        });
        return usedVariables;
    };
    const addRootTheme = (themeConfig) => {
        // If inlineRootThemeVariables then only add vars to root that are used more than once
        const func = inlineRootThemeVariables
            ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                (name) => usage.has(name) && usage.get(name) > 1
            : undefined;
        return createModernTheme(':root', filterUsed('light', themeConfig, func), localize);
    };
    // 2a. If generating a single theme, simply generate the default
    if (singleTheme) {
        const rules = [];
        const rootRules = addRootTheme(mergedSingleThemeConfig);
        if (hasMergedDarkMode) {
            rules.push(createModernTheme(darkClass, filterUsed('dark', mergedSingleThemeConfig), localize));
            rules.push(rootRules);
        }
        if (!optimizeSingleTheme) {
            rules.push(rootRules);
        }
        root.append(...rules.filter((x) => Boolean(x)));
        return;
    }
    const rules = [];
    // 2b. Under normal operation, generate CSS variable blocks for each theme
    Object.entries(componentConfig).forEach(([theme, themeConfig]) => {
        if (theme === defaultTheme) {
            rules.push(addRootTheme(themeConfig));
            rules.push(createModernTheme(darkClass, filterUsed('dark', defaultTheme), localize));
        }
        else if (common_1.hasDarkMode(themeConfig)) {
            rules.push(createModernTheme(`.${theme}${lightClass}`, filterUsed('light', theme), localize), createModernTheme(`.${theme}${darkClass}`, filterUsed('dark', theme), localize));
        }
        else {
            rules.push(createModernTheme(hasRootDarkMode ? `.${theme}${lightClass}` : `.${theme}`, filterUsed('light', theme), localize));
        }
    });
    const definedRules = [];
    rules.forEach((rule) => {
        if (!rule) {
            return;
        }
        const defined = definedRules.find((definedRule) => declarationsAsString(definedRule) === declarationsAsString(rule));
        if (defined) {
            defined.selector += `,${rule.selector}`;
        }
        else {
            definedRules.push(rule);
        }
    });
    root.append(...definedRules);
};
exports.modernTheme = modernTheme;
//# sourceMappingURL=index.js.map