text: import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
  import XML from "@kyleshockey/xml"
  import memoizee from "memoizee"
  import isEmpty from "lodash/isEmpty"
  
  const primitives = {
    "string": () => "string",
    "string_email": () => "user@example.com",
    "string_date-time": () => new Date().toISOString(),
    "string_date": () => new Date().toISOString().substring(0, 10),
    "string_uuid": () => "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "string_hostname": () => "example.com",
    "string_ipv4": () => "198.51.100.42",
    "string_ipv6": () => "2001:0db8:5b96:0000:0000:426f:8e17:642a",
    "number": () => 0,
    "number_float": () => 0.0,
    "integer": () => 0,
    "boolean": (schema) => typeof schema.default === "boolean" ? schema.default : true
  }
  
  const primitive = (schema) => {
    schema = objectify(schema)
    let { type, format } = schema
  
    let fn = primitives[`${type}_${format}`] || primitives[type]
  
    if(isFunc(fn))
      return fn(schema)
  
    return "Unknown Type: " + schema.type
  }
  
  // do a couple of quick sanity tests to ensure the value
  // looks like a $$ref that swagger-client generates.
  const sanitizeRef = (value) => deeplyStripKey(value, "$$ref", (val) =>
    typeof val === "string" && val.indexOf("#") > -1)
  
  const liftSampleHelper = (oldSchema, target) => {
    if(target.example === undefined && oldSchema.example !== undefined) {
      target.example = oldSchema.example
    }
    if(target.default === undefined && oldSchema.default !== undefined) {
      target.default = oldSchema.default
    }
    if(target.enum === undefined && oldSchema.enum !== undefined) {
      target.enum = oldSchema.enum
    }
    if(target.xml === undefined && oldSchema.xml !== undefined) {
      target.xml = oldSchema.xml
    }
    return target
  }
  
  export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = undefined, respectXML = false) => {
    const _attr = {}
    let { xml, type, example, properties, additionalProperties, items } = objectify(schema)
    let { includeReadOnly, includeWriteOnly } = config
    xml = xml || {}
    let { name, prefix, namespace } = xml
    let displayName
    let res = {}
  
    // set xml naming and attributes
    if(respectXML) {
      name = name || "notagname"
      // add prefix to name if exists
      displayName = (prefix ? prefix + ":" : "") + name
      if ( namespace ) {
        //add prefix to namespace if exists
        let namespacePrefix = prefix ? ( "xmlns:" + prefix ) : "xmlns"
        _attr[namespacePrefix] = namespace
      }
    }
  
    // init xml default response sample obj
    if(respectXML) {
      res[displayName] = []
    }
  
    const usePlainValue = exampleOverride !== undefined || example !== undefined || schema && schema.default !== undefined
  
    const hasOneOf = !usePlainValue && schema && schema.oneOf && schema.oneOf.length > 0
    const hasAnyOf = !usePlainValue && schema && schema.anyOf && schema.anyOf.length > 0
    if(!usePlainValue && (hasOneOf || hasAnyOf)) {
      const someSchema = hasOneOf
        ? schema.oneOf[0]
        : schema.anyOf[0]
      liftSampleHelper(schema, someSchema)
      return sampleFromSchemaGeneric(someSchema, config, undefined, respectXML)
    }
  
    // try recover missing type
    if(schema && !type) {
      if(properties || additionalProperties) {
        type = "object"
      } else if(items) {
        type = "array"
      } else if(!usePlainValue){
        return
      }
    }
  
    // add to result helper init for xml or json
    const props = objectify(properties)
    let addPropertyToResult
    if(respectXML) {
      addPropertyToResult = (propName, overrideE = undefined) => {
        if(schema) {
          // case it is an xml attribute
          props[propName].xml = props[propName].xml || {}
  
          if (props[propName].xml.attribute) {
            const enumAttrVal = Array.isArray(props[propName].enum)
              ? props[propName].enum[0]
              : undefined
            const attrExample = props[propName].example
            const attrDefault = props[propName].default
  
            if(attrExample !== undefined) {
              _attr[props[propName].xml.name || propName] = attrExample
            } else if(attrDefault !== undefined) {
              _attr[props[propName].xml.name || propName] = attrDefault
            } else if(enumAttrVal !== undefined) {
              _attr[props[propName].xml.name || propName] = enumAttrVal
            } else {
              _attr[props[propName].xml.name || propName] = primitive(props[propName])
            }
  
            return
          }
          props[propName].xml.name = props[propName].xml.name || propName
        }
  
        let t = sampleFromSchemaGeneric(schema && props[propName] || undefined, config, overrideE, respectXML)
        if (Array.isArray(t)) {
          res[displayName] = res[displayName].concat(t)
        } else {
          res[displayName].push(t)
        }
      }
    } else {
      addPropertyToResult = (propName, overrideE) =>
        res[propName] = sampleFromSchemaGeneric(props[propName], config, overrideE, respectXML)
    }
  
    // check for plain value and if found use it to generate sample from it
    if(usePlainValue) {
      let sample
      if(exampleOverride !== undefined) {
        sample = sanitizeRef(exampleOverride)
      } else if(example !== undefined) {
        sample = sanitizeRef(example)
      } else {
        sample = sanitizeRef(schema.default)
      }
  
      // if json just return
      if(!respectXML) {
        return sample
      }
  
      // recover missing type
      if(!schema) {
        type = Array.isArray(sample) ? "array" : typeof sample
      }
  
      // generate xml sample recursively for array case
      if(type === "array") {
        if (!Array.isArray(sample)) {
          sample = [sample]
        }
        const itemSchema = schema
          ? schema.items
          : undefined
        if(itemSchema) {
          itemSchema.xml = itemSchema.xml || xml || {}
          itemSchema.xml.name = itemSchema.xml.name || xml.name
        }
        const itemSamples = sample
          .map(s => sampleFromSchemaGeneric(itemSchema, config, s, respectXML))
        if(xml.wrapped) {
          res[displayName] = itemSamples
          if (!isEmpty(_attr)) {
            res[displayName].push({_attr: _attr})
          }
        }
        else {
          res = itemSamples
        }
        return res
      }
  
      // generate xml sample recursively for object case
      if(type === "object") {
        for (let propName in sample) {
          if (!sample.hasOwnProperty(propName)) {
            continue
          }
          if (schema && props[propName] && props[propName].readOnly && !includeReadOnly) {
            continue
          }
          if (schema && props[propName] && props[propName].writeOnly && !includeWriteOnly) {
            continue
          }
          if (schema && props[propName] && props[propName].xml && props[propName].xml.attribute && !(example && example[propName])) {
            _attr[props[propName].xml.name || propName] = sample[propName]
            continue
          }
          if (schema && props[propName] && props[propName].xml && props[propName].xml.attribute) {
            _attr[props[propName].xml.name || propName] = example[propName]
            continue
          }
          addPropertyToResult(propName, sample[propName])
        }
        if (!isEmpty(_attr)) {
          res[displayName].push({_attr: _attr})
        }
  
        return res
      }
  
      res[displayName] = !isEmpty(_attr) ? [{_attr: _attr}, sample] : sample
      return res
    }
  
    // use schema to generate sample
  
    if(type === "object") {
      for (let propName in props) {
        if (!props.hasOwnProperty(propName)) {
          continue
        }
        if ( props[propName] && props[propName].deprecated ) {
          continue
        }
        if ( props[propName] && props[propName].readOnly && !includeReadOnly ) {
          continue
        }
        if ( props[propName] && props[propName].writeOnly && !includeWriteOnly ) {
          continue
        }
        addPropertyToResult(propName)
      }
  
      if ( additionalProperties === true ) {
        if(respectXML) {
          res[displayName].push({additionalProp: "Anything can be here"})
        } else {
          res.additionalProp1 = {}
        }
      } else if ( additionalProperties ) {
        const additionalProps = objectify(additionalProperties)
        const additionalPropSample = sampleFromSchemaGeneric(additionalProps, config, undefined, respectXML)
  
        if(respectXML && additionalProps.xml && additionalProps.xml.name && additionalProps.xml.name !== "notagname")
        {
          res[displayName].push(additionalPropSample)
        } else {
          for (let i = 1; i < 4; i++) {
            if(respectXML) {
              const temp = {}
              temp["additionalProp" + i] = additionalPropSample["notagname"]
              res[displayName].push(temp)
            } else {
              res["additionalProp" + i] = additionalPropSample
            }
          }
        }
      }
      if (respectXML && _attr) {
        res[displayName].push({_attr: _attr})
      }
  
      return res
    }
  
    if(type === "array") {
      let sampleArray
      if(respectXML) {
        items.xml = items.xml || schema.xml || {}
        items.xml.name = items.xml.name || xml.name
      }
      if(Array.isArray(items.anyOf)) {
        sampleArray = items.anyOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i), config, undefined, respectXML))
      } else if(Array.isArray(items.oneOf)) {
        sampleArray = items.oneOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i), config, undefined, respectXML))
      } else if(!respectXML || respectXML && xml.wrapped) {
        sampleArray = [sampleFromSchemaGeneric(items, config, undefined, respectXML)]
      } else {
        return sampleFromSchemaGeneric(items, config, undefined, respectXML)
      }
      if(respectXML && xml.wrapped) {
        res[displayName] = sampleArray
        if (!isEmpty(_attr)) {
          res[displayName].push({_attr: _attr})
        }
        return res
      }
      return sampleArray
    }
  
    let value
    if (schema && Array.isArray(schema.enum)) {
      //display enum first value
      value = normalizeArray(schema.enum)[0]
    } else if(schema) {
      // display schema default
      value = primitive(schema)
    } else {
      return
    }
    if (type === "file") {
      return
    }
  
    if(respectXML) {
      res[displayName] = !isEmpty(_attr) ? [{_attr: _attr}, value] : value
      return res
    }
  
    return value
  }
  
  export const inferSchema = (thing) => {
    if(thing.schema)
      thing = thing.schema
  
    if(thing.properties) {
      thing.type = "object"
    }
  
    return thing // Hopefully this will have something schema like in it... `type` for example
  }
  
  export const createXMLExample = (schema, config={}, o) => {
    const json = sampleFromSchemaGeneric(schema, config, o, true)
    if (!json) { return }
  
    return XML(json, { declaration: true, indent: "\t" })
  }
  
  export const sampleFromSchema = (schema, config={}, o) =>
    sampleFromSchemaGeneric(schema, config, o, false)
  
  export const memoizedCreateXMLExample = memoizee(createXMLExample)
  
  export const memoizedSampleFromSchema = memoizee(sampleFromSchema)
  