const fs = require('fs');


const CONFIG_FILE_PATH = './';
const CONFIG_FILE_NAME = 'content-config.ts';
const DIST_PATH = '../../../dist/cd-plugin/code-examples/';

const IMPORT_REGEX = /import {(\w+)} from ["']\.\/([^"']+)["']/;
const ROUTE_REGEX = /routerLink: ["'](\w+)["'], componentClass: (\w+)/;
const FRAGMENTS_REGEX = / *(\/\/|<!--) region (\w+)( -->)? *\r?\n(.*)\r?\n *\1 endregion \2( -->)? */sg;
const IGNORE_REGEX = / *(\/\/|<!--) region (@ignore)(.*?)\r?\n\s*\1 endregion \2( -->)? */sg;

const CAPITAL_LATIN_REGEX = /([A-Z])/g;

const TRIM_LINE_BREAKS_REGEX = /^[\r\n]+(.*)[\r\n]+$/s;
const MULTIPLE_LINE_BREAKS_REGEX = /(\r?\n)(\1| )+\1/g;
const LINE_BREAK_REGEX = /\r?\n/;


prepare();


function prepare() {
    const file = fs.readFileSync(CONFIG_FILE_PATH + CONFIG_FILE_NAME, { encoding: 'utf-8' });
    // console.log(file);

    // 1. parse component file paths
    const componentPaths = getComponentPaths(file);

    // 2. parse routes
    const componentRoutes = getComponentRoutes(file);

    // 3. parse found files and write them to dist
    const counters = { success: 0, pathNotFound: 0, error: 0 };
    Object.keys(componentRoutes)
        // .filter(cmp => cmp === 'CdItemCardRowDemoComponent') // FIXME temporary play with single file
        .forEach(cmp => {
            const routerLink = componentRoutes[cmp];
            const path = componentPaths[cmp];
            if (path) {
                try {
                    writeExampleCode(routerLink, path);
                    counters.success++;
                } catch (e) {
                    console.error(e);
                    counters.error++;
                }

            } else {
                counters.pathNotFound++;
            }
        });

    console.log(`Code examples have been prepared:`, counters);
}


function getComponentPaths(file) {
    const res = {};
    file.split('\n').forEach(line => {
        const execRes = IMPORT_REGEX.exec(line);
        if (execRes) {
            res[execRes[1]] = execRes[2];
        }
    });
    return res;
}


function getComponentRoutes(file) {
    const res = {};
    file.split('\n').forEach(line => {
        const execRes = ROUTE_REGEX.exec(line);
        if (execRes) {
            res[execRes[2]] = execRes[1];
        }
    });
    return res;

}


function writeExampleCode(routerLink, path) {
    const fullPath = CONFIG_FILE_PATH + path + '.ts';
    const file = fs.readFileSync(fullPath, { encoding: 'utf-8' });

    const parsed = parseComponentFile(file);

    // console.log(JSON.stringify(parsed, null, 4));
    fs.mkdirSync(DIST_PATH, { recursive: true});
    fs.writeFileSync(DIST_PATH + routerLink + '.json', JSON.stringify(parsed));
}


function parseComponentFile(file) {
    // remove parts inside @ignore comments
    let fileRest = file.replace(IGNORE_REGEX, '');

    const snippets = [];

    fileRest = fileRest.replace(FRAGMENTS_REGEX, (...args) => {
        snippets.push({
            code: prettifyCode(args[4]),
            header: camelCaseToHuman(args[2]),
            fragment: args[2],
            langCode: args[1] === '<!--' ? 'markup' : 'ts'
        });

        return '';
    });

    // console.log('### fileRest:\n', fileRest);
    snippets.unshift({
        code: prettifyCode(fileRest),
        header: 'Demo component',
        fragment: 'demoComponent'
    });

    return snippets;
}


function camelCaseToHuman(str) {
    const formatted = str.replace(CAPITAL_LATIN_REGEX, (match, char) => (' ' + char.toLowerCase()))
    return formatted.charAt(0).toUpperCase() + formatted.slice(1);
}

function prettifyCode(text) {
    // 1. trim leading, tail and multiple line breaks
    text = trimLineBreaks(text);

    // 2. remove excess left margin
    text = shiftLeft(text)

    return text;
}

function trimLineBreaks(text) {
    return text
        .replace(TRIM_LINE_BREAKS_REGEX, '$1')
        .replace(MULTIPLE_LINE_BREAKS_REGEX, '\n\n');
}

function shiftLeft(text) {
    const lines = text.split(LINE_BREAK_REGEX);

    let minMargin = Number.MAX_SAFE_INTEGER;
    for (const line of lines) {
        if (!line) {
            continue; // skip empty lines
        }

        const margin = line.length - line.trimStart().length;
        if (margin < minMargin) {
            minMargin = margin;
        }

        if (minMargin === 0) {
            break; // there is no sense to check other lines
        }
    }

    return minMargin === 0 || minMargin === Number.MAX_SAFE_INTEGER ?
        text :
        lines.map(line => !line ? line : line.slice(minMargin)).join('\n');
}
