# This script parses the comments in the Ruby C extension files and translates them into Ruby
# stubs. It looks for an instance of `ruby-doc:` and takes the following string as the object
# under documentation (a class, instance method, or class method).
# The intention of generating these files is for use by YARD to auto-generate documentation
# here: https://www.rubydoc.info/gems/google-protobuf/
#
# Because these comments are essentially "pretending" to be Ruby, we had to add one additional
# YARD tag, `@paramdefault`, which is not a standard YARD tag. This tag is used to specify default
# values for parameters.

require 'erb'

files = Dir.glob('ext/**/*.c')
classes = {}
MethodDefn = Struct.new(:name, :body, keyword_init: true) do
  def params
    params = body.scan(/@param (\S+) /).map { |p| p[0]}
    defaults = body.scan(/@paramdefault (\S+) (\S+)/).map { |p| [p[0], p[1]]}.to_h
    params = params.map { |p| p == 'kwargs' ? '**kwargs' : p }
    params = params.map { |p| defaults[p] ? "#{p}=#{defaults[p]}" : p }
    if body.include?('@yield')
      params.push('&block')
    end
    params
  end
end

Defn = Struct.new(:name, :instance_meths, :class_meths, :body) do
  def initialize(*args, **kwargs)
    super
    self.instance_meths ||= []
    self.class_meths ||= []
  end
end

files.each do |file|
  defs = File.read(file).scan(/ruby-doc:(.*?)\n(.*?)\*\//m)
  defs.each do |definition|
    name = definition[0].strip
    body = definition[1].strip.
      gsub(/^\s*\*/, '').
      gsub(/\n/m, "\n  # ").strip
    if name.include?('.')
      klass, method_name = name.split('.')
      method = MethodDefn.new(name: method_name, body: body)
      classes[klass] ||= Defn.new(name: klass)
      classes[klass].class_meths.push(method)
    elsif name.include?('#')
      klass, method_name = name.split('#')
      method = MethodDefn.new(name: method_name, body: body)
      classes[klass] ||= Defn.new(name: klass)
      classes[klass].instance_meths.push(method)
    else
      classes[name] = Defn.new(name: name, body: body)
    end
  end
end

# copied from ActiveSupport::Inflector
def underscore(str)
  regex = /(?:(?<=([A-Za-z\d]))|\b)((?=a)b)(?=\b|[^a-z])/
  str.gsub(regex) { "#{$1 && '_' }#{$2.downcase}" }.
    gsub(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }.
    downcase
end

class_erb = <<-ERB
# This file was generated by generate_stubs.rb
# Do not edit this file directly.

<%- if defn.body -%>
<%= defn.body.gsub('  #', '#').gsub('#  ', '# ') %>
<%- end -%>
class Google::Protobuf::<%= name %>
<%- defn.instance_meths.each do |meth| -%>

  <%= meth.body.gsub('#  ', '# ') %>
  def <%= meth.name %><% if meth.params.any? %>(<%= meth.params.join(', ') %>)<% end %>; end
<%- end -%>
<%- defn.class_meths.each do |meth| -%>

  <%= meth.body.gsub('#  ', '# ') %>
  def self.<%= meth.name %><% if meth.params.any? %>(<%= meth.params.join(', ') %>)<% end %>; end
<%- end -%>

end # class Google::Protobuf::<%= name %>
ERB

classes.each do |name, defn|
  file = File.join('lib/stubs', "#{underscore(name)}.rb")
  File.open(file, 'w') do |f|
    result = ERB.new(class_erb, trim_mode: '-').result(binding)
    f.puts result
  end
end
