# frozen_string_literal: true

# Streaming JSON handler relays sections and streamed values back to the importer instance.

class JsonStreamHandler < Oj::Saj
  HashState = Struct.new(:hash, :root, :key)
  ArrayState = Struct.new(:array, :key)
  StreamState = Struct.new(:key)

  def initialize(processor)
    super()
    @processor = processor
    @stack = []
  end

  def hash_start(key = nil, *_)
    state = HashState.new({}, @stack.empty?, normalize_key(key))
    @stack << state
  end

  def hash_end(key = nil, *_)
    state = @stack.pop
    value = state.hash
    parent = @stack.last

    dispatch_to_parent(parent, value, normalize_key(key) || state.key)
  end

  def array_start(key = nil, *_)
    normalized_key = normalize_key(key)
    parent = @stack.last

    if parent.is_a?(HashState) && parent.root && @stack.size == 1 && Users::ImportData::STREAMED_SECTIONS.include?(normalized_key)
      @stack << StreamState.new(normalized_key)
    else
      @stack << ArrayState.new([], normalized_key)
    end
  end

  def array_end(key = nil, *_)
    state = @stack.pop
    case state
    when StreamState
      @processor.send(:finish_stream, state.key)
    when ArrayState
      value = state.array
      parent = @stack.last
      dispatch_to_parent(parent, value, normalize_key(key) || state.key)
    end
  end

  def add_value(value, key)
    parent = @stack.last
    dispatch_to_parent(parent, value, normalize_key(key))
  end

  private

  def normalize_key(key)
    key&.to_s
  end

  def dispatch_to_parent(parent, value, key)
    return unless parent

    case parent
    when HashState
      if parent.root && @stack.size == 1
        @processor.send(:handle_section, key, value)
      else
        parent.hash[key] = value
      end
    when ArrayState
      parent.array << value
    when StreamState
      @processor.send(:handle_stream_value, parent.key, value)
    end
  end
end
