module GOVUKDesignSystemFormBuilder
  class Base
    delegate :capture, :content_tag, :safe_join, :tag, :raw, :link_to, to: :@builder

    def initialize(builder, object_name, attribute_name, &block)
      @builder = builder
      @object_name = object_name
      @attribute_name = attribute_name
      @block_content = capture { block.call } if block_given?
    end

    def hint_element
      @hint_element ||= Elements::Hint.new(@builder, @object_name, @attribute_name, @hint_text)
    end

    def error_element
      @error_element ||= Elements::ErrorMessage.new(@builder, @object_name, @attribute_name)
    end

    def label_element
      @label_element ||= Elements::Label.new(@builder, @object_name, @attribute_name, **@label)
    end

    def supplemental_content
      @supplemental_content ||= Containers::Supplemental.new(@builder, @object_name, @attribute_name, @block_content)
    end

    # returns the id value used for the input
    #
    # @note field_id is overridden so that the error summary can link to the
    #   correct element.
    #
    #   It's straightforward for inputs with a single element (like a textarea
    #   or text input) but the GOV.UK Design System requires that the error
    #   summary link to the first checkbox or radio in a list, so additional
    #   logic is requred
    #
    # @return [String] the element's +id+
    # @see https://design-system.service.gov.uk/components/error-summary/#linking-from-the-error-summary-to-each-answer
    #   GOV.UK linking to elements from the error summary
    def field_id(link_errors: false)
      if link_errors && has_errors?
        build_id('field-error', include_value: false)
      else
        build_id('field')
      end
    end

    def hint_id
      return nil unless @hint_text.present?

      build_id('hint')
    end

    def error_id
      return nil unless has_errors?

      build_id('error')
    end

    def conditional_id
      build_id('conditional')
    end

    def supplemental_id
      return nil unless @block_content.present?

      build_id('supplemental')
    end

    def has_errors?
      @builder.object.errors.any? &&
        @builder.object.errors.messages.dig(@attribute_name).present?
    end

    def wrap_conditional(block)
      content_tag('div', class: conditional_classes, id: conditional_id) do
        capture { block.call }
      end
    end

    def described_by(*ids)
      ids.flatten.compact.join(' ').presence
    end

  private

    # Builds the values used for HTML id attributes throughout the builder
    #
    # @param id_type [String] a description of the id's type, eg +hint+, +error+
    # @param delimiter [String] the characters used to 'split' the output
    # @param replace [String] the targets to be replaced by the delimiter
    # @param attribute_name [String] overrides the object's +@attribute_name+
    # @param include_value [Boolean] controls whether or not the value will form part
    #   of the final id
    #
    # @return [String] the id composed of object, attribute, value and type
    #
    # @example
    #   build_id('hint') #=> "person-name-hint"
    def build_id(id_type, delimiter = '-', replace = '_', attribute_name: nil, include_value: true)
      attribute = attribute_name || @attribute_name
      value     = include_value && @value || nil
      [
        @object_name,
        attribute,
        value,
        id_type
      ]
        .compact
        .join(delimiter)
        .parameterize
        .tr(replace, delimiter)
    end
  end
end