1# frozen_string_literal: true
  2
  3require "delegate"
  4
  5class AdminAction
  6	class NoOp
  7		def to_s
  8			"NoOp"
  9		end
 10	end
 11
 12	module Direction
 13		class InvalidDirection < StandardError; end
 14
 15		def self.for(direction)
 16			{
 17				forward: Forward,
 18				reverse: Reverse,
 19				reforward: Reforward
 20			}.fetch(direction.to_sym) { raise InvalidDirection }
 21		end
 22
 23		class Forward < SimpleDelegator
 24			def with(**kwargs)
 25				self.class.new(__getobj__.with(**kwargs))
 26			end
 27
 28			def perform
 29				check_forward.then { forward }.then { |x| self.class.new(x) }
 30			end
 31
 32			def to_h
 33				super.merge(direction: :forward)
 34			end
 35
 36			def undo
 37				Reverse.new(__getobj__.with(parent_id: id))
 38			end
 39		end
 40
 41		class Reverse < SimpleDelegator
 42			def with(**kwargs)
 43				self.class.new(__getobj__.with(**kwargs))
 44			end
 45
 46			def perform
 47				check_reverse.then { reverse }.then { |x| self.class.new(x) }
 48			end
 49
 50			def to_s
 51				"UNDO(#{parent_id}) #{super}"
 52			end
 53
 54			def to_h
 55				super.merge(direction: :reverse)
 56			end
 57
 58			def undo
 59				Reforward.new(__getobj__)
 60			end
 61		end
 62
 63		class Reforward < Forward
 64			def with(**kwargs)
 65				self.class.new(__getobj__.with(**kwargs))
 66			end
 67
 68			def to_s
 69				"REDO(#{parent_id}) #{super}"
 70			end
 71
 72			def to_h
 73				super.merge(direction: :reforward)
 74			end
 75
 76			def undo
 77				Reverse.new(__getobj__)
 78			end
 79		end
 80	end
 81
 82	def self.for(**kwargs)
 83		Direction::Forward.new(new(**kwargs))
 84	end
 85
 86	def initialize(**kwargs)
 87		@attributes = kwargs
 88	end
 89
 90	def with(**kwargs)
 91		self.class.new(**@attributes.merge(kwargs))
 92	end
 93
 94	def id
 95		@attributes[:id]
 96	end
 97
 98	def parent_id
 99		@attributes[:parent_id]
100	end
101
102	# This tells us if this is the first time we're running this command.
103	# This can be used by actions which take destructive or annoying actions
104	# and don't want to redo those parts on an undo or redo.
105	def first_time?
106		!parent_id
107	end
108
109	def actor_id
110		@attributes[:actor_id]
111	end
112
113	def check_forward
114		EMPromise.resolve(nil)
115	end
116
117	def forward
118		EMPromise.resolve(self)
119	end
120
121	def check_reverse
122		EMPromise.resolve(nil)
123	end
124
125	def reverse
126		EMPromise.resolve(self)
127	end
128
129	def to_h
130		@attributes.merge({
131			class: self.class.to_s.delete_prefix("AdminAction::")
132		}.compact)
133	end
134
135	module Isomorphic
136		def check_reverse
137			to_reverse.check_forward
138		end
139
140		def reverse
141			# We don't want it to return the reversed one
142			# We want it to return itself but with the reverse state
143			to_reverse.forward.then { self }
144		end
145	end
146end