class ViewComponent::Compiler

def compile(raise_errors: false, force: false)

def compile(raise_errors: false, force: false)
  return if compiled? && !force
  return if @component == ViewComponent::Base
  gather_templates
  if self.class.development_mode && @templates.any?(&:requires_compiled_superclass?)
    @component.superclass.compile(raise_errors: raise_errors)
  end
  if template_errors.present?
    raise TemplateError.new(template_errors) if raise_errors
    # this return is load bearing, and prevents the component from being considered "compiled?"
    return false
  end
  if raise_errors
    @component.validate_initialization_parameters!
    @component.validate_collection_parameter!
  end
  define_render_template_for
  @component.register_default_slots
  @component.build_i18n_backend
  CompileCache.register(@component)
end

def compiled?

def compiled?
  CompileCache.compiled?(@component)
end

def define_render_template_for

def define_render_template_for
  @templates.each do |template|
    @redefinition_lock.synchronize do
      template.compile_to_component
    end
  end
  method_body =
    if @templates.one?
      @templates.first.safe_method_name
    elsif (template = @templates.find(&:inline?))
      template.safe_method_name
    else
      branches = []
      @templates.each do |template|
        conditional =
          if template.inline_call?
            "variant&.to_sym == #{template.variant.inspect}"
          else
            [
              template.default_format? ? "(format == #{ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT.inspect} || format.nil?)" : "format == #{template.format.inspect}",
              template.variant.nil? ? "variant.nil?" : "variant&.to_sym == #{template.variant.inspect}"
            ].join(" && ")
          end
        branches << [conditional, template.safe_method_name]
      end
      out = branches.each_with_object(+"") do |(conditional, branch_body), memo|
        memo << "#{(!memo.present?) ? "if" : "elsif"} #{conditional}\n  #{branch_body}\n"
      end
      out << "else\n  #{templates.find { _1.variant.nil? && _1.default_format? }.safe_method_name}\nend"
    end
  @redefinition_lock.synchronize do
    @component.silence_redefinition_of_method(:render_template_for)
    @component.class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def render_template_for(variant = nil, format = nil)
      #{method_body}
    end
    RUBY
  end
end

def gather_templates

def gather_templates
  @templates ||=
    begin
      templates = @component.sidecar_files(
        ActionView::Template.template_handler_extensions
      ).map do |path|
        # Extract format and variant from template filename
        this_format, variant =
          File
            .basename(path)     # "variants_component.html+mini.watch.erb"
            .split(".")[1..-2]  # ["html+mini", "watch"]
            .join(".")          # "html+mini.watch"
            .split("+")         # ["html", "mini.watch"]
            .map(&:to_sym)      # [:html, :"mini.watch"]
        out = Template.new(
          component: @component,
          type: :file,
          path: path,
          lineno: 0,
          extension: path.split(".").last,
          this_format: this_format,
          variant: variant
        )
        # TODO: We should consider inlining the HTML output safety logic into the compiled render_template_for
        # instead of this indirect approach
        @rendered_templates << [out.variant, out.this_format]
        out
      end
      component_instance_methods_on_self = @component.instance_methods(false)
      (
        @component.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - @component.included_modules
      ).flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call(_|$)/) }
        .uniq
        .each do |method_name|
          templates << Template.new(
            component: @component,
            type: :inline_call,
            this_format: ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT,
            variant: method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil,
            method_name: method_name,
            defined_on_self: component_instance_methods_on_self.include?(method_name)
          )
        end
      if @component.inline_template.present?
        templates << Template.new(
          component: @component,
          type: :inline,
          path: @component.inline_template.path,
          lineno: @component.inline_template.lineno,
          source: @component.inline_template.source.dup,
          extension: @component.inline_template.language
        )
      end
      templates
    end
end

def initialize(component)

def initialize(component)
  @component = component
  @redefinition_lock = Mutex.new
  @rendered_templates = Set.new
end

def renders_template_for?(variant, format)

def renders_template_for?(variant, format)
  @rendered_templates.include?([variant, format])
end

def template_errors

def template_errors
  @_template_errors ||= begin
    errors = []
    errors << "Couldn't find a template file or inline render method for #{@component}." if @templates.empty?
    # We currently allow components to have both an inline call method and a template for a variant, with the
    # inline call method overriding the template. We should aim to change this in v4 to instead
    # raise an error.
    @templates.reject(&:inline_call?)
      .map { |template| [template.variant, template.format] }
      .tally
      .select { |_, count| count > 1 }
      .each do |tally|
      variant, this_format = tally.first
      variant_string = " for variant `#{variant}`" if variant.present?
      errors << "More than one #{this_format.upcase} template found#{variant_string} for #{@component}. "
    end
    default_template_types = @templates.each_with_object(Set.new) do |template, memo|
      next if template.variant
      memo << :template_file if !template.inline_call?
      memo << :inline_render if template.inline_call? && template.defined_on_self?
      memo
    end
    if default_template_types.length > 1
      errors <<
        "Template file and inline render method found for #{@component}. " \
        "There can only be a template file or inline render method per component."
    end
    # If a template has inline calls, they can conflict with template files the component may use
    # to render. This attempts to catch and raise that issue before run time. For example,
    # `def render_mobile` would conflict with a sidecar template of `component.html+mobile.erb`
    duplicate_template_file_and_inline_call_variants =
      @templates.reject(&:inline_call?).map(&:variant) &
      @templates.select { _1.inline_call? && _1.defined_on_self? }.map(&:variant)
    unless duplicate_template_file_and_inline_call_variants.empty?
      count = duplicate_template_file_and_inline_call_variants.count
      errors <<
        "Template #{"file".pluralize(count)} and inline render #{"method".pluralize(count)} " \
        "found for #{"variant".pluralize(count)} " \
        "#{duplicate_template_file_and_inline_call_variants.map { |v| "'#{v}'" }.to_sentence} " \
        "in #{@component}. There can only be a template file or inline render method per variant."
    end
    @templates.select(&:variant).each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |template, memo|
      memo[template.normalized_variant_name] << template.variant
      memo
    end.each do |_, variant_names|
      next unless variant_names.length > 1
      errors << "Colliding templates #{variant_names.sort.map { |v| "'#{v}'" }.to_sentence} found in #{@component}."
    end
    errors
  end
end