package entity

import "time"

// Work — чью работу ждёт запись, стоя на рубеже. От этого зависит предел
// простоя: своя работа мерится одним числом, ожидание чужой операции — другим.
// Граница проходит по исполнителю, а не по рубежу: число на каждый рубеж
// назвало бы разными вещи, различающиеся только им.
type Work int

const (
	// WorkOwn — работу делаем мы сами.
	WorkOwn Work = iota
	// WorkForeign — ждём операцию внешнего сервиса.
	WorkForeign
)

// Stage — объявление рубежа одним местом.
//
// Из этого перечня выводятся все потребители: выбор следующего шага, отбор
// захвата, срок протухания захвата и предел простоя. Перечислять рубежи порознь
// в каждом потребителе нельзя: рубеж, забытый в отборе захвата, не выдаётся ни
// одному воркеру никогда, а пустой прогон по инварианту проекта не пишется в
// журнал и не считается в метрику — запись встала бы без единого следа.
type Stage struct {
	Name string
	// Work — чью работу ждём, стоя на этом рубеже.
	Work Work
	// AcquireTimeout — срок протухания захвата. Едет с рубежом, а не с воркером:
	// воркер не привязан к шагу и не знает заранее, что вытянет.
	AcquireTimeout time.Duration
	// Terminal — рубеж, из которого запись в работу не берут. Такой рубеж не
	// подпадает и под предел простоя: стоять в нём запись будет вечно по
	// построению.
	Terminal bool
}

// Сроки захвата. Каждый не меньше того, что его шаг может занять на самом
// длинном допустимом входе: расчётный потолок записи — шесть часов, и приведение
// такой записи идёт дольше часа по построению.
const (
	normalizeAcquireTimeout = 8 * time.Hour
	submitAcquireTimeout    = 8 * time.Hour
	pollAcquireTimeout      = time.Hour
	finishAcquireTimeout    = time.Hour
)

// stages — цепочка рубежей в порядке прохождения.
var stages = []Stage{
	{Name: StateUploaded, Work: WorkOwn, AcquireTimeout: normalizeAcquireTimeout},
	{Name: StateNormalized, Work: WorkOwn, AcquireTimeout: submitAcquireTimeout},
	{Name: StateSubmitted, Work: WorkForeign, AcquireTimeout: pollAcquireTimeout},
	{Name: StateTranscribed, Work: WorkOwn, AcquireTimeout: finishAcquireTimeout},
	{Name: StateDone, Terminal: true},
}

// WorkingStages — рубежи, с которых запись берут в работу.
func WorkingStages() []Stage {
	out := make([]Stage, 0, len(stages))
	for _, s := range stages {
		if !s.Terminal {
			out = append(out, s)
		}
	}
	return out
}

// StageByName находит рубеж по имени. Второе значение ложно у рубежа, которого
// в цепочке нет: запись с таким рубежом до шага не доходит.
func StageByName(name string) (Stage, bool) {
	for _, s := range stages {
		if s.Name == name {
			return s, true
		}
	}
	return Stage{}, false
}

// ListFilter — состояние записи, по которому её отбирает список приложения.
//
// Состояний три, а не два, и это не педантизм. Остановленная запись не в работе
// и не завершена: при отборе надвое она выпала бы из обеих половин — исчезла бы
// из списка при любом значении отбора, — хотя ради неё человек список и
// открывает.
type ListFilter string

const (
	// ListFilterWorking — запись идёт по конвейеру.
	ListFilterWorking ListFilter = "working"
	// ListFilterHalted — запись остановлена признаком.
	ListFilterHalted ListFilter = "halted"
	// ListFilterDone — запись прошла конвейер.
	ListFilterDone ListFilter = "done"
)

// ParseListFilter узнаёт состояние отбора по его имени. Второе значение ложно у
// имени, которого в перечне нет: такой отбор — негодный ввод, а не пустая
// выборка.
func ParseListFilter(v string) (ListFilter, bool) {
	switch ListFilter(v) {
	case ListFilterWorking, ListFilterHalted, ListFilterDone:
		return ListFilter(v), true
	}
	return "", false
}

// TerminalStages — рубежи, из которых запись в работу не берут.
//
// Выводится из дескриптора наравне с WorkingStages: отбор списка — очередной
// потребитель словаря рубежей, и перечислять их у него строкой запроса нельзя.
// Рубеж, добавленный конвейером, иначе молча поменял бы состав всех трёх
// состояний отбора.
func TerminalStages() []Stage {
	out := make([]Stage, 0, len(stages))
	for _, s := range stages {
		if s.Terminal {
			out = append(out, s)
		}
	}
	return out
}

// StageNames разворачивает рубежи в их имена — для запроса к хранилищу.
func StageNames(list []Stage) []string {
	out := make([]string, 0, len(list))
	for _, s := range list {
		out = append(out, s.Name)
	}
	return out
}

// StuckLimits — пределы простоя, приходящие из настроек.
type StuckLimits struct {
	// Own — предел на своей работе.
	Own time.Duration
	// Foreign — предел на ожидании чужой операции.
	Foreign time.Duration
}

// Limit — предел простоя для этого рубежа. У конечного рубежа предела нет:
// запись стоит в нём вечно по построению.
func (s Stage) Limit(limits StuckLimits) (time.Duration, bool) {
	if s.Terminal {
		return 0, false
	}
	if s.Work == WorkForeign {
		return limits.Foreign, true
	}
	return limits.Own, true
}
