# frozen_string_literal: true

module Grumlin
  class Action
    START_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
    CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
    REGULAR_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze

    ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS

    attr_reader :name, :args, :params, :shortcuts, :next_step, :configuration_steps, :previous_step

    def initialize(name, args: [], params: {}, previous_step: nil, shortcuts: {}, pool: Grumlin.default_pool)
      @name = name.to_sym
      @args = args # TODO: add recursive validation: only json types or Action
      @params = params # TODO: add recursive validation: only json types
      @previous_step = previous_step
      @shortcuts = shortcuts
      @pool = pool
    end

    ALL_STEPS.each do |step|
      define_method step do |*args, **params|
        step(step, *args, **params)
      end
    end

    def step(name, *args, **params)
      Action.new(name, args: args, params: params, previous_step: self, shortcuts: @shortcuts, pool: @pool)
    end

    def configuration_step?
      CONFIGURATION_STEPS.include?(@name)
    end

    def start_step?
      START_STEPS.include?(@name)
    end

    def regular_step?
      REGULAR_STEPS.include?(@name)
    end

    def supported_step?
      ALL_STEPS.include?(@name)
    end

    def shortcut?
      @shortcuts.key?(@name)
    end

    def method_missing(name, *args, **params)
      return step(name, *args, **params) if @shortcuts.key?(name)

      super
    end

    def ==(other)
      self.class == other.class &&
        @name == other.name &&
        @args == other.args &&
        @params == other.params &&
        @previous_step == other.previous_step &&
        @shortcuts == other.shortcuts
    end

    def steps
      @steps ||= Steps.from(self)
    end

    def to_s(**params)
      StepsSerializers::String.new(steps, **params).serialize
    end

    # TODO: add human readable mode
    def inspect
      conf_steps, regular_steps = StepsSerializers::HumanReadableBytecode.new(steps).serialize
      "#{conf_steps.any? ? conf_steps : nil}#{regular_steps}"
    end

    def bytecode(no_return: false)
      StepsSerializers::Bytecode.new(steps, no_return: no_return)
    end

    def next
      to_enum.next
    end

    def hasNext # rubocop:disable Naming/MethodName
      to_enum.peek
      true
    rescue StopIteration
      false
    end

    def to_enum
      @to_enum ||= toList.to_enum
    end

    def toList
      @pool.acquire do |client|
        client.write(bytecode)
      end
    end

    def iterate
      @pool.acquire do |client|
        client.write(bytecode(no_return: true))
      end
    end

    private

    def respond_to_missing?(name, _include_private = false)
      @shortcuts.key?(name)
    end
  end
end
