# :nodoc:
abstract class YAML::Parser
  def initialize(content : String | IO)
    @pull_parser = PullParser.new(content)
  end

  def self.new(content, &)
    parser = new(content)
    yield parser ensure parser.close
  end

  abstract def new_documents
  abstract def new_document
  abstract def new_sequence
  abstract def new_mapping
  abstract def new_scalar
  abstract def put_anchor(anchor, value)
  abstract def get_anchor(anchor)
  abstract def add_to_documents(documents, document)
  abstract def add_to_document(document, node)
  abstract def add_to_sequence(sequence, node)
  abstract def add_to_mapping(mapping, key, value)

  def end_value(value)
  end

  def process_tag(tag, &block)
  end

  protected def cast_document(document)
    document
  end

  # Deserializes multiple YAML document.
  def parse_all
    documents = new_documents

    @pull_parser.read_next
    loop do
      case @pull_parser.kind
      when .stream_end?
        return documents
      when .document_start?
        add_to_documents(documents, parse_document)
      else
        unexpected_event
      end
    end
  end

  # Deserializes a YAML document.
  def parse
    @pull_parser.read_next

    document = new_document

    case @pull_parser.kind
    when .stream_end?
    when .document_start?
      parse_document(document)
    else
      unexpected_event
    end

    cast_document(document)
  end

  private def parse_document
    document = new_document
    parse_document(document)
    cast_document(document)
  end

  private def parse_document(document)
    @pull_parser.read_next
    add_to_document(document, parse_node)
    end_value(document)
    @pull_parser.read_document_end
  end

  protected def parse_node
    tag = @pull_parser.tag
    if tag
      process_tag(tag) do |value|
        return value
      end
    end

    case @pull_parser.kind
    when .scalar?
      parse_scalar
    when .alias?
      parse_alias
    when .sequence_start?
      parse_sequence
    when .mapping_start?
      parse_mapping
    else
      unexpected_event
    end
  end

  protected def parse_scalar
    value = anchor(@pull_parser.anchor, new_scalar)
    end_value(value)
    @pull_parser.read_next
    value
  end

  protected def parse_alias
    value = get_anchor(@pull_parser.anchor.not_nil!)
    end_value(value)
    @pull_parser.read_next
    value
  end

  protected def parse_sequence
    sequence = anchor new_sequence

    parse_sequence(sequence) do
      add_to_sequence(sequence, parse_node)
    end

    sequence
  end

  protected def parse_sequence(sequence, &)
    @pull_parser.read_sequence_start

    until @pull_parser.kind.sequence_end?
      yield
    end

    end_value(sequence)

    @pull_parser.read_next
  end

  protected def parse_mapping
    mapping = anchor new_mapping

    parse_mapping(mapping) do
      add_to_mapping(mapping, parse_node, parse_node)
    end

    mapping
  end

  protected def parse_mapping(mapping, &)
    @pull_parser.read_mapping_start

    until @pull_parser.kind.mapping_end?
      yield
    end

    end_value(mapping)

    @pull_parser.read_next
  end

  # Closes this parser, freeing up resources.
  def close : Nil
    @pull_parser.close
  end

  private def anchor(anchor, value)
    put_anchor(anchor, value) if anchor
    value
  end

  private def anchor(value)
    anchor(@pull_parser.anchor, value)
  end

  private def unexpected_event
    raise "Unexpected event: #{@pull_parser.kind}"
  end

  private def raise(msg)
    @pull_parser.raise(msg)
  end
end
