var VNode = require('../vnode/vnode.js');
var VText = require('../vnode/vtext.js');
var isVNode = require('../vnode/is-vnode');
var isVText = require('../vnode/is-vtext');
var isWidget = require('../vnode/is-widget');
var isHook = require('../vnode/is-vhook');
var isVThunk = require('../vnode/is-thunk');

var parseTag = require('./parse-tag.js');
var softSetHook = require('./hooks/soft-set-hook.js');
var evHook = require('./hooks/ev-hook.js');

module.exports = h;

function h(tagName, properties, children) {
  var childNodes = [];
  var tag, props, key, namespace;

  if (!children && isChildren(properties)) {
    children = properties;
    props = {};
  }

  props = props || properties || {};
  tag = parseTag(tagName, props);

  // support keys
  if (props.hasOwnProperty('key')) {
    key = props.key;
    props.key = undefined;
  }

  // support namespace
  if (props.hasOwnProperty('namespace')) {
    namespace = props.namespace;
    props.namespace = undefined;
  }

  // fix cursor bug
  if (tag === 'INPUT' &&
    !namespace &&
    props.hasOwnProperty('value') &&
    props.value !== undefined &&
    !isHook(props.value)
  ) {
    props.value = softSetHook(props.value);
  }

  transformProperties(props);

  if (children !== undefined && children !== null) {
    addChild(children, childNodes, tag, props);
  }


  return new VNode(tag, props, childNodes, key, namespace);
}

    childNodes.push(c);
  }
  else if (Array.isArray(c)) {
    for (var i = 0; i < c.length; i++) {
      addChild(c[i], childNodes, tag, props);
    }
  }
  else if (c === null || c === undefined) {
    return;
  }
  else {
    throw UnexpectedVirtualElement({
      foreignObject: c,
      parentVnode: {
        tagName: tag,
        properties: props
      }
    });
  }
}

function transformProperties(props) {
  for (var propName in props) {
    if (props.hasOwnProperty(propName)) {
      var value = props[propName];

      if (isHook(value)) {
        continue;
      }

      if (propName.substr(0, 3) === 'ev-') {
        // add ev-foo support
        props[propName] = evHook(value);
      }
    }
  }
}

function isChild(x) {
  return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x);
}

function isChildren(x) {
  return typeof x === 'string' || Array.isArray(x) || isChild(x);
}

function UnexpectedVirtualElement(data) {
  var err = new Error();

  err.type = 'virtual-hyperscript.unexpected.virtual-element';
  err.message = `Unexpected virtual child passed to h().
Expected a VNode / Vthunk / VWidget / string but:
got:
${errorString(data.foreignObject)}.
The parent vnode is:
${errorString(data.parentVnode)}
Suggested fix: change your h(..., [ ... ]) callsite.`;
  err.foreignObject = data.foreignObject;
  err.parentVnode = data.parentVnode;

  return err;
}

function errorString(obj) {
  try {
    return JSON.stringify(obj, null, '    ');
  }
  catch (e) {
    return String(obj);
  }
}
function addChild(c, childNodes, tag, props) {
  if (typeof c === 'string') {
    childNodes.push(new VText(c));
  }
  else if (isChild(c)) {