using System.Text.RegularExpressions; using Nalu.Web.Models; namespace Nalu.Web.Services; public enum DeterministicOutcome { Unresolved, Rejected, Accepted, ConstraintFailed } public record DeterministicResult { public DeterministicOutcome Outcome { get; init; } public string? ExtractedValue { get; init; } public string? Reasoning { get; init; } } public class DeterministicLayer { public DeterministicResult Evaluate(ValidatorDefinition validator, string userInput, string language = "pt-BR") { var normalized = userInput.Trim().ToLowerInvariant().TrimEnd('.', '!', '?', ',', ';'); // Use localized stop words when available, fall back to flat set var stopWords = validator.LocalizedStopWords.TryGetValue(language, out var localized) ? localized : validator.StopWords; if (stopWords.Contains(normalized)) { return new DeterministicResult { Outcome = DeterministicOutcome.Rejected, Reasoning = "Usuário respondeu com saudação ou palavra de parada" }; } // Reject patterns foreach (var pattern in validator.RejectPatterns) { try { if (Regex.IsMatch(normalized, pattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(100))) { return new DeterministicResult { Outcome = DeterministicOutcome.Rejected, Reasoning = "Resposta corresponde a padrão de rejeição" }; } } catch (RegexMatchTimeoutException) { /* skip on timeout */ } } // Accept patterns — capture group 1 is the extracted value. // Matched case-insensitively against the original (trimmed) input. var original = userInput.Trim().TrimEnd('.', '!', '?', ',', ';'); foreach (var pattern in validator.AcceptPatterns) { try { var m = Regex.Match(original, pattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(100)); if (!m.Success) continue; var extracted = m.Groups.Count > 1 && m.Groups[1].Success ? m.Groups[1].Value.Trim() : original; var violation = CheckConstraints(validator.Constraints, extracted); if (violation is not null) { return new DeterministicResult { Outcome = DeterministicOutcome.ConstraintFailed, ExtractedValue = extracted, Reasoning = violation }; } return new DeterministicResult { Outcome = DeterministicOutcome.Accepted, ExtractedValue = extracted, Reasoning = "Padrão de aceitação encontrado" }; } catch (RegexMatchTimeoutException) { /* skip on timeout */ } } return new DeterministicResult { Outcome = DeterministicOutcome.Unresolved }; } private static string? CheckConstraints(Dictionary constraints, string value) { if (constraints.TryGetValue("min_length", out var minStr) && int.TryParse(minStr, out var min)) { if (value.Length < min) return $"Valor muito curto (mínimo {min} caracteres)"; } if (constraints.TryGetValue("max_length", out var maxStr) && int.TryParse(maxStr, out var max)) { if (value.Length > max) return $"Valor muito longo (máximo {max} caracteres)"; } if (constraints.TryGetValue("max_digits", out var maxDigStr) && int.TryParse(maxDigStr, out var maxDig)) { var digitCount = value.Count(char.IsDigit); if (digitCount > maxDig) return $"Número de dígitos excede o máximo permitido ({maxDig})"; } if (constraints.TryGetValue("must_have_alpha", out var alphaStr) && bool.TryParse(alphaStr, out var mustHaveAlpha) && mustHaveAlpha) { if (!value.Any(char.IsLetter)) return "Valor deve conter letras"; } if (constraints.TryGetValue("must_have_space", out var spaceStr) && bool.TryParse(spaceStr, out var mustHaveSpace) && mustHaveSpace) { if (!value.Contains(' ')) return "Valor deve conter ao menos duas palavras"; } return null; } }