/**
 * Formats generated code with the consumer project's Prettier so style and lockfile
 * hashes stay consistent. Loaded programmatically once per workspace root,
 * cached for the process lifetime. Falls back gracefully when Prettier is
 * missing or plugins crash.
 */
import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
const cacheByRoot = new Map();
const failedRoots = new Set();
/**
 * Walk up from `startDir` to find the nearest directory containing
 * `package.json`. Returns `null` if none is found before the filesystem root.
 *
 * Used to resolve which workspace owns the file being written, so Prettier and
 * its plugins are loaded from THAT workspace's `node_modules` (e.g. when
 * generating frontend files, load Prettier from `frontend/`, not from the
 * monorepo root which may not declare it under pnpm 11+ stricter hoisting).
 */
function findPackageRoot(startDir) {
    let dir = startDir;
    const { root } = path.parse(dir);
    while (dir !== root) {
        if (fs.existsSync(path.join(dir, 'package.json')))
            return dir;
        dir = path.dirname(dir);
    }
    return null;
}
async function getPrettier(root) {
    if (failedRoots.has(root))
        return null;
    const hit = cacheByRoot.get(root);
    if (hit)
        return hit;
    try {
        const requireFromConsumer = createRequire(path.join(root, 'package.json'));
        const instance = requireFromConsumer('prettier');
        const rawConfig = (await instance.resolveConfig(path.join(root, 'index.ts'), {
            editorconfig: true,
        })) ?? {};
        // Resolve plugin strings to absolute paths from the workspace root, not
        // from the .prettierrc location. Under pnpm 11+ stricter hoisting, the
        // .prettierrc may live at the monorepo root while plugins are installed
        // in a sub-workspace (frontend/, backend/) — Prettier's default plugin
        // resolution starts from the config file's directory and would fail.
        // Passing absolute paths bypasses Prettier's lookup entirely.
        const config = { ...rawConfig };
        if (Array.isArray(rawConfig.plugins)) {
            config.plugins = rawConfig.plugins.map((p) => typeof p === 'string' ? requireFromConsumer.resolve(p) : p);
        }
        const entry = { config, instance, pluginsWork: null };
        cacheByRoot.set(root, entry);
        return entry;
    }
    catch {
        failedRoots.add(root);
        return null;
    }
}
/**
 * Format code with the consumer project's Prettier, loading it from the
 * workspace that owns `filepath` (the nearest ancestor with a `package.json`).
 *
 * Behavior:
 *   1. Tries with plugins declared in the consumer's `.prettierrc`.
 *   2. If plugins crash on the first attempt, retries without them and
 *      remembers the decision per workspace.
 *   3. Returns the original code unchanged if Prettier is not installed in
 *      that workspace.
 */
export async function formatCode(code, filepath) {
    const root = findPackageRoot(path.dirname(path.resolve(filepath)));
    if (!root)
        return code;
    const p = await getPrettier(root);
    if (!p)
        return code;
    // Fast path — plugins-decision already cached
    if (p.pluginsWork === true) {
        return p.instance.format(code, { ...p.config, filepath });
    }
    if (p.pluginsWork === false) {
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { plugins, ...safeConfig } = p.config;
        return p.instance.format(code, { ...safeConfig, filepath });
    }
    try {
        const result = await p.instance.format(code, { ...p.config, filepath });
        p.pluginsWork = true;
        return result;
    }
    catch {
        p.pluginsWork = false;
        try {
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const { plugins, ...safeConfig } = p.config;
            return await p.instance.format(code, { ...safeConfig, filepath });
        }
        catch {
            return code;
        }
    }
}
