module Navigasmic
  module Builder
    class ListBuilder < Base
      class Configuration < Base::Configuration
        attr_accessor :wrapper_class, :item_class, :has_nested_class, :is_nested_class, :disabled_class, :highlighted_class
        attr_accessor :wrapper_tag, :group_tag, :item_tag
        attr_accessor :link_generator, :label_generator

        def initialize
          # which keys (for other builder) should be removed from options
          @excluded_keys = [:map]

          # tag configurations
          @wrapper_tag = :ul
          @group_tag = :ul
          @item_tag = :li

          # class configurations
          @wrapper_class = "semantic-navigation"
          @item_class = nil
          @has_nested_class = "has-nested"
          @is_nested_class = "is-nested"
          @disabled_class = "disabled"
          @highlighted_class = "active"

          # generator callbacks
          @link_generator = proc { |label, link, options, is_nested| link_to(label, link, options) }
          @label_generator = proc { |label, is_linked, is_nested| "<span>#{label}</span>" }

          super
        end
      end

      def initialize(context, name, options, &block)
        super
        @options[:id] ||= name.to_s.underscore unless @options.has_key?(:id)
        @options[:class] = merge_classes!(@options, @config.wrapper_class)
      end

      def render
        content_tag(@config.wrapper_tag, capture(&@definition), @options)
      end

      def group(label_or_options = nil, options = {}, &block)
        raise ArgumentError, "Missing block for group" unless block_given?
        if label_or_options.is_a?(Hash)
          options = label_or_options
          label_or_options = nil
        elsif label_or_options.is_a?(Proc)
          label_or_options = eval_in_context(&label_or_options)
        end
        return "" unless visible?(options)

        concat(structure_for(label_or_options, false, options.except(:hidden_unless), &block))
      end

      def item(label, *args, &block)
        options = args.extract_options!
        options = flatten_and_eval_options(options)
        return "" unless visible?(options)

        if label.is_a?(Proc)
          label = eval_in_context(&label)
        end

        item = Navigasmic::Item.new(label, extract_and_determine_link(label, options, *args), visible?(options), options)

        merge_classes!(options, @config.item_class)
        merge_classes!(options, @config.disabled_class) if item.disabled?
        merge_classes!(options, @config.highlighted_class) if item.highlights_on?(@context.request.path, @context.params)

        concat(structure_for(label, item.link? ? item.link : false, options, &block))
      end

      private
        def structure_for(label, link = false, options = {}, &block)
          label = label_for(label, link, block_given?, options)

          content = ""
          if block_given?
            merge_classes!(options, @config.has_nested_class)
            content = content_tag(@config.group_tag, capture(&block), class: @config.is_nested_class)
          end

          content_tag(@config.item_tag, "#{label}#{content}".html_safe, options)
        end

        def label_for(label, link, is_nested = false, options = {})
          if label.present?
            label = @context.instance_exec(label, options, !!link, is_nested, &@config.label_generator).html_safe
          end
          label = @context.instance_exec(label, link, options.delete(:link_html) || {}, is_nested, &@config.link_generator).html_safe if link
          label
        end

        def merge_classes!(hash, classname)
          return if classname.blank?
          hash[:class] = (hash[:class] ? "#{hash[:class]} " : "") << classname
        end
    end
  end
end
