module Navigasmic::Builder
  autoload :ListBuilder,  'navigasmic/builders/list_builder'
  autoload :MapBuilder,   'navigasmic/builders/map_builder'
  autoload :CrumbBuilder, 'navigasmic/builders/crumb_builder'

  class Base
    class Configuration
      attr_accessor :excluded_keys

      def initialize
        @excluded_keys ||= []
        yield self if block_given?
      end
    end

    def initialize(context, name, options, &block)
      @definition = block_given? ? block : Navigasmic.configuration.definitions[name]
      raise ArgumentError, "Missing block or configuration" unless @definition

      @context, @name, @options = context, name, options
      @config = configuration_or_default(@options.delete(:config))
      remove_excluded_options(@options)
    end

    def group(label = nil, options = {}, &block)
      raise "Expected subclass to implement group"
    end

    def item(label = nil, *args, &block)
      raise "Expected subclass to implement item"
    end

    def render
      raise "Expected subclass to implement render"
    end

  private

    def configuration_or_default(config = nil)
      configurations = Navigasmic.configuration.builder_configurations[self.class.to_s]
      proc = configurations.present? ? configurations[config || :default] : nil
      self.class::Configuration.new(&proc)
    end

    def remove_excluded_options(options)
      @config.excluded_keys.each { |key| options.delete(key) }
    end

    def capture(&block)
      (block_given? ? @context.capture(self, &block) : nil).to_s.html_safe
    end

    def eval_in_context(&block)
      @context.instance_eval(&block)
    end

    def method_missing(meth, *args, &block)
      @context.send(meth, *args, &block)
    end

    def flatten_and_eval_options(options)
      remove_excluded_options(options)
      options.inject({}) do |hash, (key, value)|
        if value.is_a?(Array)
          value = value.map{ |v| v.is_a?(Proc) ? eval_in_context(&v) : v }
        elsif value.is_a?(Proc)
          value = eval_in_context(&value)
        end
        hash.update(key => value)
      end
    end

    def visible?(options)
      if options[:hidden_unless].nil?
        true
      elsif options[:hidden_unless].is_a?(Proc)
        eval_in_context(&options[:hidden_unless])
      else
        options[:hidden_unless]
      end
    end

    def extract_and_determine_link(label, options, *args)
      determine_link(label, extract_link(options, *args))
    end

    def extract_link(options, *args)
      if args.length == 1
        args.delete_at(0)
      else
        hash = {controller: options.delete(:controller), action: options.delete(:action)}
        hash = options.delete(:link) || hash
        hash.select{ |key, value| value.present? }
      end
    end

    def determine_link(label, link)
      if link.blank?
        path_helper = "#{label.to_s.underscore.gsub(/\s/, '_')}_path"
        @context.send(path_helper) if @context.respond_to?(path_helper)
      elsif link.is_a?(Proc)
        eval_in_context(&link)
      elsif link.is_a?(String)
        link
      elsif link.is_a?(Hash)
        link
      else
        url_for(link)
      end
    end

  end
end