require "relaton/element_finder"

module Relaton
  class Bibcollection
    extend Relaton::ElementFinder

    ATTRIBS = %i[title items doctype author].freeze

    attr_accessor(*ATTRIBS)

    # @param options [Hash]
    def initialize(options)
      ATTRIBS.each do |k|
        value = options[k] || options[k.to_s]
        send("#{k}=", value)
      end
      reduce_items
    end

    # arbitrary number, must sort after all bib items
    def doc_number
      9999999
    end

    # Add a dcoument to the collection
    # @param item [RelatonBib::BibliographicItem]
    def <<(item)
      items << new_bib_item_class(item)
    end

    # rubocop:disable Metrics/MethodLength

    # @param source [Nokogiri::XML::Element]
    def self.from_xml(source)
      title = find_text("./relaton-collection/title", source)
      author = find_text(
        "./relaton-collection/contributor[role/@type='author']/organization/"\
        "name", source
      )

      items = find_xpath("./relaton-collection/relation", source)&.map do |item|
        bibdata = find("./bibdata|./bibitem", item)
        klass = bibdata ? Bibdata : Bibcollection
        klass.from_xml(bibdata || item)
      end

      new(title: title, author: author, items: items)
    end

    # rubocop:disable Metrics/AbcSize

    # @param opts [Hash]
    # @return [String] XML
    def to_xml(opts = {})
      items.sort_by! &:doc_number

      collection_type = if doctype
                          "type=\"#{doctype}\""
                        else
                          'xmlns="https://open.ribose.com/relaton-xml"'
                        end

      ret = "<relaton-collection #{collection_type}>"
      ret += "<title>#{title}</title>" if title
      if author
        ret += "<contributor><role type='author'/><organization><name>"\
        "#{author}</name></organization></contributor>"
      end
      unless items.empty?
        items.each do |item|
          ret += "<relation type='partOf'>"
          ret += item.to_xml(opts)
          ret += "</relation>\n"
        end
      end
      ret += "</relaton-collection>\n"
    end
    # rubocop:enable Metrics/AbcSize

    # @param item [Hash, RelatonBib::BibliographicItem, Relatin::Bibdata,
    #   Relaton::Bibcollection]
    # @return [Relaton::Bibdata, Relaton::Bibcollection]
    def new_bib_item_class(item)
      if item.is_a?(Hash)
        if item["items"]
          ::Relaton::Bibcollection.new(item)
        else
          bibitem = ::Relaton::Cli::YAMLConvertor.convert_single_file item
          ::Relaton::Bibdata.new bibitem
        end
      elsif item.is_a?(Relaton::Bibdata) || item.is_a?(Relaton::Bibcollection)
        item
      else ::Relaton::Bibdata.new(item)
      end
    end
    # rubocop:enable Metrics/MethodLength

    def items_flattened
      items.sort_by! &:doc_number

      items.reduce([]) do |acc, item|
        acc << if item.is_a? ::Relaton::Bibcollection
                 item.items_flattened
               else
                 item
               end
      end
    end

    def to_yaml
      to_h.to_yaml
    end

    def to_h
      items.sort_by! &:doc_number

      a = ATTRIBS.reduce({}) do |acc, k|
        acc[k.to_s] = send(k)
        acc
      end

      a["items"] = a["items"].map(&:to_h)

      { "root" => a }
    end

    private

    def reduce_items
      self.items = (items || []).reduce([]) do |acc, item|
        acc << if item.is_a?(Bibcollection) || item.is_a?(Bibdata)
                 item
               else new_bib_item_class(item)
               end
      end
    end
  end
end