# Data provides functionalities to accesses information
class LHS::Data
  autoload :Equality,
    'lhs/concerns/data/equality'
  autoload :Json,
    'lhs/concerns/data/json'
  autoload :ToHash,
    'lhs/concerns/data/to_hash'

  include Equality
  include Json
  include ToHash
  include LHS::Inspect

  delegate :instance_methods, :items_key, :limit_key, :total_key, :pagination_key, to: :class

  # prevent clashing with attributes of underlying data
  attr_accessor :_proxy, :_raw, :_parent, :_record, :_request, :_endpoint

  def initialize(input, parent = nil, record = nil, request = nil, endpoint = nil)
    self._raw = raw_from_input(input)
    self._parent = parent
    self._record = record
    self._proxy = proxy_from_input(input)
    self._request = request
    self._endpoint = endpoint
  end

  # merging data
  # e.g. when loading remote data via link
  def merge_raw!(data)
    return false if data.blank? || !data._raw.is_a?(Hash)
    if _record && _record.item_created_key
      _raw.merge! data._raw.dig(*_record.item_created_key)
    else
      _raw.merge! data._raw
    end
  end

  def _root
    root = self
    root = root._parent while root && root._parent
    root
  end

  def parent
    if _parent && _parent._record
      _parent._record.new(_parent, false)
    else
      _parent
    end
  end

  def class
    _root._record
  end

  # enforce internal data structure to have deep symbolized keys
  def _raw=(raw)
    raw.to_hash.deep_symbolize_keys! if raw && raw.respond_to?(:to_hash)
    @_raw = raw
  end

  def root_item?
    root_item == self
  end

  def collection?
    _proxy.is_a? LHS::Collection
  end

  def item?
    _proxy.is_a? LHS::Item
  end

  protected

  def method_missing(name, *args, &block)
    _proxy.send(name, *args, &block)
  end

  def respond_to_missing?(name, include_all = false)
    (root_item? && instance_methods.include?(name)) ||
      _proxy.respond_to?(name, include_all)
  end

  private

  def collection_proxy?(input)
    (input.is_a?(Hash) && LHS::Collection.access(input: input, record: _record)) ||
      input.is_a?(Array) ||
      _raw.is_a?(Array)
  end

  def root_item
    return if _proxy.class != LHS::Item
    root = root_item = self
    loop do
      root = root._parent
      root_item = root if root && root._proxy.is_a?(LHS::Item)
      if !(root && root._parent)
        break
      end
    end
    root_item
  end

  def root?
    _root == self
  end

  def proxy_from_input(input)
    if input.is_a? LHS::Proxy
      input
    elsif collection_proxy?(raw_from_input(input))
      LHS::Collection.new(self)
    else
      LHS::Item.new(self)
    end
  end

  def raw_from_input(input)
    if json?(input)
      raw_from_json_string(input)
    elsif defined?(input._raw)
      input._raw
    elsif defined?(input._data)
      input._data._raw
    else
      raw_from_anything_else(input)
    end
  end

  def json?(input)
    input.is_a?(String) && !input.empty? && !!input.match(/^("|\[|'|\{)/)
  end

  def raw_from_json_string(input)
    json = JSON.parse(input)
    if json.is_a?(Hash)
      json.deep_symbolize_keys
    elsif json.is_a?(Array)
      json.map { |item| item.is_a?(Hash) ? item.deep_symbolize_keys : item }
    else
      json
    end
  end

  def raw_from_anything_else(input)
    input = input.to_hash if input.class != Hash && input.respond_to?(:to_hash)
    input.deep_symbolize_keys! if input.is_a?(Hash)
    input
  end
end