1# frozen_string_literal: true
  2
  3require "value_semantics/monkey_patched"
  4require_relative "../admin_action"
  5require_relative "../form_to_h"
  6
  7class AdminAction
  8	class AddInvites < AdminAction
  9		class Command
 10			using FormToH
 11
 12			def self.for(target_customer, reply:)
 13				EMPromise.resolve(
 14					new(customer_id: target_customer.customer_id)
 15				).then { |x|
 16					reply.call(x.form).then(&x.method(:create))
 17				}
 18			end
 19
 20			def initialize(**bag)
 21				@bag = bag
 22			end
 23
 24			def form
 25				FormTemplate.render("admin_add_invites")
 26			end
 27
 28			def create(result)
 29				AdminAction::AddInvites.for(
 30					**@bag,
 31					**result.form.to_h
 32						.reject { |_k, v| v == "nil" }.transform_keys(&:to_sym)
 33				)
 34			end
 35		end
 36
 37		CodesTaken = Struct.new(:codes) do
 38			def to_s
 39				"One of these tokens already exists: #{codes.join(', ')}"
 40			end
 41		end
 42
 43		CodesClaimed = Struct.new(:codes) do
 44			def to_s
 45				"One of these tokens is already claimed: #{codes.join(', ')}"
 46			end
 47		end
 48
 49		class MustHaveCodes
 50			def to_s
 51				"Action must have list of codes to reverse"
 52			end
 53		end
 54
 55		def customer_id
 56			@attributes[:customer_id]
 57		end
 58
 59		def to_add
 60			@attributes[:to_add].to_i
 61		end
 62
 63		def check_forward
 64			EMPromise.resolve(nil)
 65				.then { check_noop }
 66				.then {
 67					next nil if chosen_invites.empty?
 68
 69					InvitesRepo.new.any_existing?(chosen_invites).then { |taken|
 70						EMPromise.reject(CodesTaken.new(chosen_invites)) if taken
 71					}
 72				}
 73		end
 74
 75		def forward
 76			if chosen_invites.empty?
 77				InvitesRepo.new.create_n_codes(customer_id, to_add).then { |codes|
 78					with(invites: codes.join("\n"))
 79				}
 80			else
 81				InvitesRepo.new.create_codes(customer_id, chosen_invites).then { self }
 82			end
 83		end
 84
 85		def check_reverse
 86			return EMPromise.reject(MustHaveCodes.new) if chosen_invites.empty?
 87
 88			InvitesRepo.new.any_claimed?(chosen_invites).then { |claimed|
 89				EMPromise.reject(CodesClaimed.new(chosen_invites)) if claimed
 90			}
 91		end
 92
 93		def reverse
 94			InvitesRepo.new.delete_codes(chosen_invites).then { self }
 95		end
 96
 97		def to_s
 98			"add_invites(#{customer_id}): #{chosen_invites.join(', ')}"
 99		end
100
101	protected
102
103		def check_noop
104			EMPromise.reject(NoOp.new) if to_add.zero?
105		end
106
107		def check_too_many
108			max = split_invites.length
109			EMPromise.reject(TooMany.new(to_add, max)) if to_add > max
110		end
111
112		def chosen_invites
113			@attributes[:invites]&.split("\n") || []
114		end
115	end
116end