# frozen_string_literal: true
require "value_semantics/monkey_patched"
require_relative "tts_template"
require_relative "low_balance"
class CallAttempt
def self.for(customer:, usage:, **kwargs)
credit = [customer.minute_limit.to_d - usage, 0].max + customer.balance
@kinds.each do |kind|
ca = kind.call(
customer: customer, usage: usage, credit: credit,
**kwargs.merge(limits(customer, usage, credit, **kwargs))
)
return ca if ca
end
raise "No CallAttempt matched"
end
def self.limits(customer, usage, credit, rate:, **)
return {} unless customer && usage && rate
can_use = customer.minute_limit.to_d + customer.monthly_overage_limit
{
limit_remaining: ([can_use - usage, 0].max / rate).to_i,
max_minutes: (credit / rate).to_i
}
end
def self.register(&maybe_mk)
@kinds ||= []
@kinds << maybe_mk
end
value_semantics do
customer_id String
from String
to(/\A\+\d+\Z/)
call_id String
direction Either(:inbound, :outbound)
limit_remaining Integer
max_minutes Integer
end
def to_render
["#{direction}/connect", { locals: to_h }]
end
def to_s
"Allowed(max_minutes: #{max_minutes}, limit_remaining: #{limit_remaining})"
end
def create_call(fwd, *args, &block)
fwd.create_call(*args, &block)
end
def as_json(*)
{
from: from,
to: to,
customer_id: customer_id,
limit_remaining: limit_remaining,
max_minutes: max_minutes
}
end
def to_json(*args)
as_json.to_json(*args)
end
class Expired
CallAttempt.register do |customer:, direction:, **|
new(direction: direction) if customer.plan_name && !customer.active?
end
value_semantics do
direction Either(:inbound, :outbound)
end
def view
"#{direction}/expired"
end
def tts
TTSTemplate.new(view).tts(self)
end
def to_render
[view]
end
def create_call(*); end
def as_json(*)
{ tts: tts }
end
def to_json(*args)
as_json.to_json(*args)
end
end
class Unsupported
CallAttempt.register do |supported:, direction:, **|
new(direction: direction) unless supported
end
value_semantics do
direction Either(:inbound, :outbound)
end
def view
"#{direction}/unsupported"
end
def tts
TTSTemplate.new(view).tts(self)
end
def to_render
[view]
end
def to_s
"Unsupported"
end
def create_call(*); end
def as_json(*)
{ tts: tts }
end
def to_json(*args)
as_json.to_json(*args)
end
end
class NoBalance
CallAttempt.register do |credit:, rate:, **kwargs|
self.for(rate: rate, **kwargs) if credit < rate * 10
end
def self.for(customer:, direction:, low_balance: LowBalance, **kwargs)
low_balance.for(customer).then(&:notify!).then do |amount|
if amount&.positive?
CallAttempt.for(
customer: customer.with_balance(customer.balance + amount),
**kwargs.merge(direction: direction)
)
else
NoBalance.new(balance: customer.balance, direction: direction)
end
end
end
value_semantics do
balance Numeric
direction Either(:inbound, :outbound)
end
def view
"#{direction}/no_balance"
end
def tts
TTSTemplate.new(view).tts(self)
end
def to_render
[view, { locals: to_h }]
end
def to_s
"NoBalance"
end
def create_call(*); end
def as_json(*)
{ tts: tts }
end
def to_json(*args)
as_json.to_json(*args)
end
end
class AtLimit
value_semantics do
customer_id String
from String
to(/\A\+\d+\Z/)
call_id String
direction Either(:inbound, :outbound)
limit_remaining Integer
max_minutes Integer
end
CallAttempt.register do |digits: nil, limit_remaining:, customer:, **kwargs|
if digits != "1" && limit_remaining < 10
new(
**kwargs
.merge(
limit_remaining: limit_remaining,
customer_id: customer.customer_id
).slice(*value_semantics.attributes.map(&:name))
)
end
end
def view
"#{direction}/at_limit"
end
def tts
TTSTemplate.new(view).tts(self)
end
def to_render
[view, { locals: to_h }]
end
def to_s
"AtLimit(max_minutes: #{max_minutes}, "\
"limit_remaining: #{limit_remaining})"
end
def create_call(fwd, *args, &block)
fwd.create_call(*args, &block)
end
def as_json(*)
{
tts: tts,
from: from,
to: to,
customer_id: customer_id,
limit_remaining: limit_remaining,
max_minutes: max_minutes
}
end
def to_json(*args)
as_json.to_json(*args)
end
end
register do |customer:, **kwargs|
new(
**kwargs
.merge(customer_id: customer.customer_id)
.slice(*value_semantics.attributes.map(&:name))
)
end
end