# frozen_string_literal: true

require_relative 'oppen/mixins'
require_relative 'oppen/printer'
require_relative 'oppen/print_stack'
require_relative 'oppen/scan_stack'
require_relative 'oppen/token'
require_relative 'oppen/version'
require_relative 'wadler/print'

# Oppen.
module Oppen
  extend Mixins

  # Entry point of the pretty printer.
  #
  # @param config [Config]
  # @param space [String, Proc] could be a String or a callable.
  #   If it's a string, spaces will be generated with the the
  #   lambda `->(n){ n * space }`, where `n` is the number of columns
  #   to indent.
  #   If it's a callable, it will receive `n` and it needs to return
  #   a string.
  # @param new_line [String] the delimiter between lines
  # @param out [Object] should have a write and string method
  # @param tokens [Array[Token]] the list of tokens to be printed
  # @param width [Integer] maximum line width desired
  #
  # @return [String] output of the pretty printer
  def self.print(config: Config.oppen, space: ' ',
                 new_line: "\n", out: StringIO.new, tokens: [], width: 80)
    printer = Printer.new width, new_line, config, space, out
    tokens.each do |token|
      printer.print token
    end
    printer.output
  end

  # Config.
  class Config
    # IndentAnchor.
    #
    # ON_BREAK => anchor on break position (as in Oppen's original paper)
    # ON_BEGIN => anchor on begin block position
    module IndentAnchor
      # @return [Integer]
      ON_BREAK = 0
      # @return [Integer]
      ON_BEGIN = 1
    end

    attr_accessor :indent_anchor

    def initialize(indent_anchor: IndentAnchor::ON_BREAK, eager_print: false,
                   trim_trailing_whitespaces: false, upsize_stack: false)
      @indent_anchor = indent_anchor
      @eager_print = eager_print
      @trim_trailing_whitespaces = trim_trailing_whitespaces
      @upsize_stack = upsize_stack
    end

    # Print groups eagerly
    #
    # @example
    #  out = Oppen::Wadler.new (width: 13)
    #  out.group {
    #    out.group {
    #      out.text 'abc'
    #      out.breakable
    #      out.text 'def'
    #    }
    #    out.group {
    #      out.text 'ghi'
    #      out.breakable
    #      out.text 'jkl'
    #    }
    #  }
    #  out.output
    #
    #  # eager_print: false
    #  # =>
    #  # abc
    #  # defghi jkl
    #  #
    #  # eager_print: true
    #  # =>
    #  # abc defghi
    #  # jkl
    #
    # @return [Boolean]
    def eager_print? = @eager_print

    def trim_trailing_whitespaces? = @trim_trailing_whitespaces

    def upsize_stack? = @upsize_stack

    # Default config for Oppen usage
    # @return [Config]
    def self.oppen
      new
    end

    # Default config for Wadler usage
    # @return [Config]
    def self.wadler(eager_print: true, trim_trailing_whitespaces: true, upsize_stack: true)
      new(indent_anchor: IndentAnchor::ON_BEGIN, eager_print:, trim_trailing_whitespaces:, upsize_stack:)
    end
  end

  # @param value [String]
  # @param width [Integer] token width that defaults to value.length
  #
  # @return [Oppen::Token::String] a new String token
  def self.string(value, width: value.length)
    Token::String.new(value, width:)
  end

  # @see Token::Whitespace
  #
  # @return [Oppen::Token::Whitespace] a new Whitespace token.
  def self.whitespace(value)
    Token::Whitespace.new(value, width: value.bytesize)
  end

  # @param str [String]
  # @param line_continuation [String] If a new line is needed display this string before the new line
  # @param offset [Integer]
  # @param width [Integer] token width that defaults to str.length
  #
  # @return [Oppen::Token::Break] a new Break token
  def self.break(str = ' ', line_continuation: '', offset: 0, width: str.length)
    Token::Break.new(str, width:, line_continuation:, offset:)
  end

  # @param line_continuation [String] If a new line is needed display this string before the new line
  # @param offset [Integer]
  #
  # @return [Oppen::Token::LineBreak] a new LineBreak token
  def self.line_break(line_continuation: '', offset: 0)
    Token::LineBreak.new(line_continuation:, offset:)
  end

  # @param offset [Integer]
  #
  # @return [Oppen::Token::Begin] a new consistent Begin token
  def self.begin_consistent(offset: 2)
    Token::Begin.new(break_type: Token::BreakType::CONSISTENT, offset:)
  end

  # @param offset [Integer]
  #
  # @return [Oppen::Token::Begin] a new inconsistent Begin token
  def self.begin_inconsistent(offset: 2)
    Token::Begin.new(break_type: Token::BreakType::INCONSISTENT, offset:)
  end

  # @return [Oppen::Token::End] a new End token
  def self.end
    Token::End.new
  end

  # @return [Oppen::Token::EOF] a new EOF token
  def self.eof
    Token::EOF.new
  end
end