# frozen_string_literal: true

require "delegate"

class AdminAction
	class NoOp
		def to_s
			"NoOp"
		end
	end

	module Direction
		class InvalidDirection < StandardError; end

		def self.for(direction)
			{
				forward: Forward,
				reverse: Reverse,
				reforward: Reforward
			}.fetch(direction.to_sym) { raise InvalidDirection }
		end

		class Forward < SimpleDelegator
			def with(**kwargs)
				self.class.new(__getobj__.with(**kwargs))
			end

			def perform
				check_forward.then { forward }.then { |x| self.class.new(x) }
			end

			def to_h
				super.merge(direction: :forward)
			end

			def undo
				Reverse.new(__getobj__.with(parent_id: id))
			end
		end

		class Reverse < SimpleDelegator
			def with(**kwargs)
				self.class.new(__getobj__.with(**kwargs))
			end

			def perform
				check_reverse.then { reverse }.then { |x| self.class.new(x) }
			end

			def to_s
				"UNDO(#{parent_id}) #{super}"
			end

			def to_h
				super.merge(direction: :reverse)
			end

			def undo
				Reforward.new(__getobj__)
			end
		end

		class Reforward < Forward
			def with(**kwargs)
				self.class.new(__getobj__.with(**kwargs))
			end

			def to_s
				"REDO(#{parent_id}) #{super}"
			end

			def to_h
				super.merge(direction: :reforward)
			end

			def undo
				Reverse.new(__getobj__)
			end
		end
	end

	def self.for(**kwargs)
		Direction::Forward.new(new(**kwargs))
	end

	def initialize(**kwargs)
		@attributes = kwargs
	end

	def with(**kwargs)
		self.class.new(**@attributes.merge(kwargs))
	end

	def id
		@attributes[:id]
	end

	def parent_id
		@attributes[:parent_id]
	end

	# This tells us if this is the first time we're running this command.
	# This can be used by actions which take destructive or annoying actions
	# and don't want to redo those parts on an undo or redo.
	def first_time?
		!parent_id
	end

	def actor_id
		@attributes[:actor_id]
	end

	def check_forward
		EMPromise.resolve(nil)
	end

	def forward
		EMPromise.resolve(self)
	end

	def check_reverse
		EMPromise.resolve(nil)
	end

	def reverse
		EMPromise.resolve(self)
	end

	def to_h
		@attributes.merge({
			class: self.class.to_s.delete_prefix("AdminAction::")
		}.compact)
	end

	module Isomorphic
		def check_reverse
			to_reverse.check_forward
		end

		def reverse
			# We don't want it to return the reversed one
			# We want it to return itself but with the reverse state
			to_reverse.forward.then { self }
		end
	end
end
