module Jekyll
  class ConfigurationBlock < Liquid::Block
    TYPE_LINKS = {
      'action'       => '/docs/scripts/',
      'device_class' => '/docs/configuration/customizing-devices/#device-class',
      'template'     => '/docs/configuration/templating/',
      'icon'         => '/docs/configuration/customizing-devices/#icon',
      'selector'     => '/docs/blueprint/selectors/',
    }

    TYPES = [
      'action', 'boolean', 'string', 'integer', 'float', 'time', 'template',
      'device_class', 'icon', 'map', 'list', 'date', 'datetime', 'any',
      'selector',
    ]

    MIN_DEFAULT_LENGTH = 30

    def initialize(tag_name, text, tokens)
      super
      @component, @platform = text.strip.split('.', 2)
    end

    def slug(key)
      key.downcase.strip.gsub(' ', '-').gsub(/[^\w\-]/, '')
    end

    def type_class(type)
      ((type.is_a? Array) ? type.join(' ') : type).downcase
    end

    def type_link(type, component: nil)
      if type.include? ','
        type = type.split(',')
      end

      if type.is_a? Array
        return (type.map { |t| type_link(t, component: component) }).join(' | ')
      end

      type.strip!
      if TYPE_LINKS.include? type.downcase
        url = TYPE_LINKS[type.downcase] % {component: component}
        "<a href=\"%s\">%s</a>" % [url, type]
      else
        type
      end
    end

    def required_value(value)
      if value === true
        "Required"
      elsif value === false
        "Optional"
      else
        value.strip.titlecase
      end
    end

    def render_config_vars(vars:, component:, platform:, converter:, classes: nil, parent_type: nil)
      result = Array.new
      result << "<div class='#{classes}'>"

      result << vars.map do |key, attr|
        markup = Array.new
        # There are spaces around the "{key}", to improve double-click selection in Chrome.
        markup << "<div class='config-vars-item'><div class='config-vars-label'><a name='#{slug(key)}' class='title-link' href='\##{slug(key)}'></a> <span class='config-vars-label-name'> #{key} </span>"

        if attr.key? 'type'

          # Check if the type (or list of types) are valid
          if attr['type'].kind_of? Array
            attr['type'].each do |type|
              raise ArgumentError, "Configuration type '#{type}' for key '#{key}' is not a valid type in the documentation."\
              " See: https://developers.home-assistant.io/docs/en/documentation_create_page.html#configuration" unless \
                TYPES.include? type
            end
          else
            raise ArgumentError, "Configuration type '#{attr['type']}' for key '#{key}' is not a valid type in the documentation."\
            " See: https://developers.home-assistant.io/docs/en/documentation_create_page.html#configuration" unless \
              TYPES.include? attr['type']
          end

          markup << "<span class='config-vars-type'>#{type_link(attr['type'], component: component)}</span>"
        else
          # Type is missing, which is required (unless we are in a list or template)
          raise ArgumentError, "Configuration key '#{key}' is missing a type definition" \
            unless ['list', 'template'].include? parent_type
        end

        defaultValue = ""
        isDefault = false
        if attr.key? 'default' and not attr['default'].to_s.empty?
          isDefault = true
          defaultValue = converter.convert(attr['default'].to_s)
        elsif attr['type'].to_s.include? 'boolean'
          # If the type is a boolean, a default key is required
          raise ArgumentError, "Configuration key '#{key}' is a boolean type and"\
            " therefore, requires a default."
        end

        if attr.key? 'required'
          # Check if required is a valid value
          raise ArgumentError, "Configuration key '#{key}' required field must be specified as: "\
            "true, false, inclusive or exclusive."\
            unless [true, false, 'inclusive', 'exclusive'].include? attr['required']

          isTrue = attr['required'].to_s == 'true'
          startSymbol = isTrue ? ' ' : ' ('
          endSymbol = isTrue ? '' : ')'
          showDefault = isDefault && (defaultValue.length <= MIN_DEFAULT_LENGTH)
          shortDefaultValue = ""
          if showDefault
            shortDefaultValue = defaultValue
            shortDefaultValue.slice!("<p>")
            shortDefaultValue.slice!("</p>")
            shortDefaultValue = shortDefaultValue.strip
            shortDefaultValue = ", default: " + shortDefaultValue
          end

          markup << "<span class='config-vars-required'>#{startSymbol}<span class='#{attr['required'].to_s}'>#{required_value(attr['required'])}</span><span class='default'>#{shortDefaultValue}</span>#{endSymbol}</span>"
        end

        markup << "</div><div class='config-vars-description-and-children'>"

        if attr.key? 'description'
          markup << "<span class='config-vars-description'>#{converter.convert(attr['description'].to_s)}</span>"
        else
          # Description is missing
          raise ArgumentError, "Configuration key '#{key}' is missing a description."
        end

        if isDefault && defaultValue.length > MIN_DEFAULT_LENGTH
          markup << "<div class='config-vars-default'>\nDefault: #{defaultValue}</div>"
        end
        markup << "</div>"

        # Check for nested configuration variables
        if attr.key? 'keys'
          markup << render_config_vars(
            vars: attr['keys'], component: component,
            platform: platform, converter: converter,
            classes: 'nested', parent_type: attr['type'])
        end

        markup << "</div>"
      end

      result << "</div>"
      result.join
    end

    def render(context)
      if @component.nil? and @platform.nil?
        page = context.environments.first['page']
        @component, @platform = page['slug'].split('.', 2)
      end

      contents = super(context)

      component = Liquid::Template.parse(@component).render context
      platform  = Liquid::Template.parse(@platform).render context

      site = context.registers[:site]
      converter = site.find_converter_instance(::Jekyll::Converters::Markdown)

      vars = SafeYAML.load(contents)

      <<~MARKUP
        <div class="config-vars">
          <h3>
            <a class="title-link" name="configuration-variables" href="#configuration-variables"></a> Configuration Variables
          </h3>
          <div class="configuration-link">
            <a href="/docs/configuration/" target="_blank">Looking for your configuration file?</a>
          </div>
          #{render_config_vars(
            vars: vars,
            component: component,
            platform: platform,
            converter: converter
          )}
        </div>
      MARKUP
    end
  end
end

Liquid::Template.register_tag('configuration', Jekyll::ConfigurationBlock)