import { computed, toValue, type ComputedRef, type Ref } from 'vue' import type { GroupItem } from '../types' export type GroupAssignmentZone = 'zone1' | 'zone2' export type GroupAssignmentSource = | T | Ref | ComputedRef | (() => T) export interface GroupAssignmentState { group1: string[] group2: string[] } export interface UseGroupAssignmentOptions { groups: GroupAssignmentSource group1: GroupAssignmentSource group2: GroupAssignmentSource label1?: GroupAssignmentSource label2?: GroupAssignmentSource minPerGroup?: GroupAssignmentSource } export interface UseGroupAssignmentReturn { unassignedGroups: ComputedRef zone1Groups: ComputedRef zone2Groups: ComputedRef zone1Count: ComputedRef zone2Count: ComputedRef isValid: ComputedRef validationMessage: ComputedRef getZoneForGroup: (groupName: string) => GroupAssignmentZone | null assignToZone: (groupName: string, zone: GroupAssignmentZone) => GroupAssignmentState removeFromZone: (groupName: string, zone: GroupAssignmentZone) => GroupAssignmentState clearAll: () => GroupAssignmentState } /** * Shared two-zone group assignment model for control/treatment style workflows. * * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function useGroupAssignment(options: UseGroupAssignmentOptions): UseGroupAssignmentReturn { const groups = computed(() => [...toValue(options.groups)]) const group1 = computed(() => [...toValue(options.group1)]) const group2 = computed(() => [...toValue(options.group2)]) const label1 = computed(() => toValue(options.label1) || 'Control') const label2 = computed(() => toValue(options.label2) || 'Treatment') const minPerGroup = computed(() => normalizeMinimum(toValue(options.minPerGroup))) const group1Set = computed(() => new Set(group1.value)) const group2Set = computed(() => new Set(group2.value)) const unassignedGroups = computed(() => groups.value.filter(group => !group1Set.value.has(group.name) && !group2Set.value.has(group.name)) ) const zone1Groups = computed(() => groups.value.filter(group => group1Set.value.has(group.name)) ) const zone2Groups = computed(() => groups.value.filter(group => group2Set.value.has(group.name)) ) const zone1Count = computed(() => zone1Groups.value.reduce((sum, group) => sum + group.count, 0) ) const zone2Count = computed(() => zone2Groups.value.reduce((sum, group) => sum + group.count, 0) ) const isValid = computed(() => group1.value.length >= minPerGroup.value && group2.value.length >= minPerGroup.value ) const validationMessage = computed(() => { if (isValid.value) return null const missing1 = Math.max(0, minPerGroup.value - group1.value.length) const missing2 = Math.max(0, minPerGroup.value - group2.value.length) const parts: string[] = [] if (missing1 > 0) parts.push(`${missing1} more to ${label1.value}`) if (missing2 > 0) parts.push(`${missing2} more to ${label2.value}`) return `Add ${parts.join(' and ')}` }) function getZoneForGroup(groupName: string): GroupAssignmentZone | null { if (group1Set.value.has(groupName)) return 'zone1' if (group2Set.value.has(groupName)) return 'zone2' return null } function assignToZone(groupName: string, zone: GroupAssignmentZone): GroupAssignmentState { const next = removeFromBoth(groupName) if (zone === 'zone1') { return { group1: [...next.group1, groupName], group2: next.group2, } } return { group1: next.group1, group2: [...next.group2, groupName], } } function removeFromZone(groupName: string, zone: GroupAssignmentZone): GroupAssignmentState { if (zone === 'zone1') { return { group1: group1.value.filter(name => name !== groupName), group2: group2.value, } } return { group1: group1.value, group2: group2.value.filter(name => name !== groupName), } } function clearAll(): GroupAssignmentState { return { group1: [], group2: [] } } function removeFromBoth(groupName: string): GroupAssignmentState { return { group1: group1.value.filter(name => name !== groupName), group2: group2.value.filter(name => name !== groupName), } } return { unassignedGroups, zone1Groups, zone2Groups, zone1Count, zone2Count, isValid, validationMessage, getZoneForGroup, assignToZone, removeFromZone, clearAll, } } function normalizeMinimum(value: number | null | undefined): number { if (value === null || value === undefined || !Number.isFinite(value)) return 1 return Math.max(0, Math.floor(value)) }