using OperationControl.Mapas.Models; namespace OperationControl.Mapas.Services { public class MapaSelectionService { /// /// Alterna o estado de seleção de uma rua. /// /// Retorna true se a rua ficou selecionada. /// Retorna false se a rua ficou desmarcada. /// public bool AlternarRua( MapaDocumento documento, MapaSelecaoOperacao selecao, string ruaId) { ValidarArgumentos( documento, selecao ); string id = NormalizarId( ruaId ); GarantirRuaExistente( documento, id ); if (selecao.RuasMutaveis.Contains( id, StringComparer.OrdinalIgnoreCase)) { RemoverRua( documento, selecao, id ); return false; } SelecionarRua( documento, selecao, id ); return true; } /// /// Seleciona uma rua e a coloca no final da ordem atual. /// /// Se já estiver selecionada, nada é alterado. /// public bool SelecionarRua( MapaDocumento documento, MapaSelecaoOperacao selecao, string ruaId) { ValidarArgumentos( documento, selecao ); string id = NormalizarId( ruaId ); GarantirRuaExistente( documento, id ); if (selecao.RuasMutaveis.Contains( id, StringComparer.OrdinalIgnoreCase)) { return false; } selecao.RuasMutaveis.Add( id ); return true; } /// /// Remove uma rua da seleção. /// /// Como a ordem é representada pela própria posição /// na lista, nenhuma renumeração manual é necessária. /// public bool RemoverRua( MapaDocumento documento, MapaSelecaoOperacao selecao, string ruaId) { ValidarArgumentos( documento, selecao ); string id = NormalizarId( ruaId ); int indice = selecao.RuasMutaveis.FindIndex( x => string.Equals( x, id, StringComparison.OrdinalIgnoreCase ) ); if (indice < 0) { return false; } selecao.RuasMutaveis.RemoveAt( indice ); return true; } /// /// Substitui a seleção atual por uma lista já ordenada. /// /// Útil para restaurar uma operação previamente /// parametrizada ou recebida de outro componente. /// /// IDs inválidos, vazios ou duplicados não são aceitos. /// public void DefinirSelecao( MapaDocumento documento, MapaSelecaoOperacao selecao, IEnumerable? ruasOrdenadas) { ValidarArgumentos( documento, selecao ); var novaSelecao = new List(); var idsUsados = new HashSet( StringComparer.OrdinalIgnoreCase ); foreach ( string? ruaId in ruasOrdenadas ?? Enumerable.Empty()) { string id = NormalizarId( ruaId ); GarantirRuaExistente( documento, id ); if (!idsUsados.Add(id)) { throw new InvalidOperationException( $"A seleção contém o ID de rua '{id}' repetido." ); } novaSelecao.Add( id ); } /* * Só altera o objeto oficial depois que a nova * seleção inteira foi validada. * * Assim a operação é transacional: * se houver um ID inválido no meio da lista, * a seleção anterior permanece intacta. */ selecao.RuasMutaveis.Clear(); selecao.RuasMutaveis.AddRange( novaSelecao ); } public void Limpar( MapaSelecaoOperacao selecao) { if (selecao == null) { throw new ArgumentNullException( nameof(selecao) ); } selecao.RuasMutaveis.Clear(); } public bool EstaSelecionada( MapaSelecaoOperacao selecao, string ruaId) { if (selecao == null) { throw new ArgumentNullException( nameof(selecao) ); } string id = NormalizarId( ruaId ); return selecao.RuasMutaveis.Contains( id, StringComparer.OrdinalIgnoreCase ); } /// /// Retorna a ordem 1-based da rua. /// /// Exemplo: /// primeira selecionada = 1. /// /// Retorna null caso a rua não esteja selecionada. /// public int? ObterOrdem( MapaSelecaoOperacao selecao, string ruaId) { if (selecao == null) { throw new ArgumentNullException( nameof(selecao) ); } string id = NormalizarId( ruaId ); int indice = selecao.RuasMutaveis.FindIndex( x => string.Equals( x, id, StringComparison.OrdinalIgnoreCase ) ); return indice < 0 ? null : indice + 1; } /// /// Retorna uma cópia independente da seleção. /// /// Este é o método que futuramente alimentará /// OperacaoParametrosModel.RuasPercorrer. /// public List ObterRuasOrdenadas( MapaSelecaoOperacao selecao) { if (selecao == null) { throw new ArgumentNullException( nameof(selecao) ); } return selecao.RuasOrdenadas .ToList(); } private static void GarantirRuaExistente( MapaDocumento documento, string ruaId) { int quantidade = documento.Ruas.Count( x => x != null && string.Equals( x.Id, ruaId, StringComparison.OrdinalIgnoreCase ) ); if (quantidade == 0) { throw new InvalidOperationException( $"A rua '{ruaId}' não existe no mapa." ); } /* * Isso jamais deveria acontecer depois que o * MapaValidationService aprovou o documento. * * Mesmo assim protegemos esta fronteira. */ if (quantidade > 1) { throw new InvalidOperationException( $"O mapa possui {quantidade} ruas com o ID '{ruaId}'." ); } } private static string NormalizarId( string? ruaId) { if (string.IsNullOrWhiteSpace( ruaId)) { throw new ArgumentException( "O ID da rua não foi informado.", nameof(ruaId) ); } return ruaId.Trim(); } private static void ValidarArgumentos( MapaDocumento documento, MapaSelecaoOperacao selecao) { if (documento == null) { throw new ArgumentNullException( nameof(documento) ); } if (selecao == null) { throw new ArgumentNullException( nameof(selecao) ); } if (documento.Ruas == null || documento.Ruas.Count == 0) { throw new InvalidOperationException( "O mapa não possui ruas para seleção." ); } } } }