"use strict";
/**
 * The following code is modified based on
 * https://github.com/seek-oss/css-modules-typescript-loader
 *
 * only generator .d.ts for css modules file
 *
 * MIT Licensed
 * Author mattcompiles
 * Copyright 2018 SEEK
 * https://github.com/seek-oss/css-modules-typescript-loader/blob/master/LICENSE
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
// @ts-expect-error
const line_diff_1 = __importDefault(require("line-diff"));
const css_1 = require("../css");
const bannerMessage = '// This file is automatically generated.\n// Please do not change this file!';
const cssModuleExport = 'export const cssExports: CssExports;\nexport default cssExports;\n';
const getNoDeclarationFileError = ({ filename }) => new Error(`Generated type declaration does not exist. Run webpack and commit the type declaration for '${filename}'`);
const getTypeMismatchError = ({ filename, expected, actual, }) => {
    const diff = new line_diff_1.default(enforceLFLineSeparators(actual), expected).toString();
    return new Error(`Generated type declaration file is outdated. Run webpack and commit the updated type declaration for '${filename}'\n\n${diff}`);
};
const cssModuleToInterface = (cssModuleKeys) => {
    const interfaceFields = cssModuleKeys
        .sort()
        .map(key => `  '${key}': string;`)
        .join('\n');
    return `interface CssExports {\n${interfaceFields}\n}`;
};
const filenameToTypingsFilename = (filename) => {
    const dirName = path_1.default.dirname(filename);
    const baseName = path_1.default.basename(filename);
    return path_1.default.join(dirName, `${baseName}.d.ts`);
};
const enforceLFLineSeparators = (text) => {
    if (text) {
        // replace all CRLFs (Windows) by LFs (Unix)
        return text.replace(/\r\n/g, '\n');
    }
    else {
        return text;
    }
};
const compareText = (contentA, contentB) => {
    return (enforceLFLineSeparators(contentA) === enforceLFLineSeparators(contentB));
};
const validModes = ['emit', 'verify'];
const isFileNotFound = (err) => err && err.code === 'ENOENT';
const makeDoneHandlers = (callback, content, rest) => ({
    failed: (e) => callback(e),
    success: () => callback(null, content, ...rest),
});
const makeFileHandlers = (filename) => ({
    read: (handler) => fs_1.default.readFile(filename, { encoding: 'utf-8' }, handler),
    write: (content, handler) => fs_1.default.writeFile(filename, content, { encoding: 'utf-8' }, handler),
});
const extractLocalExports = (content) => {
    let localExports = content.split('exports.locals')[1];
    if (!localExports) {
        localExports = content.split('___CSS_LOADER_EXPORT___.locals')[1];
    }
    return localExports;
};
// eslint-disable-next-line consistent-return
function default_1(content, ...rest) {
    const { failed, success } = makeDoneHandlers(this.async(), content, rest);
    const filename = this.resourcePath;
    const { mode = 'emit', modules = true } = this.getOptions() || {};
    if (!validModes.includes(mode)) {
        return failed(new Error(`Invalid mode option: ${mode}`));
    }
    // filter non css-modules file
    if (!(0, css_1.isCssModules)(filename, modules)) {
        return success();
    }
    const cssModuleInterfaceFilename = filenameToTypingsFilename(filename);
    const { read, write } = makeFileHandlers(cssModuleInterfaceFilename);
    const keyRegex = /"([^\\"]+)":/g;
    let match;
    const cssModuleKeys = [];
    const localExports = extractLocalExports(content);
    // eslint-disable-next-line no-cond-assign
    while ((match = keyRegex.exec(localExports))) {
        if (cssModuleKeys.indexOf(match[1]) < 0) {
            cssModuleKeys.push(match[1]);
        }
    }
    const cssModuleDefinition = `${bannerMessage}\n${cssModuleToInterface(cssModuleKeys)}\n${cssModuleExport}`;
    if (mode === 'verify') {
        read((err, fileContents) => {
            if (isFileNotFound(err)) {
                return failed(getNoDeclarationFileError({
                    filename: cssModuleInterfaceFilename,
                }));
            }
            if (err) {
                return failed(err);
            }
            if (!compareText(cssModuleDefinition, fileContents)) {
                return failed(getTypeMismatchError({
                    filename: cssModuleInterfaceFilename,
                    expected: cssModuleDefinition,
                    actual: fileContents,
                }));
            }
            return success();
        });
    }
    else {
        read((_, fileContents) => {
            if (!compareText(cssModuleDefinition, fileContents)) {
                write(cssModuleDefinition, err => {
                    if (err) {
                        failed(err);
                    }
                    else {
                        success();
                    }
                });
            }
            else {
                success();
            }
        });
    }
}
exports.default = default_1;
