# frozen_string_literal: true

class PGTrunk::Operation
  # @private
  # Helpers to build ruby snippet from the operation definition
  module RubyHelpers
    extend ActiveSupport::Concern

    class_methods do
      # The name of the builder
      # @return [Symbol]
      def ruby_name
        @ruby_name ||= name.split("::").last.underscore.to_sym
      end

      # The name of the inverted builder
      # @return [Symbol]
      def ruby_iname
        "invert_#{ruby_name}".to_sym
      end

      # Get/set positional params of the ruby method
      #
      # @example Provide a method `add_check_constraint(table = nil, **opts)`
      #   class AddCheckConstraint < PGTrunk::Operation
      #     ruby_params :table
      #     # ...
      #   end
      #
      def ruby_params(*params)
        @ruby_params = params.compact.map(&:to_sym) if params.any?
        @ruby_params ||= []
      end

      # Gets or sets the block building a ruby snippet
      #
      # @yieldparam [PGTrunk::Operation::RubyBuilder]
      #
      # @example
      #   ruby_snippet do |s|
      #     s.ruby_param(comment: comment) if comment.present?
      #     values.each { |v| s.ruby_line(:value, v) }
      #   end
      #
      #   # will build something like
      #
      #   do_something "foo.bar", comment: "comment" do |s|
      #     s.value "baz"
      #     s.value "qux"
      #   end
      def ruby_snippet(&block)
        @ruby_snippet ||= block
      end

      # Build the operation from arguments sent to Ruby method
      def from_ruby(*args, &block)
        options = args.last.is_a?(Hash) ? args.pop.symbolize_keys : {}
        params = ruby_params.zip(args).to_h
        new(**params, **options, &block)
      end

      private

      def inherited(klass)
        # Use params from a parent class by default (can be overloaded).
        klass.instance_variable_set(:@ruby_params, ruby_params)
        klass.instance_variable_set(:@ruby_snippet, ruby_snippet)
        super
      end
    end

    # @private
    # Ruby snippet to dump the creator
    # @return [String]
    def to_ruby
      builder = RubyBuilder.new(self.class.ruby_name)
      instance_exec(builder, &self.class.ruby_snippet)
      builder.build.rstrip
    end

    # List of attributes assigned that are assigned
    # via Ruby method parameters.
    #
    # We can use it to announce the operation to $stdout
    # like `create_foreign_key("users", "roles")`.
    def to_a
      to_h.values_at(*self.class.ruby_params)
    end

    def to_opts
      to_h.except(*self.class.ruby_params)
    end

    # @param [IO] stream
    def dump(stream)
      to_ruby&.rstrip&.lines&.each { |line| stream.print(line.indent(2)) }
    end
  end
end
