'use strict'

var html = require('property-information/html')
var svg = require('property-information/svg')
var find = require('property-information/find')
var ns = require('web-namespaces')
var s = require('hastscript/svg')
var h = require('hastscript')
var xtend = require('xtend')
var count = require('ccount')

module.exports = wrapper

var own = {}.hasOwnProperty

// Handlers.
var map = {
  '#document': root,
  '#document-fragment': root,
  '#text': text,
  '#comment': comment,
  '#documentType': doctype
}

// Wrapper to normalise options.
function wrapper(ast, options) {
  var settings = options || {}
  var file

  if (settings.messages) {
    file = settings
    settings = {}
  } else {
    file = settings.file
  }

  return transform(ast, {
    schema: settings.space === 'svg' ? svg : html,
    file: file,
    verbose: settings.verbose,
    location: false
  })
}

// Transform a node.
function transform(ast, config) {
  var schema = config.schema
  var fn = own.call(map, ast.nodeName) ? map[ast.nodeName] : element
  var children
  var node
  var pos

  if (fn === element) {
    config.schema = ast.namespaceURI === ns.svg ? svg : html
  }

  if (ast.childNodes) {
    children = nodes(ast.childNodes, config)
  }

  node = fn(ast, children, config)

  if (ast.sourceCodeLocation && config.file) {
    pos = location(node, ast.sourceCodeLocation, config)

    if (pos) {
      config.location = true
      node.position = pos
    }
  }

  config.schema = schema

  return node
}

// Transform children.
function nodes(children, config) {
  var length = children.length
  var index = -1
  var result = []

  while (++index < length) {
    result[index] = transform(children[index], config)
  }

  return result
}

// Transform a document.
// Stores `ast.quirksMode` in `node.data.quirksMode`.
function root(ast, children, config) {
  var node = {type: 'root', children: children, data: {}}
  var doc

  node.data.quirksMode = ast.mode === 'quirks' || ast.mode === 'limited-quirks'

  if (config.file && config.location) {
    doc = String(config.file)

    node.position = {
      start: {line: 1, column: 1, offset: 0},
      end: {
        line: count(doc, '\n') + 1,
        column: doc.length - doc.lastIndexOf('\n'),
        offset: doc.length
      }
    }
  }

  return node
}

// Transform a doctype.
function doctype(ast) {
  return {
    type: 'doctype',
    name: ast.name || '',
    public: ast.publicId || null,
    system: ast.systemId || null
  }
}

// Transform a text.
function text(ast) {
  return {type: 'text', value: ast.value}
}

// Transform a comment.
function comment(ast) {
  return {type: 'comment', value: ast.data}
}

// Transform an element.
function element(ast, children, config) {
  var fn = config.schema.space === 'svg' ? s : h
  var name = ast.tagName
  var attributes = ast.attrs
  var length = attributes.length
  var props = {}
  var index = -1
  var attribute
  var prop
  var node
  var pos
  var start
  var end

  while (++index < length) {
    attribute = attributes[index]
    prop = (attribute.prefix ? attribute.prefix + ':' : '') + attribute.name
    props[prop] = attribute.value
  }

  node = fn(name, props, children)

  if (name === 'template' && 'content' in ast) {
    pos = ast.sourceCodeLocation
    start = pos && pos.startTag && position(pos.startTag).end
    end = pos && pos.endTag && position(pos.endTag).start

    node.content = transform(ast.content, config)

    if ((start || end) && config.file) {
      node.content.position = {start: start, end: end}
    }
  }

  return node
}

// Create clean positional information.
function location(node, location, config) {
  var schema = config.schema
  var verbose = config.verbose
  var pos = position(location)
  var reference
  var attributes
  var attribute
  var props
  var prop

  if (node.type === 'element') {
    reference = node.children[node.children.length - 1]

    // Bug for unclosed with children.
    // See: <https://github.com/inikulin/parse5/issues/109>.
    if (
      !location.endTag &&
      reference &&
      reference.position &&
      reference.position.end
    ) {
      pos.end = xtend(reference.position.end)
    }

    if (verbose) {
      attributes = location.attrs
      props = {}

      for (attribute in attributes) {
        prop = find(schema, attribute).property
        props[prop] = position(attributes[attribute])
      }

      node.data = {
        position: {
          opening: position(location.startTag),
          closing: location.endTag ? position(location.endTag) : null,
          properties: props
        }
      }
    }
  }

  return pos
}

function position(loc) {
  var start = point({
    line: loc.startLine,
    column: loc.startCol,
    offset: loc.startOffset
  })
  var end = point({
    line: loc.endLine,
    column: loc.endCol,
    offset: loc.endOffset
  })
  return start || end ? {start: start, end: end} : null
}

function point(point) {
  return point.line && point.column ? point : null
}
