import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts" import { controllerFromEitherType } from "#Source/result/index.ts" import type { BaseSelectorOptions, GetItemResult } from "./base-selector.ts" import { BaseSelector } from "./base-selector.ts" interface ItemStates { item: Target downCount: number } export interface DownCountSelectorOptions extends BaseSelectorOptions, LoggerFriendlyOptions { /** * @default 1 */ maxDownCount?: number | undefined /** * @default true */ resetAllWhenNoAvailable?: boolean | undefined } export class DownCountSelector extends BaseSelector implements LoggerFriendly { private maxDownCount: number private resetAllWhenNoAvailable: boolean private itemStatesMap: Map> constructor(options: DownCountSelectorOptions) { super(options) this.logger.setDefaultName("DownCountSelector") this.maxDownCount = options.maxDownCount ?? 1 this.resetAllWhenNoAvailable = options.resetAllWhenNoAvailable ?? true this.itemStatesMap = new Map() options.itemList.forEach((item) => { this.itemStatesMap.set(item, { item, downCount: 0, }) }) } private markItemAvailable(item: Target): void { const states = this.itemStatesMap.get(item) if (states === undefined) { throw new Error(`Item not found: ${String(item)}`) } states.downCount = 0 } private markItemUnavailable(item: Target): void { const states = this.itemStatesMap.get(item) if (states === undefined) { throw new Error(`Item not found: ${String(item)}`) } states.downCount = states.downCount + 1 } private resetAllItem(): void { this.itemStatesMap.forEach((states) => { states.downCount = 0 }) } private getAvailableItemList(): Target[] { const availableItemList: Target[] = [] this.itemStatesMap.forEach((states) => { if (states.downCount < this.maxDownCount) { availableItemList.push(states.item) } }) return availableItemList } async getItem(): Promise> { const controller = controllerFromEitherType>() if (this.itemStatesMap.size === 0) { return await controller.returnLeft({ code: "NO_CHOICE", }) } const availableItemList = this.getAvailableItemList() if (availableItemList.length === 0) { if (this.resetAllWhenNoAvailable === false) { return await controller.returnLeft({ code: "NO_AVAILABLE", }) } else { this.resetAllItem() return await this.getItem() } } availableItemList.sort((a, b) => { const aStates = this.itemStatesMap.get(a)! const bStates = this.itemStatesMap.get(b)! if (aStates.downCount === bStates.downCount) { return 0 } return aStates.downCount - bStates.downCount }) const item = availableItemList[0]! return await controller.returnRight({ item, markAvailable: () => { this.markItemAvailable(item) }, markUnavailable: () => { this.markItemUnavailable(item) }, }) } }