"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withCssInterop = withCssInterop;
const fs_1 = __importDefault(require("fs"));
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const module_1 = require("module");
const nativewindRequire = (0, module_1.createRequire)(require.resolve("nativewind/metro"));
const connect_1 = __importDefault(nativewindRequire("connect"));
const debug_1 = nativewindRequire("debug");
const css_to_rn_1 = nativewindRequire("react-native-css-interop/dist/css-to-rn");
const expo_1 = nativewindRequire("react-native-css-interop/dist/metro/expo");
let haste;
let virtualModulesPossible = undefined;
const virtualModules = new Map();
const outputDirectory = path_1.default.resolve(__dirname, "../.cache/react-native-css-interop");
const isRadonIDE = "REACT_NATIVE_IDE_LIB_PATH" in process.env;
function withCssInterop(config, options) {
    return typeof config === "function"
        ? async function WithCSSInterop() {
            return getConfig(await config(), options);
        }
        : getConfig(config, options);
}
function getConfig(config, options) {
    const debug = (0, debug_1.debug)(options.parent?.name || "react-native-css-interop");
    debug("withCssInterop");
    debug(`outputDirectory ${outputDirectory}`);
    debug(`isRadonIDE: ${isRadonIDE}`);
    (0, expo_1.expoColorSchemeWarning)();
    const originalResolver = config.resolver?.resolveRequest;
    const originalGetTransformOptions = config.transformer?.getTransformOptions;
    const originalMiddleware = config.server?.enhanceMiddleware;
    const poisonPillPath = "./interop-poison.pill";
    fs_1.default.mkdirSync(outputDirectory, { recursive: true });
    fs_1.default.writeFileSync(platformPath("ios"), "");
    fs_1.default.writeFileSync(platformPath("android"), "");
    fs_1.default.writeFileSync(platformPath("native"), "");
    fs_1.default.writeFileSync(platformPath("macos"), "");
    fs_1.default.writeFileSync(platformPath("windows"), "");
    return {
        ...config,
        transformerPath: require.resolve("react-native-css-interop/dist/metro/transformer"),
        transformer: {
            ...config.transformer,
            ...{
                cssInterop_transformerPath: config.transformerPath,
                cssInterop_outputDirectory: path_1.default.relative(process.cwd(), outputDirectory),
            },
            async getTransformOptions(entryPoints, transformOptions, getDependenciesOf) {
                debug(`getTransformOptions.dev ${transformOptions.dev}`);
                debug(`getTransformOptions.platform ${transformOptions.platform}`);
                debug(`getTransformOptions.virtualModulesPossible ${Boolean(virtualModulesPossible)}`);
                const platform = transformOptions.platform || "native";
                const filePath = platformPath(platform);
                if (virtualModulesPossible) {
                    await virtualModulesPossible;
                    await startCSSProcessor(filePath, platform, transformOptions.dev, options, debug);
                }
                const writeToFileSystem = !virtualModulesPossible || !transformOptions.dev;
                debug(`getTransformOptions.writeToFileSystem ${writeToFileSystem}`);
                if (writeToFileSystem) {
                    debug(`getTransformOptions.output ${filePath}`);
                    const watchFn = isRadonIDE
                        ? async (css) => {
                            const output = platform === "web"
                                ? css.toString()
                                : getNativeJS((0, css_to_rn_1.cssToReactNativeRuntime)(css, options, debug), debug);
                            await promises_1.default.writeFile(filePath, output);
                        }
                        : undefined;
                    const css = await options.getCSSForPlatform(platform, watchFn);
                    const output = platform === "web"
                        ? css.toString("utf-8")
                        : getNativeJS((0, css_to_rn_1.cssToReactNativeRuntime)(css, options, debug), debug);
                    await promises_1.default.mkdir(outputDirectory, { recursive: true });
                    await promises_1.default.writeFile(filePath, output);
                    if (platform !== "web") {
                        await promises_1.default.writeFile(filePath.replace(/\.js$/, ".map"), "");
                    }
                    debug(`getTransformOptions.finished`);
                }
                return Object.assign({}, await originalGetTransformOptions?.(entryPoints, transformOptions, getDependenciesOf));
            },
        },
        server: {
            ...config.server,
            enhanceMiddleware: (middleware, metroServer) => {
                debug(`enhanceMiddleware.setup`);
                const server = (0, connect_1.default)();
                const bundler = metroServer.getBundler().getBundler();
                if (options.forceWriteFileSystem) {
                    debug(`forceWriteFileSystem true`);
                }
                else {
                    if (!isRadonIDE) {
                        virtualModulesPossible = bundler
                            .getDependencyGraph()
                            .then(async (graph) => {
                            haste = graph?._haste;
                            if (graph?._fileSystem) {
                                ensureFileSystemPatched(graph._fileSystem);
                            }
                            ensureBundlerPatched(bundler);
                        });
                        server.use(async (_, __, next) => {
                            await virtualModulesPossible;
                            next();
                        });
                    }
                }
                return originalMiddleware
                    ? server.use(originalMiddleware(middleware, metroServer))
                    : server.use(middleware);
            },
        },
        resolver: {
            ...config.resolver,
            sourceExts: [...(config?.resolver?.sourceExts || []), "css"],
            resolveRequest: (context, moduleName, platform) => {
                if (moduleName === poisonPillPath) {
                    return { type: "empty" };
                }
                const resolver = originalResolver ?? context.resolveRequest;
                const resolved = resolver(context, moduleName, platform);
                if (!("filePath" in resolved && resolved.filePath === options.input)) {
                    return resolved;
                }
                platform = platform || "native";
                const filePath = platformPath(platform);
                const development = context.isDev || context.dev;
                const isWebProduction = !development && platform === "web";
                debug(`resolveRequest.input ${resolved.filePath}`);
                debug(`resolveRequest.resolvedTo: ${filePath}`);
                debug(`resolveRequest.development: ${development}`);
                debug(`resolveRequest.platform: ${platform}`);
                if (virtualModulesPossible && !isWebProduction) {
                    startCSSProcessor(filePath, platform, development, options, debug);
                }
                return {
                    ...resolved,
                    filePath,
                };
            },
        },
    };
}
async function startCSSProcessor(filePath, platform, isDev, { input, getCSSForPlatform, ...options }, debug) {
    if (virtualModules.has(filePath)) {
        return;
    }
    debug(`virtualModules ${filePath}`);
    debug(`virtualModules.isDev ${isDev}`);
    debug(`virtualModules.size ${virtualModules.size}`);
    options = {
        cache: {
            keyframes: new Map(),
            rules: new Map(),
            rootVariables: {},
            universalVariables: {},
        },
        ...options,
    };
    if (!isDev) {
        debug(`virtualModules.fastRefresh disabled`);
        virtualModules.set(filePath, getCSSForPlatform(platform).then((css) => {
            return platform === "web"
                ? css
                : getNativeJS((0, css_to_rn_1.cssToReactNativeRuntime)(css, options), debug);
        }));
    }
    else {
        debug(`virtualModules.fastRefresh enabled`);
        virtualModules.set(filePath, getCSSForPlatform(platform, (css) => {
            debug(`virtualStyles.update ${platform}`);
            virtualModules.set(filePath, Promise.resolve(platform === "web"
                ? css
                : getNativeJS((0, css_to_rn_1.cssToReactNativeRuntime)(css, options), debug)));
            debug(`virtualStyles.emit ${platform}`);
            if (haste?.emit) {
                haste.emit("change", {
                    eventsQueue: [
                        {
                            filePath,
                            metadata: {
                                modifiedTime: Date.now(),
                                size: 1,
                                type: "virtual",
                            },
                            type: "change",
                        },
                    ],
                });
            }
        }).then((css) => {
            debug(`virtualStyles.initial ${platform}`);
            return platform === "web"
                ? css
                : getNativeJS((0, css_to_rn_1.cssToReactNativeRuntime)(css, options), debug);
        }));
    }
}
function ensureFileSystemPatched(fs) {
    if (!fs || typeof fs.getSha1 !== "function") {
        return fs;
    }
    if (!fs.getSha1.__css_interop_patched) {
        const original_getSha1 = fs.getSha1.bind(fs);
        fs.getSha1 = (filename) => {
            if (virtualModules.has(filename)) {
                return `${filename}-${Date.now()}`;
            }
            return original_getSha1(filename);
        };
        fs.getSha1.__css_interop_patched = true;
    }
    return fs;
}
function ensureBundlerPatched(bundler) {
    if (bundler.transformFile.__css_interop__patched) {
        return;
    }
    const transformFile = bundler.transformFile.bind(bundler);
    bundler.transformFile = async function (filePath, transformOptions, fileBuffer) {
        const virtualModule = virtualModules.get(filePath);
        if (virtualModule) {
            fileBuffer = Buffer.from(await virtualModule);
        }
        return transformFile(filePath, transformOptions, fileBuffer);
    };
    bundler.transformFile.__css_interop__patched = true;
}
function getNativeJS(data = {}, debug) {
    debug("Start stringify");
    const output = `(${stringify(data)})`;
    debug(`Output size: ${Buffer.byteLength(output, "utf8")} bytes`);
    return output;
}
function platformPath(platform = "native") {
    return path_1.default.join(outputDirectory, `${platform}.${platform === "web" ? "css" : "js"}`);
}
function stringify(data) {
    switch (typeof data) {
        case "bigint":
        case "symbol":
        case "function":
            throw new Error(`Cannot stringify ${typeof data}`);
        case "string":
            return `"${data}"`;
        case "number":
            return `${Math.round(data * 1000) / 1000}`;
        case "boolean":
            return `${data}`;
        case "undefined":
            return "null";
        case "object": {
            if (data === null) {
                return "null";
            }
            else if (Array.isArray(data)) {
                return `[${data
                    .map((value) => {
                    return value === null || value === undefined
                        ? ""
                        : stringify(value);
                })
                    .join(",")}]`;
            }
            else {
                return `{${Object.entries(data)
                    .flatMap(([key, value]) => {
                    if (value === null || value === undefined) {
                        return [];
                    }
                    if (key.match(/[^a-zA-Z]/)) {
                        key = `"${key}"`;
                    }
                    value = stringify(value);
                    return [`${key}:${value}`];
                })
                    .join(",")}}`;
            }
        }
    }
}
//# sourceMappingURL=index.js.map
