# src/main.js

    // import React, { createElement, } from 'react';
    import React from 'react';
    import ReactDOM from 'react-dom';
    import ReactDOMServer from 'react-dom/server';
    import useGlobalHook from 'use-global-hook';

    import * as jsonxComponents from './components';
    import * as jsonxProps from './props';
    import * as jsonxChildren from './children';
    import * as jsonxUtils from './utils';
    const createElement = React.createElement;
    const { componentMap, getComponentFromMap, getBoundedComponents, DynamicComponent, } = jsonxComponents;
    const { getComputedProps, } = jsonxProps;
    const { getJSONXChildren, } = jsonxChildren;
    const { displayComponent, } = jsonxUtils;
    export let renderIndex = 0;

    /**
     * Use JSONX without any configuration to render JSONX JSON to HTML and insert JSONX into querySelector using ReactDOM.render
     * @example
     * // Uses react to create <!DOCTYPE html><body><div id="myApp"><div class="jsonx-generated"><p style="color:red;">hello world</p></div></div></body>
     * jsonx.jsonxRender({ jsonx: { component: 'div', props:{className:'jsonx-generated',children:[{ component:'p',props:{style:{color:'red'}}, children:'hello world' }]}}, querySelector:'#myApp', });
     * @param {object} config - options used to inject html via ReactDOM.render
     * @param {object} config.jsonx - any valid JSONX JSON object
     * @param {object} config.resources - any additional resource used for asynchronous properties
     * @param {string} config.querySelector - selector for document.querySelector
     * @property {object} this - options for getReactElementFromJSONX
     */
    export function jsonxRender(config = {}) {
      const { jsonx, resources, querySelector, options, DOM, portal, } = config;
      const Render = portal ? ReactDOM.createPortal : ReactDOM.render;
      Render(
        getReactElementFromJSONX.call(this || {}, jsonx, resources, options),
        DOM || document.querySelector(querySelector)
      );
    }

    /**
     * Use ReactDOMServer.renderToString to render html from JSONX
     * @example
     * // Uses react to create <div class="jsonx-generated"><p style="color:red;">hello world</p></div>
     * jsonx.outputHTML({ jsonx: { component: 'div', props:{className:'jsonx-generated',children:[{ component:'p',props:{style:{color:'red'}}, children:'hello world' }]}}, });
     * @param {object} config - options used to inject html via ReactDOM.render
     * @param {object} config.jsonx - any valid JSONX JSON object
     * @param {object} config.resources - any additional resource used for asynchronous properties
     * @property {object} this - options for getReactElementFromJSONX
     * @returns {string} React genereated html via JSONX JSON
     */
    export function outputHTML(config = {}) {
      const { jsonx, resources, } = config;

      return (this && this.useJSON)
        ? ReactDOMServer.renderToString(getReactElementFromJSON.call(this || {}, jsonx, resources))
        : ReactDOMServer.renderToString(getReactElementFromJSONX.call(this || {}, jsonx, resources));
    }

    /**
     * Use React.createElement and JSONX JSON to create React elements
     * @example
     * // Uses react to create the equivalent JSX <myComponent style={{color:blue}}>hello world</myComponent>
     * jsonx.getReactElementFromJSONX({component:'myCompnent',props:{style:{color:'blue'}},children:'hello world'})
     * @param {object} jsonx - any valid JSONX JSON object
     * @param {object} resources - any additional resource used for asynchronous properties
     * @property {object} this - options for getReactElementFromJSONX
     * @property {Object} [this.componentLibraries] - react components to render with JSONX
     * @property {boolean} [this.debug=false] - use debug messages
     * @property {boolean} [this.returnJSON=false] - return json object of {type,props,children} instead of react element
     * @property {boolean} [this.disableRenderIndexKey=false] - disables auto assign a key prop
     * @property {function} [this.logError=console.error] - error logging function
     * @property {string[]} [this.boundedComponents=[]] - list of components that require a bound this context (usefult for redux router)
     * @returns {function} React element via React.createElement
     */
    export function getReactElementFromJSONX(jsonx = {}, resources = {}) {
      // eslint-disable-next-line
      const { componentLibraries = {}, debug = false, returnJSON=false, logError = console.error, boundedComponents = [], disableRenderIndexKey = true, } = this || {};
      // const componentLibraries = this.componentLibraries;
      if (!jsonx) return null;
      if (jsonx.type) jsonx.component = jsonx.type;
      if (jsonxUtils.validSimpleJSONXSyntax(jsonx)) jsonx = jsonxUtils.simpleJSONXSyntax(jsonx);
      if (!jsonx.component) return createElement('span', {}, debug ? 'Error: Missing Component Object' : '');
      try {
        const components = Object.assign({ DynamicComponent: DynamicComponent.bind(this), }, componentMap, this.reactComponents);

        const reactComponents = (boundedComponents.length)
          ? getBoundedComponents.call(this, { boundedComponents, reactComponents: components, })
          : components;
        renderIndex++;
        const element = getComponentFromMap({ jsonx, reactComponents, componentLibraries, debug, logError, });
        const props = getComputedProps.call(this, { jsonx, resources, renderIndex, componentLibraries, debug, logError, disableRenderIndexKey, });
        const displayElement = (jsonx.comparisonprops)
          ? displayComponent.call(this, { jsonx, props, renderIndex, componentLibraries, debug, })
          : true;
        if (displayElement) {
          const children = getJSONXChildren.call(this, { jsonx, props, resources, renderIndex, });
          if (returnJSON) return { type:element, props, children, };
          return createElement(element, props, children);
        } else {
          return null;
        }
      } catch (e) {
        if (debug) {
          logError({ jsonx, resources, }, 'this', this);
          logError(e, (e.stack) ? e.stack : 'no stack');
        }
        throw e;
      }
    }

    export const getRenderedJSON = getReactElementFromJSONX;
    export const getReactElement = getReactElementFromJSONX;

    /** converts a json object {type,props,children} into a react element
     * @example
     * jsonx.getReactElementFromJSON({type:'div',props:{title:'some title attribute'},children:'inner html text'})
     * @param {Object|String} options.type - 'div' or react component
     * @param {Object} options.props - props for react element
     * @param {String|[Object]} options.children - children elements
     * @returns {function} React element via React.createElement
    */
    export function getReactElementFromJSON({ type, props, children, }) {
      return createElement(type, props, Array.isArray(children)
        ? children.map(getReactElementFromJSON)
        : children);
    }

    /** converts a jsonx json object into a react function component
     * @example
     * jsonx.compile({jsonx:{component:'div',props:{title:'some title attribute'},children:'inner html text'}}) //=>React Function Component
     * @param {Object} jsonx - valid JSONX JSON
     * @param {Object} resources - props for react element
     * @returns {function} React element via React.createElement
    */
    export function compile(jsonx, resources) {
      const context = Object.assign({}, this, { returnJSON: true, });
      const json = getReactElementFromJSONX.call(context, jsonx, resources);
      const func = function compiledJSONX(props) {
        json.props = Object.assign({}, json.props, props);
        return getReactElementFromJSON(json);
      };
      Object.defineProperty(func, 'name', { value: this.name, });
      return func;
    }

    /**
     * converts JSONX JSON IR to JSX
     * @example
     * jsonx.jsonToJSX({ type: 'div', props: { key: 5, title: 'test' }, children: 'hello' }) // => '<div key={5} title="test">hello</div>'
     * @param {Object} json - {type,props,children}
     * @returns {String} jsx string
     */
    export function outputJSX(jsonx, resources) {
      const context = Object.assign({}, this, { returnJSON: true, });
      const json = getReactElementFromJSONX.call(context, jsonx, resources);
      return jsonToJSX(json);
    }

    /**
     * Compiles JSONX into JSON IR format for react create element
     * @example
     * jsonx.outputJSON({ component: 'div', props: { title: 'test', }, children: 'hello', }); //=> { type: 'div',
     props: { key: 5, title: 'test' },
     children: 'hello' }
     * @property {object} this - options for getReactElementFromJSONX
     * @param {object} jsonx - any valid JSONX JSON object
     * @param {object} resources - any additional resource used for asynchronous properties
     * @returns {Object} json - {type,props,children}
     */
    export function outputJSON(jsonx, resources) {
      const context = Object.assign({}, this, { returnJSON: true, });
      return getReactElementFromJSONX.call(context, jsonx, resources);
    }
    export const jsonxHTMLString = outputHTML;

    /**
     * converts JSONX JSON IR to JSX
     * @example
     * jsonx.jsonToJSX({ type: 'div', props: { key: 5, title: 'test' }, children: 'hello' }) // => '<div key={5} title="test">hello</div>'
     * @param {Object} json - {type,props,children}
     * @returns {String} jsx string
     */
    export function jsonToJSX(json) {
      const propsString = json.props
        ? Object.keys(json.props)
          .filter(prop => prop.includes('__eval_') === false)
          .reduce((propString, prop) => {
            propString += ` ${prop.toString()}=${
              typeof json.props[ prop ] === 'string'
                ? `"${json.props[ prop ].toString()}"`
                : `{${(json.props[ `__eval_${prop}` ]||json.props[ prop ]).toString()}}`
            }`;
            return propString;
          }, '')
        : '';
      return Array.isArray(json.children)
        ? `<${json.type} ${propsString}>
      ${json.children.map(jsonToJSX)}
    </${json.type}>`
        : `<${json.type}${propsString}>${json.children}</${json.type}>`;
    }
    /**
     * Exposes react module used in JSONX
     * @returns {Object} React
     */
    export function __getReact() {
      return React;
    }

    /**
     * Exposes react dom module used in JSONX
     * @returns {Object} ReactDOM
     */
    export function __getReactDOM() {
      return ReactDOM;
    }

    /**
     * Exposes global hook used in JSONX
     * @returns {Object} useGlobalHook
     */
    export function __getUseGlobalHook() {
      return useGlobalHook;
    }

    export const _jsonxChildren = jsonxChildren;
    export const _jsonxComponents = jsonxComponents;
    export const _jsonxProps = jsonxProps;
    export const _jsonxUtils = jsonxUtils;
    export { __express, __express as renderFile, } from './express';

    export default getReactElementFromJSONX;