System.register(['babel-plugin-syntax-jsx', 'src/client/preferences.js'], function (_export, _context) {
  "use strict";

  var jsx, Preferences;


  function detectUnsupportedNodes(path, filename) {
    function gainPrintableFullPath(path) {
      let fullPath = [];

      while (path) {
        fullPath.unshift(path.node.type);
        path = path.parentPath;
      }

      return fullPath.map((nodeType, index) => '  '.repeat(index) + nodeType).join('\n');
    }

    path.traverse({
      /**
       * No support for JSXMemberExpression yet. #TODO: what are the semantics outside of react for this?
       * 
       * <foo.bar></foo.bar>
       */
      JSXMemberExpression(path, state) {
        throw new SyntaxError(`JSXMemberExpression not yet supported.
${gainPrintableFullPath(path)}`, filename, path.node.loc.start.line);
      },
      /**
       * No support for JSXEmptyExpression yet. #TODO: where is this feature even useful?
       * 
       * <div id={}></div>
       * or
       * <div>{}</div>
       */
      JSXEmptyExpression(path, state) {
        throw new SyntaxError(`JSXEmptyExpression not yet supported.
${gainPrintableFullPath(path)}`, filename);
      },
      /**
       * No support for jSXNamespacedName yet.
       * 
       * <div ns:attr="val" />;
       */
      JSXNamespacedName(path, state) {
        throw new SyntaxError(`jSXNamespacedName not yet supported.
${gainPrintableFullPath(path)}`, filename, path.node.loc.start.line);
      }
    });
  }

  /**
   * Resources for JSX Syntax
   * JSX babel Preset: https://github.com/babel/babel/blob/master/packages/babel-preset-react/src/index.js
   * JSX spec draft: https://github.com/facebook/jsx
   * JSX Syntax definition in babel: https://github.com/babel/babel/blob/master/packages/babel-types/src/definitions/jsx.js#L10
   * Babel nodes list: https://babeljs.io/docs/core-packages/babel-types/#apij-sxidentifier
   */

  _export('default', function ({ types: t, template, traverse }) {
    const GENERATED_IMPORT_IDENTIFIER = Symbol("generated import identifier");

    // #TODO: duplicate with aexpr transform -> extract it
    function addCustomTemplate(file, name) {
      let declar = file.declarations[name];
      if (declar) return declar;

      let identifier = file.declarations[name] = file.addImport("reactive-jsx", name, name);
      identifier[GENERATED_IMPORT_IDENTIFIER] = true;
      return identifier;
    }

    return {
      inherits: jsx,
      visitor: {
        Program(path, state) {
          detectUnsupportedNodes(path, state && state.opts && state.opts.filename);

          const fileName = state && state.file && state.file.log && state.file.log.filename || 'no_file_given';
          const sourceLocation = template(`({
        file: '${fileName}',
        end: {
          column: END_COLUMN,
          line: END_LINE
        },
        start: {
          column: START_COLUMN,
          line: START_LINE
        }
      })`);
          function buildSourceLocation(node) {
            return sourceLocation({
              END_COLUMN: t.numericLiteral(node.loc.end.column),
              END_LINE: t.numericLiteral(node.loc.end.line),
              START_COLUMN: t.numericLiteral(node.loc.start.column),
              START_LINE: t.numericLiteral(node.loc.start.line)
            }).expression;
          }

          function transformPath(path, programState) {
            function jSXAttributeToBuilder(path) {

              function getCallExpressionFor(functionName, ...additionalParameters) {
                return t.callExpression(addCustomTemplate(programState.file, functionName), // builder function
                [t.stringLiteral(path.get("name").node.name), // key
                ...additionalParameters]);
              }

              let attributeValue = path.get("value");
              if (path.isJSXSpreadAttribute()) {
                return t.callExpression(addCustomTemplate(programState.file, "attributeSpread"), [path.get("argument").node]);
              } else if (!path.node.value) {
                return getCallExpressionFor("attributeEmpty");
              } else if (attributeValue.isStringLiteral()) {
                return getCallExpressionFor("attributeStringLiteral", attributeValue.node);
              } else if (attributeValue.isJSXExpressionContainer()) {
                return getCallExpressionFor("attributeExpression", attributeValue.node.expression);
              } else if (attributeValue.isJSXElement()) {
                // #TODO: what would that even mean?
                throw new SyntaxError(`JSXElement as property value of JSXAttribute not yet supported.`);
              }

              throw new Error('unknown node type in JSXAttribute value ' + attributeValue.node.type);
            }

            function jSXChildrenToBuilder(child) {
              function getCallExpressionFor(functionName, childSpec) {
                return t.callExpression(addCustomTemplate(programState.file, functionName), // builder function
                [childSpec]);
              }

              if (child.isJSXText()) {
                return getCallExpressionFor("childText", t.stringLiteral(child.node.value));
              }
              if (child.isJSXElement()) {
                return getCallExpressionFor("childElement", child.node);
              }
              if (child.isJSXExpressionContainer()) {
                return getCallExpressionFor("childExpression", child.get("expression").node);
              }
              if (child.isJSXSpreadChild()) {
                return getCallExpressionFor("childSpread", child.get("expression").node);
                //throw new SyntaxError(`JSXSpreadChild as child of JSXElement not yet supported.`);
              }
              throw new Error('unknown node type in children of JSXElement ' + child.node.type);
            }

            path.traverse({
              JSXElement(path, state) {
                const jSXAttributes = path.get("openingElement").get("attributes");
                const jSXChildren = path.get("children");

                let newNode = t.callExpression(addCustomTemplate(programState.file, "element"), [t.stringLiteral(path.get("openingElement").get("name").node.name), t.callExpression(addCustomTemplate(programState.file, "attributes"), jSXAttributes.map(jSXAttributeToBuilder)), t.callExpression(addCustomTemplate(programState.file, "children"), jSXChildren.map(jSXChildrenToBuilder)), buildSourceLocation(path.node)]);

                path.replaceWith(newNode);
              },
              CallExpression(path) {
                if (path.node.__wrappedInMetaInformation__) {
                  return;
                }
                path.node.__wrappedInMetaInformation__ = true;

                function isDomCreatingCall(path) {
                  const callee = path.get('callee');
                  if (!callee.isMemberExpression() || callee.node.computed) {
                    return false;
                  }

                  const object = callee.get('object');
                  const property = callee.get('property');

                  if (!object.isIdentifier()) {
                    return false;
                  }
                  if (!property.isIdentifier()) {
                    return false;
                  }

                  const livelyCreate = object.node.name === 'lively' && property.node.name === 'create';
                  const documentCreateElement = object.node.name === 'document' && property.node.name === 'createElement';
                  return livelyCreate || documentCreateElement;
                }

                if (isDomCreatingCall(path)) {
                  path.replaceWith(t.callExpression(addCustomTemplate(programState.file, "addSourceLocation"), [path.node, buildSourceLocation(path.node)]));
                }
              }
            });

            return path;
          }

          transformPath(path, state);
        }
      }
    };
  });

  return {
    setters: [function (_babelPluginSyntaxJsx) {
      jsx = _babelPluginSyntaxJsx.default;
    }, function (_srcClientPreferencesJs) {
      Preferences = _srcClientPreferencesJs.default;
    }],
    execute: function () {}
  };
});