# frozen_string_literal: true

module MicroMicro
  class Item
    include Collectible

    # Extract items from a context.
    #
    # @param context [Nokogiri::HTML::Document, Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
    # @return [Array<MicroMicro::Item>]
    def self.from_context(context)
      node_set_from(context).map { |node| new(node) }
    end

    # Extract item nodes from a context.
    #
    # @param context [Nokogiri::XML::NodeSet, Nokogiri::XML::Element]
    # @param node_set [Nokogiri::XML::NodeSet]
    # @return [Nokogiri::XML::NodeSet]
    # rubocop:disable Metrics
    def self.node_set_from(context, node_set = Nokogiri::XML::NodeSet.new(context.document, []))
      context.each { |node| node_set_from(node, node_set) } if context.is_a?(Nokogiri::XML::NodeSet)

      if context.is_a?(Nokogiri::XML::Element) && !Helpers.ignore_node?(context)
        if Helpers.item_node?(context)
          node_set << context unless Helpers.item_nodes?(context.ancestors) && Helpers.property_node?(context)
        else
          node_set_from(context.element_children, node_set)
        end
      end

      node_set
    end
    # rubocop:enable Metrics

    # Parse a node for microformats2-encoded data.
    #
    # @param node [Nokogiri::XML::Element]
    def initialize(node)
      @node = node

      properties << implied_name if implied_name?
      properties << implied_photo if implied_photo?
      properties << implied_url if implied_url?
    end

    # A collection of child items parsed from the node.
    #
    # @see https://microformats.org/wiki/microformats2-parsing#parse_an_element_for_class_microformats
    #
    # @return [MicroMicro::Collections::ItemsCollection]
    def children
      @children ||= Collections::ItemsCollection.new(self.class.from_context(node.element_children))
    end

    # The value of the node's `id` attribute, if present.
    #
    # @return [String, nil]
    def id
      @id ||= node['id']&.strip
    end

    # :nocov:
    # @return [String]
    def inspect
      "#<#{self.class}:#{format('%#0x', object_id)} " \
        "types: #{types.inspect}, " \
        "properties: #{properties.count}, " \
        "children: #{children.count}>"
    end
    # :nocov:

    # A collection of plain text properties parsed from the node.
    #
    # @return [MicroMicro::Collections::PropertiesCollection]
    def plain_text_properties
      @plain_text_properties ||= properties.plain_text_properties
    end

    # A collection of properties parsed from the node.
    #
    # @return [MicroMicro::Collections::PropertiesCollection]
    def properties
      @properties ||= Collections::PropertiesCollection.new(Property.from_context(node.element_children))
    end

    # Return the parsed item as a Hash.
    #
    # @see https://microformats.org/wiki/microformats2-parsing#parse_an_element_for_class_microformats
    #
    # @return [Hash]
    def to_h
      hash = {
        type: types,
        properties: properties.to_h
      }

      hash[:id] = id if id.present?
      hash[:children] = children.to_a if children.any?

      hash
    end

    # An array of root class names parsed from the node's `class` attribute.
    #
    # @return [Array<String>]
    def types
      @types ||= Helpers.root_class_names_from(node)
    end

    # A collection of url properties parsed from the node.
    #
    # @return [MicroMicro::Collections::PropertiesCollection]
    def url_properties
      @url_properties ||= properties.url_properties
    end

    private

    attr_reader :node

    # @return [MicroMicro::ImpliedProperty]
    def implied_name
      @implied_name ||= ImpliedProperty.new(node, 'p-name')
    end

    # @return [Boolean]
    def implied_name?
      imply_name? && implied_name.value?
    end

    # @return [MicroMicro::ImpliedProperty]
    def implied_photo
      @implied_photo ||= ImpliedProperty.new(node, 'u-photo')
    end

    # @return [Boolean]
    def implied_photo?
      imply_photo? && implied_photo.value?
    end

    # @return [MicroMicro::ImpliedProperty]
    def implied_url
      @implied_url ||= ImpliedProperty.new(node, 'u-url')
    end

    # @return [Boolean]
    def implied_url?
      imply_url? && implied_url.value?
    end

    # @return [Boolean]
    def imply_name?
      properties.names.none?('name') &&
        properties.none?(&:embedded_markup_property?) &&
        properties.none?(&:plain_text_property?) &&
        !nested_items?
    end

    # @return [Boolean]
    def imply_photo?
      properties.names.none?('photo') &&
        properties.reject(&:implied?).none?(&:url_property?) &&
        !nested_items?
    end

    # @return [Boolean]
    def imply_url?
      properties.names.none?('url') &&
        properties.reject(&:implied?).none?(&:url_property?) &&
        !nested_items?
    end

    # @return [Boolean]
    def nested_items?
      @nested_items ||= properties.find(&:item_node?) || children.any?
    end
  end
end