import 'package:meta/meta.dart'; import 'package:uto_manager/domain/entities/doctrinal_branch.dart'; /// Represents a validated Military Occupational Specialty (MOS) code. /// /// MOS codes follow the U.S. Army standard format: 2 digits + 1 letter /// (e.g., "11B", "68W", "13F"). Aviation warrant officer AOCs use a /// 3-digit + 1-letter format (e.g., "153A"). Both formats are accepted. /// This value object enforces that format and validates against the known /// MOS dictionary. @immutable class DutyMos { /// Creates a [DutyMos] from a raw MOS code string. /// /// Throws [ArgumentError] if the code is not a valid 3-character MOS. factory DutyMos(String code) { final normalized = code.trim().toUpperCase(); if (!_isValidFormat(normalized)) { throw ArgumentError( 'Invalid MOS code format: "$code". ' 'Expected format: 2 digits + 1 letter (e.g., "11B", "68W") ' 'or 3 digits + 1 letter for aviation AOCs (e.g., "153A").', ); } if (!_knownMosCodes.contains(normalized)) { throw ArgumentError( 'Unknown MOS code: "$normalized". ' 'Code is not in the authorized MOS dictionary.', ); } return DutyMos._(normalized); } /// Creates a [DutyMos] without dictionary validation (for testing/migration). factory DutyMos.unvalidated(String code) { final normalized = code.trim().toUpperCase(); if (!_isValidFormat(normalized)) { throw ArgumentError( 'Invalid MOS code format: "$code". ' 'Expected format: 2 digits + 1 letter (e.g., "11B", "68W") ' 'or 3 digits + 1 letter for aviation AOCs (e.g., "153A").', ); } return DutyMos._(normalized); } const DutyMos._(this.code); /// The normalized MOS code string (e.g., "11B", "68W", "13F"). final String code; /// Returns true if [code] matches a valid MOS format: /// - Standard: 2 digits + 1 letter (e.g., "11B", "68W") /// - Aviation AOC: 3 digits + 1 letter (e.g., "153A") static bool _isValidFormat(String code) { // Standard 3-char format: NNx if (code.length == 3) { final digits = code.substring(0, 2); final letter = code[2]; return RegExp(r'^\d{2}$').hasMatch(digits) && RegExp(r'^[A-Z]$').hasMatch(letter); } // Aviation AOC 4-char format: NNNx if (code.length == 4) { final digits = code.substring(0, 3); final letter = code[3]; return RegExp(r'^\d{3}$').hasMatch(digits) && RegExp(r'^[A-Z]$').hasMatch(letter); } return false; } /// Returns true if [code] is a known MOS in the authorized dictionary. static bool isKnown(String code) { return _knownMosCodes.contains(code.trim().toUpperCase()); } /// Returns all known MOS codes as a sorted list. static List get allCodes => _knownMosCodes.toList()..sort(); /// Returns all MOS codes for a given [DoctrinalBranch]. static Set codesForBranch(DoctrinalBranch branch) { return _branchMosMap[branch] ?? {}; } /// The complete set of authorized MOS codes. static const Set _knownMosCodes = { // Infantry & Maneuver (CMF 11 / 15) '11A', '11B', '11C', '15W', '15A', '15T', '15B', '15D', '15G', '153A', // Medical (CMF 68) '68W', '68A', '68D', '68E', '68F', '68G', '68J', '68K', '68P', '68Q', '68R', '68S', '68T', '68X', // Field Artillery (CMF 13) '13A', '13B', '13D', '13F', '13J', '13M', '13P', '13R', '13T', // Signal (CMF 25) '25A', '25B', '25C', '25D', '25E', '25H', '25L', '25M', '25N', '25P', '25Q', '25R', '25S', '25U', '25V', '25W', '25X', '25Z', // Cyber/EW (CMF 17) '17A', '17C', '17E', '17S', // Aviation (CMF 15) '15Z', // Logistics (CMF 91/92) '91A', '91B', '91C', '91D', '91E', '91F', '91G', '91H', '91J', '91L', '91M', '91P', '91S', '91X', '91Z', '92A', '92F', '92G', '92L', '92M', '92R', '92S', '92W', '92Y', '92Z', // Engineer (CMF 12) '12A', '12B', '12C', '12D', '12G', '12H', '12K', '12M', '12N', '12P', '12Q', '12R', '12T', '12W', '12X', '12Y', '12Z', }; /// Maps each [DoctrinalBranch] to its associated MOS codes. static const Map> _branchMosMap = { DoctrinalBranch.infantry: { '11A', '11B', '11C', '15W', '15A', '15T', '153A', }, DoctrinalBranch.medical: { '68W', '68A', '68D', '68E', '68F', '68G', '68J', '68K', '68P', '68Q', '68R', '68S', '68T', '68X', }, DoctrinalBranch.fires: { '13A', '13B', '13D', '13F', '13J', '13M', '13P', '13R', '13T', }, DoctrinalBranch.signal: { '25A', '25B', '25C', '25D', '25E', '25H', '25L', '25M', '25N', '25P', '25Q', '25R', '25S', '25U', '25V', '25W', '25X', '25Z', }, DoctrinalBranch.cyberEw: {'17A', '17C', '17E', '17S'}, DoctrinalBranch.aviation: { '15A', '15B', '15D', '15G', '15T', '15W', '15Z', '153A', }, DoctrinalBranch.logistics: { '91A', '91B', '91C', '91D', '91E', '91F', '91G', '91H', '91J', '91L', '91M', '91P', '91S', '91X', '91Z', '92A', '92F', '92G', '92L', '92M', '92R', '92S', '92W', '92Y', '92Z', }, DoctrinalBranch.engineer: { '12A', '12B', '12C', '12D', '12G', '12H', '12K', '12M', '12N', '12P', '12Q', '12R', '12T', '12W', '12X', '12Y', '12Z', }, }; @override bool operator ==(Object other) => identical(this, other) || (other is DutyMos && other.code == code); @override int get hashCode => code.hashCode; @override String toString() => code; }