module Arbre
  class Context
    def resource
      helpers.resource
    end
  end

  class Element

    module BuilderMethods

      # we do not want to check the arity of the
      # block in express templates because components
      # are expected to be able to contain other components or template code
      # without use of a builder style syntax
      def build_tag(klass, *args, &block)
        return if should_supress_output?(args)
        tag = klass.new(arbre_context)
        tag.parent = current_arbre_element

        with_current_arbre_element tag do
          begin
            tag.build(*args, &block)
          rescue Exception => e
            on_component_error(tag, e)
          end
        end

        tag
      end

      # Conditionally do not emit markup
      # Example that would supress generation of the h2 markup:
      #  * h2(only_when: false) { "Some text" }
      #  * h2(unless: true) { "Some text" }
      def should_supress_output?(args)
        if args.last.kind_of?(Hash)
          only_when = args.last.delete(:only_when)
          unless_condition = args.delete(:unless)
          return true if only_when === false
          return true if !unless_condition.nil? && !!unless_condition
        else
          false
        end
      end


      def on_component_error(tag, exception)
        tag.content = "Error rendering #{tag.class} component: #{exception.message}"
        ::Rails.logger.error exception
        ::Rails.logger.error exception.backtrace.slice(0..20).join("\n")
      end
    end

    def possible_route_proxy(name)
      if helpers.controller.class.parent &&
        helpers.respond_to?(namespace = helpers.controller.class.parent.to_s.underscore)
        if (route_proxy = helpers.send(namespace)).respond_to?(name)
          return route_proxy
        end
      end
      return nil
    end

    # Implements the method lookup chain. When you call a method that
    # doesn't exist, we:
    #
    #  1. Try to call the method on the current DOM context
    #  2. Return an assigned variable of the same name
    #  3. Call the method on the helper object
    #  4. Call super
    #
    def method_missing(name, *args, &block)
      if current_arbre_element.respond_to?(name)
        current_arbre_element.send name, *args, &block
      elsif assigns && assigns.has_key?(name.to_sym)
        assigns[name.to_sym]
      elsif helpers.respond_to?(name)
        helper_method(name, *args, &block)
      elsif route_proxy = possible_route_proxy(name)
        route_proxy.send(name, *args, &block)
      else
        super
      end
    end

    # In order to not pollute our templates with helpers. prefixed
    # everywhere we want to try to distinguish helpers that are almost
    # always used as parameters to other methods such as path helpers
    # and not add them as elements
    def helper_method(name, *args, &block)
      if name.match /_path$/
        helpers.send(name, *args, &block)
      elsif (const_get([name, 'engine'].join('/').classify) rescue nil)
        helpers.send(name, *args, &block)
      else
        current_arbre_element.add_child helpers.send(name, *args, &block)
      end
    end

  end

end