# frozen_string_literal: true

require 'delegate'

module Tablesmith
  # This adjustment to `DelegateClass(Array)` is necessary to allow calling puts
  # on a `Tablesmith::Table` and still get the table output, rather than the
  # default puts output of the underlying `Array`.
  #
  # Explaining why requires breaking some things down.
  #
  # The implementation of `Kernel::puts` has special code for an `Array`. The
  # code inside `rb_io_puts` (in io.c) first checks to see if the object passed
  # to it is a `String`. If not, it then calls `io_puts_ary`, which in turn
  # calls `rb_check_array_type`. If `rb_check_array_type` confirms the passed
  # object is an `Array`, then `io_puts_ary` loops over the elements of the
  # `Array` and passes it to `rb_io_puts`. If the `Array` check fails in the
  # original `rb_io_puts`, `rb_obj_as_string` is used.
  #
  # Early versions of `Tablesmith::Table` subclassed `Array`, but even after
  # changing `Tablesmith::Table` to use any of the `Delegator` options, the code
  # in `rb_check_array_type` still detected `Tablesmith::Table` as an `Array`.
  # How does it do this?
  #
  # `rb_check_array_type` calls:
  #
  #   `return rb_check_convert_type_with_id(ary, T_ARRAY, "Array", idTo_ary);`
  #
  # If a straight up type check fails, then it attempts to convert the object to
  # an `Array` via the `to_ary` method.
  #
  # And wha-lah. We simply need to undefine the `to_ary` method added to
  # `Tablesmith::Table` by `DelegateClass(Array)` and `rb_io_puts` will no
  # longer output `Table` as an `Array` and will use its `to_s` method, the same
  # as `print`.
  def self.delegated_array_class
    DelegateClass(Array).tap do |klass|
      klass.undef_method(:to_ary)
    end
  end
end
