const { join, dirname, extname } = require('path');
const { readFile } = require('fs');
const YAML  = require('js-yaml');
const utils = require('loader-utils');

const defaultOptions = {
    importRoot: false,
    importNested: true,
    importKeyword: 'import',
    output: 'object'
};

const read = (file, ext = true) => {
    if (ext && !extname(file)) {
        let error;
        return read(file, false).catch(err => error = err)
            .then(_ => read(file + '.yml'))
            .catch(_ => read(file + '.yaml'))
            .catch(_ => read(file + '.json'))
            .catch(_ => Promise.reject(error));
    }

    return new Promise((resolve, reject) => {
        readFile(file, (err, data) => {
            if (err)
                reject(err);
            else
                resolve({ file, data: data.toString() });
        });
    });
};

const parseRootImports = (source, path, options) => {
    let oldLines = source.split('\n');
    let newLines = [];
    let deps = {};
    let promises = [];

    for (let i = 0; i < oldLines.length; i++) {
        const line  = oldLines[i];
        const regex = new RegExp(`^!${options.importKeyword}\\s+['"]?(.*)(\\.ya?ml|\\.json)?['"]?\\s*$`);
        const match = line.match(regex);
        
        if (match) {
            promises.push(
                read(join(path, match[1]))
                    .then(({ file, data }) => {
                        deps[file] = true;
                        if (file.endsWith('.json')) {
                            return {
                                lines: YAML.safeDump(JSON.parse(data)).split('\n'),
                                deps: {}
                            };
                        }
                        return parseRootImports(data, dirname(file), options);
                    })
                    .then(result => {
                        Object.assign(deps, result.deps);
                        newLines.push(...result.lines);
                    })
            );
        }
        else
            newLines.push(line);
    }

    return Promise.all(promises).then(_ => ({ lines: newLines, deps }));
};

const resolvePromises = (value) => {
    if (value instanceof Promise)
        return value;

    if (value instanceof Array)
        return Promise.all(value.map(entry => resolvePromises(entry)));
    
    if (typeof value === 'object') {
        const keys = Object.keys(value);

        return Promise.all(keys.map(key => resolvePromises(value[key])))
            .then(properties => {
                let obj = {};
                keys.forEach((key, i) => {
                    obj[key] = properties[i];
                });
                return obj;
            });

    }

    return Promise.resolve(value);
};

const parseImports = (source, path, options) => {
    let root = options.importRoot
        ? parseRootImports(source, path, options).then(({ lines, deps }) => ({ source: lines.join('\n'), deps }))
        : Promise.resolve({ source, deps: {} });

    return root.then(({ source, deps }) => {
        let types = [];
        let containsPromises = false

        if (options.importNested) {
            types.push(new YAML.Type('!' + options.importKeyword, {
                kind: 'scalar',
                construct: uri => {
                    containsPromises = true;

                    return read(join(path, uri))
                        .then(({ file, data }) => {
                            deps[file] = true;
                            return parseImports(data, dirname(file), options);
                        })
                        .then(result => {
                            Object.assign(deps, result.deps);
                            return result.obj;
                        });
                }
            }));
        }

        const schema = YAML.Schema.create(types);
        const parsed = YAML.safeLoad(source, { schema });

        // Since the construct function in our import type is async we
        // could have nested promises, these have to be resolved.
        return containsPromises
            ? resolvePromises(parsed).then(obj => ({ obj, deps }))
            : Promise.resolve({ obj: parsed, deps })
    });
};

function load(source) {
    this.cacheable && this.cacheable();
    const callback = this.async();

    let options = Object.assign({}, defaultOptions, utils.getOptions(this));
    parseImports(source, dirname(this.resourcePath), options)
        .then(({ obj, deps }) => {
            for (let dep of Object.keys(deps))
                this.addDependency(dep);

            if (options.output === 'json')
                callback(null, JSON.stringify(obj));
            else if (options.output === 'yaml')
                callback(null, YAML.safeDump(obj));
            else if (options.output === 'raw')
                callback(null, obj)
            else
                callback(null, `module.exports = ${JSON.stringify(obj)};`);
        })
        .catch(err => {
            this.emitError(err);
            callback(err, null);
        });
};

module.exports = load;

@@ -1290,38 +1290,21 @@
     
+let 
 promise
-s.push(%0A              
  
+=
  rea
@@ -1327,20 +1327,16 @@
 ch%5B1%5D))%0A
-    
         
@@ -1383,36 +1383,32 @@
                 
-    
 deps%5Bfile%5D = tru
@@ -1402,32 +1402,33 @@
 s%5Bfile%5D = true;%0A
+%0A
                 
@@ -1431,24 +1431,23 @@
         
-    if (
+return 
 file.end
@@ -1460,19 +1460,16 @@
 '.json')
-) %7B
 %0A       
@@ -1489,176 +1489,45 @@
     
-    return %7B%0A                                lines: YAML.safeDump(JSON.parse(data)).split('%5Cn'),%0A                                deps: %7B%7D%0A                           
+? %7B obj: JSON.parse(data), deps: %7B%7D
  %7D
-;
 %0A   
@@ -1551,50 +1551,15 @@
     
-%7D%0A                        return
+:
  parse
-Root
 Impo
@@ -1601,36 +1601,32 @@
                 
-    
 %7D)%0A             
@@ -1620,36 +1620,32 @@
                 
- 
-   
 .then(result =%3E 
@@ -1638,36 +1638,32 @@
 hen(result =%3E %7B%0A
-    
                 
@@ -1712,36 +1712,32 @@
                 
-   
- 
 newLines.push(..
@@ -1741,20 +1741,45 @@
 (...
-result.lines
+YAML.safeDump(result.obj).split('%5Cn')
 );%0A 
@@ -1785,38 +1785,36 @@
                 
- 
-   
 %7D)
+;%0A
 %0A            );%0A
@@ -1810,28 +1810,61 @@
         
-);%0A        %7D
+promises.push(promise);%0A            continue;
 %0A       
@@ -1856,36 +1856,33 @@
 ntinue;%0A        
-else
+%7D
 %0A            new
@@ -1866,32 +1866,37 @@
       %7D%0A        
+%0A    
     newLines.pus

fix parsing error