class ViewComponent::Compiler

def call_method_name(variant)

def call_method_name(variant)
  if variant.present? && variants.include?(variant)
    "call_#{variant}"
  else
    "call"
  end
end

def compile(raise_errors: false, force: false)

def compile(raise_errors: false, force: false)
  return if compiled? && !force
  return if component_class == ViewComponent::Base
  component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
  with_write_lock do
    CompileCache.invalidate_class!(component_class)
    subclass_instance_methods = component_class.instance_methods(false)
    if subclass_instance_methods.include?(:with_content) && raise_errors
      raise ViewComponent::ComponentError.new(
        "#{component_class} implements a reserved method, `#with_content`.\n\n" \
        "To fix this issue, change the name of the method."
      )
    end
    if template_errors.present?
      raise ViewComponent::TemplateError.new(template_errors) if raise_errors
      return false
    end
    if subclass_instance_methods.include?(:before_render_check)
      ViewComponent::Deprecation.warn(
        "`#before_render_check` will be removed in v3.0.0.\n\n" \
        "To fix this issue, use `#before_render` instead."
      )
    end
    if raise_errors
      component_class.validate_initialization_parameters!
      component_class.validate_collection_parameter!
    end
    templates.each do |template|
      # Remove existing compiled template methods,
      # as Ruby warns when redefining a method.
      method_name = call_method_name(template[:variant])
      if component_class.instance_methods.include?(method_name.to_sym)
        component_class.send(:undef_method, method_name.to_sym)
      end
      # rubocop:disable Style/EvalWithLocation
      component_class.class_eval <<-RUBY, template[:path], 0
      def #{method_name}
        #{compiled_template(template[:path])}
      end
      RUBY
      # rubocop:enable Style/EvalWithLocation
    end
    define_render_template_for
    component_class.build_i18n_backend
    component_class._after_compile
    CompileCache.register(component_class)
  end
end

def compiled?

def compiled?
  CompileCache.compiled?(component_class)
end

def compiled_template(file_path)

def compiled_template(file_path)
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
  template = File.read(file_path)
  template.rstrip! if component_class.strip_trailing_whitespace?
  if handler.method(:call).parameters.length > 1
    handler.call(component_class, template)
  else
    handler.call(
      OpenStruct.new(
        source: template,
        identifier: component_class.identifier,
        type: component_class.type
      )
    )
  end
end

def define_render_template_for

def define_render_template_for
  if component_class.instance_methods.include?(:render_template_for)
    component_class.send(:undef_method, :render_template_for)
  end
  variant_elsifs = variants.compact.uniq.map do |variant|
    "elsif variant.to_sym == :#{variant}\n    #{call_method_name(variant)}"
  end.join("\n")
  body = <<-RUBY
    if variant.nil?
      call
    #{variant_elsifs}
    else
      call
    end
  RUBY
  if development?
    component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def render_template_for(variant = nil)
      self.class.compiler.with_read_lock do
        #{body}
      end
    end
    RUBY
  else
    component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def render_template_for(variant = nil)
      #{body}
    end
    RUBY
  end
end

def development?

def development?
  self.class.mode == DEVELOPMENT_MODE
end

def initialize(component_class)

def initialize(component_class)
  @component_class = component_class
  @__vc_compiler_lock = Concurrent::ReadWriteLock.new
end

def inline_calls

def inline_calls
  @inline_calls ||=
    begin
      # Fetch only ViewComponent ancestor classes to limit the scope of
      # finding inline calls
      view_component_ancestors =
        (
          component_class.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } -
          component_class.included_modules
        )
      view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call/) }.uniq
    end
end

def inline_calls_defined_on_self

def inline_calls_defined_on_self
  @inline_calls_defined_on_self ||= component_class.instance_methods(false).grep(/^call/)
end

def should_compile_superclass?

def should_compile_superclass?
  development? &&
    templates.empty? &&
    !(
      component_class.instance_methods(false).include?(:call) ||
        component_class.private_instance_methods(false).include?(:call)
    )
end

def template_errors

def template_errors
  @__vc_template_errors ||=
    begin
      errors = []
      if (templates + inline_calls).empty?
        errors << "Couldn't find a template file or inline render method for #{component_class}."
      end
      if templates.count { |template| template[:variant].nil? } > 1
        errors <<
          "More than one template found for #{component_class}. " \
          "There can only be one default template file per component."
      end
      invalid_variants =
        templates
          .group_by { |template| template[:variant] }
          .map { |variant, grouped| variant if grouped.length > 1 }
          .compact
          .sort
      unless invalid_variants.empty?
        errors <<
          "More than one template found for #{"variant".pluralize(invalid_variants.count)} " \
          "#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \
          "There can only be one template file per variant."
      end
      if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call)
        errors <<
          "Template file and inline render method found for #{component_class}. " \
          "There can only be a template file or inline render method per component."
      end
      duplicate_template_file_and_inline_variant_calls =
        templates.pluck(:variant) & variants_from_inline_calls(inline_calls_defined_on_self)
      unless duplicate_template_file_and_inline_variant_calls.empty?
        count = duplicate_template_file_and_inline_variant_calls.count
        errors <<
          "Template #{"file".pluralize(count)} and inline render #{"method".pluralize(count)} " \
          "found for #{"variant".pluralize(count)} " \
          "#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \
          "in #{component_class}. " \
          "There can only be a template file or inline render method per variant."
      end
      errors
    end
end

def templates

def templates
  @templates ||=
    begin
      extensions = ActionView::Template.template_handler_extensions
      component_class._sidecar_files(extensions).each_with_object([]) do |path, memo|
        pieces = File.basename(path).split(".")
        memo << {
          path: path,
          variant: pieces.second.split("+").second&.to_sym,
          handler: pieces.last
        }
      end
    end
end

def variants

def variants
  @__vc_variants = (
    templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls)
  ).compact.uniq
end

def variants_from_inline_calls(calls)

def variants_from_inline_calls(calls)
  calls.reject { |call| call == :call }.map do |variant_call|
    variant_call.to_s.sub("call_", "").to_sym
  end
end

def with_read_lock(&block)

def with_read_lock(&block)
  __vc_compiler_lock.with_read_lock(&block)
end

def with_write_lock(&block)

def with_write_lock(&block)
  if development?
    __vc_compiler_lock.with_write_lock(&block)
  else
    block.call
  end
end