import assert from 'assert';
import * as _ from 'lodash-es';
import escapeStringRegexp from 'escape-string-regexp';
import { joinCallbacks } from './write-files.mjs';
const { kebabCase } = _;
export const convertToPrettierExpressions = (str) => {
    return str.replace(/\s+/g, '([\\s\n]*)').replace(/>+/g, '(\n?[\\s]*)>');
};
export const checkContentIn = (contentToCheck, content, ignoreWhitespaces = true) => {
    assert(content, 'content is required');
    assert(contentToCheck, 'contentToCheck is required');
    let re;
    if (typeof contentToCheck === 'string') {
        const pattern = ignoreWhitespaces
            ? convertToPrettierExpressions(escapeStringRegexp(contentToCheck))
            : contentToCheck
                .split('\n')
                .map(line => `\\s*${escapeStringRegexp(line)}`)
                .join('\n');
        re = new RegExp(pattern);
    }
    else {
        re = contentToCheck;
    }
    return re.test(content);
};
export const insertContentBeforeNeedle = ({ content, contentToAdd, needle, autoIndent = true }) => {
    assert(needle, 'needle is required');
    assert(content, 'content is required');
    assert(contentToAdd, 'contentToAdd is required');
    needle = needle.includes('jhipster-needle-') ? needle : `jhipster-needle-${needle}`;
    let regexp = new RegExp(`(?://|<!--|/*|#) ${needle}(?:$|\n| )`, 'g');
    let firstMatch = regexp.exec(content);
    if (!firstMatch) {
        regexp = new RegExp(`"${needle}": `, 'g');
        firstMatch = regexp.exec(content);
        if (!firstMatch) {
            return null;
        }
    }
    if (typeof contentToAdd !== 'function' && regexp.exec(content)) {
        throw new Error(`Multiple needles found for ${needle}`);
    }
    const needleIndex = firstMatch.index;
    const needleLineIndex = content.lastIndexOf('\n', needleIndex);
    const beforeContent = content.substring(0, needleLineIndex + 1);
    const afterContent = content.substring(needleLineIndex + 1);
    const needleLine = afterContent.split('\n', 2)[0];
    const needleIndent = needleLine.length - needleLine.trimStart().length;
    if (typeof contentToAdd === 'function') {
        return contentToAdd(content, { needleIndent, indentPrefix: ' '.repeat(needleIndent) });
    }
    contentToAdd = Array.isArray(contentToAdd) ? contentToAdd : [contentToAdd];
    if (autoIndent) {
        contentToAdd = contentToAdd.map(eachContentToAdd => eachContentToAdd.split('\n')).flat();
    }
    const firstContent = contentToAdd.find(line => line.trim());
    if (!firstContent) {
        return null;
    }
    const contentIndent = firstContent.length - firstContent.trimStart().length;
    if (needleIndent > contentIndent) {
        const identToApply = ' '.repeat(needleIndent - contentIndent);
        contentToAdd = contentToAdd.map(line => (line ? identToApply + line : line));
    }
    else if (needleIndent < contentIndent) {
        let identToRemove = contentIndent - needleIndent;
        contentToAdd
            .filter(line => line.trimStart())
            .forEach(line => {
            const trimmedLine = line.trimStart();
            const lineIndent = line.length - trimmedLine.length;
            if (lineIndent < identToRemove) {
                identToRemove = lineIndent;
            }
        });
        contentToAdd = contentToAdd.map(line => (line.length > identToRemove ? line.substring(identToRemove) : ''));
    }
    return `${beforeContent}${contentToAdd.join('\n')}\n${afterContent}`;
};
export const createNeedleCallback = ({ needle, contentToAdd, contentToCheck, optional = false, ignoreWhitespaces = true, autoIndent, }) => {
    assert(needle, 'needle is required');
    assert(contentToAdd, 'contentToAdd is required');
    return function (content, filePath) {
        if (contentToCheck && checkContentIn(contentToCheck, content, ignoreWhitespaces)) {
            return content;
        }
        if (typeof contentToAdd !== 'function') {
            if (typeof contentToAdd === 'string' && contentToAdd.startsWith('\n')) {
                contentToAdd = contentToAdd.slice(1);
            }
            contentToAdd = (Array.isArray(contentToAdd) ? contentToAdd : [contentToAdd]).filter(eachContent => !checkContentIn(eachContent, content, ignoreWhitespaces));
            if (contentToAdd.length === 0) {
                return content;
            }
        }
        const newContent = insertContentBeforeNeedle({
            needle,
            content,
            contentToAdd,
            autoIndent,
        });
        if (newContent) {
            return newContent;
        }
        const message = `Missing ${optional ? 'optional' : 'required'} jhipster-needle ${needle} not found at '${filePath}'`;
        if (optional) {
            this.log.warn(message);
            return content;
        }
        throw new Error(message);
    };
};
export function createBaseNeedle(options, needles) {
    const actualNeedles = needles === undefined ? options : needles;
    const actualOptions = needles === undefined ? undefined : options;
    assert(actualNeedles, 'needles is required');
    const { filePath, optional = false, ignoreWhitespaces = true } = actualOptions ?? {};
    let { needlesPrefix } = options;
    needlesPrefix = needlesPrefix ? `${needlesPrefix}-` : '';
    const callbacks = Object.entries(actualNeedles)
        .filter(([_key, contentToAdd]) => contentToAdd)
        .map(([key, contentToAdd]) => createNeedleCallback({ needle: `${needlesPrefix}${kebabCase(key)}`, contentToAdd, optional, ignoreWhitespaces }));
    assert(callbacks.length > 0, 'At least 1 needle is required');
    const callback = callbacks.length === 1 ? callbacks[0] : joinCallbacks(...callbacks);
    if (filePath) {
        assert(this, 'when passing filePath, the generator is required');
        return this.editFile(filePath, callback);
    }
    return callback;
}
