# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class ExternalDataSource
  attr_reader :json, :parsed_items, :options, :term

  def initialize(options:, term:, render_context:, limit: 10)
    @term    = term
    @options = options
    @limit   = limit
    @render_context = render_context
  end

  def process
    @json         = fetch_json
    @parsed_items = get_items_list(@json)

    @parsed_items
      .slice(...@limit)
      .map do |elem|
        {
          value: get_item_value(elem),
          label: get_item_label(elem)
        }
      end
  end

  private

  def fetch_json
    response = UserAgent.get(
      search_url,
      {},
      HttpOptions.new(options).build
    )

    raise Errors::NetworkError.new(self, response.error) if !response.success?

    response.data
  rescue ArgumentError, URI::InvalidURIError
    raise Errors::SearchUrlInvalidError, self
  end

  def search_url
    raise Errors::SearchUrlMissingError, self if options[:search_url].blank?

    NotificationFactory::Renderer.new(
      objects:    {
        **@render_context,

        # Extend the render context with the current object instance.
        #   This will allow for usage of `search.term` in the search URL, as this property is readily available here.
        #   Only this approach will guarantee that the template variable is replaced with a properly URL encoded value.
        #   https://github.com/zammad/zammad/issues/4980
        search: self,
      },
      template:   options[:search_url],
      escape:     false,
      url_encode: true,
    ).render(debug_errors: false)
  end

  def get_items_list(input)
    path = options[:search_result_list_key]

    array = if path.present?
              input.dig(*path.split('.'))
            else
              input
            end

    raise TypeError if !array
    raise Errors::ListNotArrayParsingError.new(self, path) if !array.is_a?(Array)

    array
  rescue TypeError
    raise Errors::ListPathParsingError.new(self, path)
  end

  def get_item_value(input)
    get_textual_value(:value, input)
  end

  def get_item_label(input)
    get_textual_value(:label, input)
  end

  def get_textual_value(key, input)
    options_key = "search_result_#{key}_key"

    path = options[options_key]

    value = if path.present?
              input.dig(*path.split('.'))
            else
              input
            end

    raise TypeError if value.nil?

    if [String, Numeric, TrueClass, FalseClass].none? { |elem| value.is_a?(elem) }
      raise Errors::ParsingError.class_for(key, :invalid).new(self, path)
    end

    value
  rescue TypeError
    raise Errors::ParsingError.class_for(key, :path).new(self, path)
  end

end