class XML::XPathContext
  getter errors = [] of XML::Error

  def initialize(@node : Node)
    @ctx = LibXML.xmlXPathNewContext(@node.to_unsafe.value.doc)
    @ctx.value.node = @node.to_unsafe

    {% if LibXML.has_method?(:xmlXPathSetErrorHandler) %}
      LibXML.xmlXPathSetErrorHandler(@ctx, ->Error.structured_callback, Box.box(@errors))
    {% end %}
  end

  # :nodoc:
  def finalize
    LibXML.xmlXPathFreeContext(@ctx)
  end

  def evaluate(search_path : String)
    xpath_object =
      {% if LibXML.has_method?(:xmlXPathSetErrorHandler) %}
        LibXML.xmlXPathEvalExpression(search_path, self)
      {% else %}
        XML::Error.unsafe_collect(errors) do
          LibXML.xmlXPathEvalExpression(search_path, self)
        end
      {% end %}

    unless xpath_object
      if error = @errors.first?
        raise error
      else
        raise XML::Error.new("Error in '#{search_path}' expression", 0)
      end
    end

    retval =
      case xpath_object.value.type
      when LibXML::XPathObjectType::STRING
        String.new(xpath_object.value.stringval)
      when LibXML::XPathObjectType::NUMBER
        xpath_object.value.floatval
      when LibXML::XPathObjectType::BOOLEAN
        xpath_object.value.boolval != 0
      when LibXML::XPathObjectType::NODESET
        NodeSet.new(@node.document, xpath_object.value.nodesetval)
      else
        NodeSet.new
      end

    LibXML.xmlXPathFreeObject(xpath_object)
    retval
  end

  def register_namespaces(namespaces) : Nil
    namespaces.each do |prefix, uri|
      register_namespace prefix, uri
    end
  end

  def register_namespace(prefix : String, uri : String?)
    prefix = prefix.lchop("xmlns:")
    LibXML.xmlXPathRegisterNs(self, prefix, uri.to_s)
  end

  def register_variables(variables) : Nil
    variables.each do |name, value|
      register_variable name, value
    end
  end

  def register_variable(name, value)
    case value
    when Bool
      obj = LibXML.xmlXPathNewBoolean(value ? 1 : 0)
    when Number
      obj = LibXML.xmlXPathNewFloat(value.to_f64)
    else
      obj = LibXML.xmlXPathNewCString(value.to_s)
    end

    LibXML.xmlXPathRegisterVariable(self, name, obj)
  end

  # :nodoc:
  def to_unsafe
    @ctx
  end
end

LibXML.xmlXPathInit
