var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import chalk from 'chalk';
import fs from 'fs';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import path from 'path';
import ReactRefreshTypeScript from 'react-refresh-typescript';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
import { generateScopedName } from './utils.js';
import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
import postcssUrl from 'postcss-url';
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = process.env.NODE_ENV === 'development';
const packageJson = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'));
const cssModes = ['tailwind', 'postcss'];
class WatchRunPlugin {
    apply(compiler) {
        compiler.hooks.watchRun.tap('watchRun', (comp) => {
            if (comp.modifiedFiles) {
                const changedFiles = Array.from(comp.modifiedFiles);
                console.log(chalk.magenta("Changed files sorted by 'modified time' stamps:"));
                const entriesToCheck = [];
                changedFiles.forEach((file) => {
                    try {
                        const stat = fs.statSync(file.toString());
                        console.log(`  ${chalk.magenta(file)}`);
                        entriesToCheck.push({ path: file, mtime: stat.mtime });
                        if (stat.isDirectory()) {
                            const contents = fs.readdirSync(file.toString());
                            contents.forEach((name) => {
                                const fullPath = path.join(file.toString(), name);
                                try {
                                    const innerStat = fs.statSync(fullPath);
                                    //if less than 2 minutes ago...
                                    if (innerStat.mtime > new Date(Date.now() - 2 * 60 * 1000)) {
                                        entriesToCheck.push({
                                            path: fullPath,
                                            mtime: innerStat.mtime,
                                        });
                                    }
                                }
                                catch (e) {
                                    entriesToCheck.push({
                                        path: fullPath,
                                        mtime: new Date(0),
                                        error: e.message,
                                    });
                                }
                            });
                        }
                    }
                    catch (err) {
                        entriesToCheck.push({
                            path: file,
                            mtime: new Date(0),
                            error: err.message,
                        });
                    }
                });
                entriesToCheck
                    .sort((a, b) => b.mtime - a.mtime)
                    .splice(0, 3)
                    .forEach((entry) => {
                    const label = entry.error
                        ? `[error: ${entry.error}]`
                        : `[${entry.mtime.toISOString()}]`;
                    console.log(`  ${entry.path} ${label}`);
                });
            }
        });
    }
}
/**
 * Converts a css class name to a unique scoped name (for CSS Modules)
 * @param context
 * @param currentFormat
 * @param name
 */
function getLocalIdent(context, currentFormat, name) {
    // return isProduction ? generateScopedNameProduction(name,context.resourcePath) : generateScopedName(name,context.resourcePath);
    return generateScopedName(name, context.resourcePath);
}
export const getLinkedConfig = () => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
    const linkedConfigPathJs = path.resolve(process.cwd(), 'linked.config.js');
    const linkedConfigPathJson = path.resolve(process.cwd(), 'linked.config.json');
    //default config
    let config = {
        //tailwind is default
        cssMode: cssModes[0],
        webpack: {
            cache: true,
            analyse: false,
        },
        server: {},
    };
    // Load from package.json or config files
    let loaded;
    if (typeof packageJson.linkedApp === 'object') {
        loaded = packageJson.linkedApp;
    }
    else if (fs.existsSync(linkedConfigPathJs)) {
        let linkedConfig = yield import(linkedConfigPathJs);
        loaded = linkedConfig.default;
    }
    else if (fs.existsSync(linkedConfigPathJson)) {
        loaded = JSON.parse(fs.readFileSync(linkedConfigPathJson, 'utf-8'));
    }
    // Backward compatibility: migrate flat structure to nested
    if (loaded) {
        config.cssMode = (_a = loaded.cssMode) !== null && _a !== void 0 ? _a : config.cssMode;
        // Move webpack properties
        config.webpack = {
            cache: (_d = (_c = (_b = loaded.webpack) === null || _b === void 0 ? void 0 : _b.cache) !== null && _c !== void 0 ? _c : loaded.cacheWebpack) !== null && _d !== void 0 ? _d : true,
            analyse: (_f = (_e = loaded.webpack) === null || _e === void 0 ? void 0 : _e.analyse) !== null && _f !== void 0 ? _f : loaded.analyse,
            plugins: (_h = (_g = loaded.webpack) === null || _g === void 0 ? void 0 : _g.plugins) !== null && _h !== void 0 ? _h : loaded.plugins,
            externals: (_k = (_j = loaded.webpack) === null || _j === void 0 ? void 0 : _j.externals) !== null && _k !== void 0 ? _k : loaded.externals,
            alias: (_m = (_l = loaded.webpack) === null || _l === void 0 ? void 0 : _l.alias) !== null && _m !== void 0 ? _m : loaded.alias,
            cssGlobalModulePaths: (_p = (_o = loaded.webpack) === null || _o === void 0 ? void 0 : _o.cssGlobalModulePaths) !== null && _p !== void 0 ? _p : loaded.cssGlobalModulePaths,
        };
        // Move server properties
        config.server = {
            multiCore: (_r = (_q = loaded.server) === null || _q === void 0 ? void 0 : _q.multiCore) !== null && _r !== void 0 ? _r : loaded.multiCore,
            cachePaths: (_t = (_s = loaded.server) === null || _s === void 0 ? void 0 : _s.cachePaths) !== null && _t !== void 0 ? _t : loaded.cachePaths,
            cacheTimeout: (_v = (_u = loaded.server) === null || _u === void 0 ? void 0 : _u.cacheTimeout) !== null && _v !== void 0 ? _v : loaded.cacheTimeout,
            loadAppComponent: (_x = (_w = loaded.server) === null || _w === void 0 ? void 0 : _w.loadAppComponent) !== null && _x !== void 0 ? _x : loaded.loadAppComponent,
        };
    }
    if (!cssModes.includes(config.cssMode)) {
        console.warn('Invalid value for property cssMode. Should be one of: ' +
            cssModes.join(', '));
        process.exit();
    }
    return config;
});
export const getWebpackAppConfig = () => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b, _c, _d, _e, _f;
    // set up the storage config for the app — tries the canonical
    // backend-storage-config at app root first, then the legacy scripts/
    // location, so older clones keep booting during the transition.
    const cwd = process.cwd();
    const candidates = [
        // Iter4 canonical:
        path.join(cwd, 'linked.backend.storage.ts'),
        path.join(cwd, 'linked.backend.storage.js'),
        // Iter3:
        path.join(cwd, 'backend-storage-config.ts'),
        path.join(cwd, 'backend-storage-config.js'),
        path.join(cwd, 'scripts', 'backend-storage-config.js'),
        // legacy:
        path.join(cwd, 'scripts', 'storage-config.js'),
    ];
    for (const candidate of candidates) {
        if (fs.existsSync(candidate)) {
            yield import(candidate);
            break;
        }
    }
    const accessURL = LinkedFileStorage.accessURL;
    // set up the public path for the app
    // for Capacitor apps (APP_ENV is set), use /bundles/ since Capacitor's webDir strips /public (see: capacitor.config.ts)
    // for web server builds, use /public/bundles/ to match express.static()
    const isCapacitorBuild = process.env.APP_ENV !== undefined;
    const publicPath = isCapacitorBuild ? '' : '/public';
    const bundlesPath = publicPath + '/bundles/';
    // ASSET_PATH is used to load the assets from the correct path.
    // - If ASSET_PATH is set in environment (app builds), use it directly.
    // - In development we want a port-agnostic relative path so the bundle URLs
    //   work no matter which PORT the dev server binds to (browsers resolve
    //   relative URLs against window.location.origin). Baking SITE_ROOT into
    //   the publicPath was a footgun when scaffolded apps overrode PORT.
    // - In production, use the absolute origin from accessURL (CDN, etc.).
    const ASSET_PATH = process.env.ASSET_PATH ||
        (isDevelopment ? bundlesPath : accessURL ? accessURL + bundlesPath : bundlesPath);
    let config = yield getLinkedConfig();
    let postcssPlugins = [];
    if (config.cssMode === 'tailwind') {
        // Tailwind v4 is configured via CSS directives (@config, @import, @theme)
        // See theme.css for the main configuration
        postcssPlugins.push(['@tailwindcss/postcss', {}]);
    }
    else {
        // postcss mode: use postcss-nested to enable nesting of css + CSS Modules
        postcssPlugins.push(['postcss-import', {}], [
            'postcss-preset-env',
            {
                features: { 'nesting-rules': true },
            },
        ]);
    }
    /**
     * Rewrite CSS asset URLs for the web bundle while keeping native (Capacitor) paths intact.
     *
     * Examples that will now work transparently in CSS:
     *   url('/images/foo.svg')         -> web: https://cdn/app/public/images/foo.svg
     *                                 -> native: /images/foo.svg (served from bundled webDir)
     *   url('./images/bar.png')       -> web: https://cdn/app/public/images/bar.png
     *   url('/public/images/baz.png') -> web: https://cdn/app/public/images/baz.png
     *
     * Data-URIs and absolute http(s) URLs pass through untouched.
     */
    postcssPlugins.push([
        postcssUrl({
            url: ({ url }) => {
                if (!url || url.startsWith('data:')) {
                    return url;
                }
                // remove wrapping quotes added by postcss
                let cleanedUrl = url.replace(/^['"]|['"]$/g, '').trim();
                // skip absolute http(s) references
                if (/^https?:\/\//i.test(cleanedUrl)) {
                    return cleanedUrl;
                }
                // remove leading ./ to normalise relative imports
                if (cleanedUrl.startsWith('./')) {
                    cleanedUrl = cleanedUrl.slice(1);
                }
                // ensure a single leading slash
                if (!cleanedUrl.startsWith('/')) {
                    cleanedUrl = `/${cleanedUrl}`;
                }
                // collapse optional /public prefix - CSS can freely use /images/... now
                if (cleanedUrl.startsWith('/public/')) {
                    cleanedUrl = cleanedUrl.replace('/public/', '/');
                }
                // For Capacitor builds we keep the relative path (assets are bundled locally)
                if (isCapacitorBuild) {
                    return cleanedUrl;
                }
                const baseUrl = (accessURL || '').replace(/\/$/, '');
                return `${baseUrl}${publicPath}${cleanedUrl}`;
            },
        }),
    ]);
    return {
        mode: isProduction ? 'production' : 'development',
        devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map',
        entry: [
            isDevelopment && 'webpack-hot-middleware/client',
            path.resolve(process.cwd(), process.env.ENTRY_PATH ||
                (process.env.SOURCE_PATH
                    ? process.env.SOURCE_PATH + '/index.tsx'
                    : './src/index.tsx')),
        ].filter(Boolean),
        watch: isDevelopment || ((_a = config.webpack) === null || _a === void 0 ? void 0 : _a.analyse),
        output: {
            path: path.resolve(process.cwd(), process.env.OUTPUT_PATH || './public/bundles'),
            filename: '[name].bundle.js',
            publicPath: ASSET_PATH,
            clean: true,
        },
        watchOptions: {
            //ignore everything except the src folder. ignore specific files in the src folder
            ignored: /(^((?!src).)*$|\.d\.ts$|\.js\.map$|\.css\.json$|public|\.idea|[/\\]\..*)/,
            aggregateTimeout: 500,
        },
        devServer: {
            client: {
                progress: false,
            },
        },
        plugins: [
            // new WatchRunPlugin(),
            new MiniCssExtractPlugin({
                ignoreOrder: true,
            }),
            new webpack.EnvironmentPlugin(Object.keys(process.env)),
            isDevelopment && new ReactRefreshWebpackPlugin(),
            isDevelopment && new webpack.HotModuleReplacementPlugin(),
            ((_b = config.webpack) === null || _b === void 0 ? void 0 : _b.analyse) && new BundleAnalyzerPlugin(),
            new WebpackManifestPlugin({
                fileName: 'manifest.json',
                publicPath: ASSET_PATH,
                writeToFileEmit: true,
                filter: (file) => /\.(js|css)$/i.test(file.name),
            }),
            ...(Array.isArray((_c = config.webpack) === null || _c === void 0 ? void 0 : _c.plugins) ? config.webpack.plugins : []),
        ].filter(Boolean),
        externals: ((_d = config.webpack) === null || _d === void 0 ? void 0 : _d.externals) || {},
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        {
                            loader: 'css-loader',
                            options: {
                                url: false,
                                importLoaders: 2,
                                modules: {
                                    mode: 'local',
                                    getLocalIdent: getLocalIdent,
                                    auto: (resourcePath) => {
                                        //make sure this only applies to .module.css files, and not to tailwind
                                        return (/\.module\.css$/i.test(resourcePath) &&
                                            !/tailwind/i.test(resourcePath));
                                    },
                                },
                            },
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                postcssOptions: {
                                    plugins: postcssPlugins,
                                },
                            },
                        },
                    ],
                },
                {
                    test: /\.tsx?$/,
                    use: [
                        {
                            loader: 'ts-loader?' +
                                JSON.stringify({
                                    compilerOptions: {
                                        //this is required for dynamic imports & code splitting
                                        module: 'esnext',
                                        moduleResolution: 'node',
                                        sourceMap: isDevelopment,
                                        plugins: [{ name: 'typescript-plugin-css-modules' }],
                                    },
                                }),
                            options: Object.assign(Object.assign({}, (isDevelopment
                                ? {
                                    getCustomTransformers: () => ({
                                        before: [ReactRefreshTypeScript()],
                                    }),
                                }
                                : {})), { transpileOnly: isProduction }),
                        },
                    ],
                    exclude: /node_modules/,
                },
            ],
        },
        optimization: {
            minimize: isProduction,
            minimizer: [
                new TerserPlugin({
                    terserOptions: {
                        keep_classnames: true,
                    },
                }),
            ],
            splitChunks: {
                cacheGroups: {
                    // Prevent CSS from being split into separate shared chunks
                    // Keep all CSS for a lazy-loaded route together in one file
                    // This prevents FOUC when lazy routes need multiple CSS chunks
                    defaultVendors: false,
                    default: false,
                },
            },
        },
        stats: {
            chunks: false,
            assets: true,
            entrypoints: false,
            children: true,
            modules: false,
        },
        resolve: {
            extensions: ['.tsx', '.ts', '.js', '.css', '.json'],
            alias: ((_e = config.webpack) === null || _e === void 0 ? void 0 : _e.alias) || {},
            extensionAlias: {
                '.js': ['.tsx', '.ts', '.js'],
                '.jsx': ['.tsx', '.jsx'],
            },
            // traceResolution: true
        },
        //Cache is now overwritten in LincdServer based on config, the other value for type would be 'filesystem'
        //see also https://webpack.js.org/configuration/other-options/#cache
        cache: ((_f = config.webpack) === null || _f === void 0 ? void 0 : _f.cache)
            ? { type: 'filesystem' }
            : { type: 'memory' },
    };
});
//# sourceMappingURL=config-webpack-app.js.map