// NOTE: this file needs to be written in EcmaScript 5
/*
What this plugin does
---------------------

This Babel plugin finds uses of the <LazyComponent> component and does two things:

- It adds a prop to the <LazyModule> JSX element. This parameter is for Mendel to be able to work with this, since
  it requires the use of require() with a string literal.
- It collects all the uses of <LazyModule> to create a configuration file for lazy loading

How is does it
--------------

For basic information about how a Babel plugin works, see the Babel plugin handbook:
https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md

This plugin looks at every JSX opening tag and checks whether the opening tag is pointing to the 'LazyComponent'
named export of the 'lazy_component' module. It does this by checking the named imports (import {Foo}) and that the
path to the imported module resolves to the location of the lazy_component module.

import {LazyComponent} from '../lazy_comonent'; <-- points to the right path
             ^
             |
          references
             |
     <LazyComponent import="../foo"/>

(Look for referencesImport() in this source code)
 */
var nodePath = require('path');
var fs = require('fs');
var mkdirp = require('mkdirp');
var resolve = require('browser-resolve');
var lazyComponentFolderPath = nodePath.resolve(__dirname);

module.exports = function(babel) {
    var t = babel.types;
    var moduleSet = {};

    // get a JSX attribute by name from a path object
    function getAttribute(path, attrName) {
        var i = 0;
        var attributes = path.get('attributes');

        while (i < attributes.length) {
            if (attributes[i].node.name.name === attrName) {
                return attributes[i];
            }

            i++;
        }

        throw new SyntaxError('Expected <LazyComponent> to have property ' + attrName);
    }

    // Programmatically generate the name of a bundle based on the name of the module being lazy loaded
    // Basically we're turning "src/molecules/settings/settings.jsx" into
    // "public/js/src_molecules_settings_settings_jsx.js"
    function toBundleName(filename, destFolder) {
        return nodePath.join(
            destFolder,
            filename.replace(new RegExp(nodePath.sep, 'g'), '_').replace(/\./g, '_') + '.js'
        );
    }

    function generateExtractifyConfig(obj, destFolder) {
        return Object.keys(obj).map(function(key) {
            //console.log(key);
            //console.log(resolve.sync('./'+key));
            return {
                entries: [resolve.sync('./'+key)],
                outfile: toBundleName(key, destFolder)
            };
        });
    }

    // create a JSX attribute that looks like this:
    // _moduleGetter={function() {return require('modulePath')}}
    function createModuleGetterAttr(modulePath) {
        // This would be much nicer to write using babel-template, but babel-template only seems to generate statements
        // and here we need either an attribute or a function expression
        /* eslint-disable new-cap */
        return t.JSXAttribute(t.JSXIdentifier('_moduleGetter'), t.JSXExpressionContainer(
            // name (undefined), params (empty array), body of the function
            t.functionExpression(undefined, [], t.blockStatement([
                t.returnStatement(t.callExpression(t.identifier('require'), [t.stringLiteral(modulePath)]))
            ]))
        ));
        /* eslint-enable new-cap */
    }

    return {
        visitor: {
            // import LazyComponent from '../atoms/lazy_component';
            // <LazyComponent import="../molecules/settings"/>
            JSXOpeningElement: function(path, state) {
                var name = path.get('name');
                var filename = state.file.opts.filename;

                var lazyComponentImportPath = nodePath.relative(nodePath.dirname(filename), lazyComponentFolderPath);
                var importValue;
                var normalizedEntryPoint;

                if (name.referencesImport(lazyComponentImportPath, 'LazyComponent')) {
                    importValue = getAttribute(path, 'import').get('value');

                    // We need this value to be a string literal to be able to reuse its value for Mendel
                    if (!importValue.isStringLiteral()) {
                        throw new SyntaxError(
                            'The value of the "import" property of <LazyComponent> must be a string literal'
                        );
                    }

                    // Deal with Mendel integration
                    // Mendel requires that a require('./lazy/component') call is made with the same
                    // value as the "import" attribute of the LazyComponent component. THe argument to require must
                    // be a string literal, so we need to generate it in the source code
                    path.node.attributes.push(createModuleGetterAttr(importValue.node.value));

                    // Store configuration for extractify
                    // The normalizedEntryPoint is the path to the entry point from the project root
                    // DANGER ZONE: process.cwd() can easily point to anywhere other than the project root depending
                    // on how our build scripts are called
                    normalizedEntryPoint = nodePath.relative(
                        process.cwd(),
                        nodePath.resolve(nodePath.dirname(filename), importValue.node.value)
                    );
                    // Treat this as a set to avoid duplicate entries for each lazy component
                    moduleSet[normalizedEntryPoint] = true;

                    // mark this file so that the Program.exit visitor knows that it should write the config to the
                    // file system (remember the Program visitor is called for every module in Norrin)
                    state.fileHadLazyComponent = true;
                }
            },

            Program: {
                exit: function(path, state) {
                    var destFile = state.opts.destFile;
                    var destBundleFolder = state.opts.destBundleFolder;

                    if (state.fileHadLazyComponent) {
                        mkdirp.sync(nodePath.dirname(destFile));

                        const extractifyConfig = generateExtractifyConfig(moduleSet, destBundleFolder);

                        fs.writeFileSync(destFile, JSON.stringify(extractifyConfig, null, 4), {
                            encoding: 'utf8'
                        });

                        console.log('devrim babel');

                        state.fileHadLazyComponent = false;
                    }
                }
            }
        }
    };
};
