using Manager.DTOs; using ManagerService.DTOs; using NetTopologySuite.Geometries; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations; using System.Linq; using ManagerService.Helpers; namespace ManagerService.Data.SubSection { public class GuidedStep // Étape d’un parcours { [Key] public string Id { get; set; } [Required] public string GuidedPathId { get; set; } [ForeignKey("GuidedPathId")] public GuidedPath GuidedPath { get; set; } public int Order { get; set; } [Required] [Column(TypeName = "jsonb")] public List Title { get; set; } [Column(TypeName = "jsonb")] public List Description { get; set; } public Geometry? Geometry { get; set; } // Polygon ou centre du cercle // Option : si true, cette étape doit être atteinte géographiquement (zone/rayon) pour être validée public bool IsGeoTriggered { get; set; } = false; public double? ZoneRadiusMeters { get; set; } // Optionnel, utile si zone cercle ou point public string ImageUrl { get; set; } // Rich content (calqué sur SectionArticle) [Column(TypeName = "jsonb")] public List AudioIds { get; set; } = new(); [Column(TypeName = "jsonb")] public List Contents { get; set; } = new(); [Column(TypeName = "jsonb")] public List FactContent { get; set; } = new(); public bool IsHiddenInitially { get; set; } = false; // Exemple pour escape game public List? QuizQuestions { get; set; } // One or multiple question // Option : si true, cette étape a un compte à rebourds, false sinon public bool IsStepTimer { get; set; } = false; // Option : si true, cette étape doit être validée avant de passer à la suivante public bool IsStepLocked { get; set; } = true; // Timer en secondes (durée max pour valider cette étape, optionnel) public int? TimerSeconds { get; set; } // Option : message ou action à effectuer si timer expire (ex: afficher aide, fin du jeu...) [Column(TypeName = "jsonb")] public List TimerExpiredMessage { get; set; } public GuidedStepDTO ToDTO() { return new GuidedStepDTO { id = Id, guidedPathId = GuidedPathId, order = Order, title = Title, description = Description, geometry = Geometry?.ToDto(), isGeoTriggered = IsGeoTriggered, zoneRadiusMeters = ZoneRadiusMeters, imageUrl = ImageUrl, audioIds = AudioIds, contents = Contents, factContent = FactContent, isHiddenInitially = IsHiddenInitially, isStepTimer = IsStepTimer, isStepLocked = IsStepLocked, timerSeconds = TimerSeconds, timerExpiredMessage = TimerExpiredMessage, quizQuestions = QuizQuestions }; } public GuidedStep FromDTO(GuidedStepDTO dto) { if (dto == null) return null; GuidedPathId = dto.guidedPathId; Title = dto.title ?? new List(); Description = dto.description ?? new List(); Geometry = dto.geometry.FromDto(); IsGeoTriggered = dto.isGeoTriggered; ZoneRadiusMeters = dto.zoneRadiusMeters; ImageUrl = dto.imageUrl; AudioIds = dto.audioIds ?? new List(); Contents = dto.contents ?? new List(); FactContent = dto.factContent ?? new List(); IsHiddenInitially = dto.isHiddenInitially; IsStepTimer = dto.isStepTimer; IsStepLocked = dto.isStepLocked; TimerSeconds = dto.timerSeconds; TimerExpiredMessage = dto.timerExpiredMessage ?? new List(); Order = dto.order.GetValueOrDefault(); SyncQuizQuestions(dto.quizQuestions); return this; } // Merges incoming quiz questions into the tracked QuizQuestions collection by Id, // instead of replacing the reference outright — a blind reassignment would make EF // treat every incoming question (including already-persisted ones) as a brand new // row to insert, which fails with a duplicate key error as soon as a step already // has a saved question and a new one is added alongside it. public void SyncQuizQuestions(List? dtoQuestions) { dtoQuestions ??= new List(); QuizQuestions ??= new List(); var dtoIds = dtoQuestions.Where(q => q.Id != 0).Select(q => q.Id).ToHashSet(); QuizQuestions.RemoveAll(q => !dtoIds.Contains(q.Id)); foreach (var qDto in dtoQuestions) { var existing = qDto.Id != 0 ? QuizQuestions.FirstOrDefault(q => q.Id == qDto.Id) : null; if (existing != null) { existing.Label = qDto.Label; existing.ResourceId = qDto.ResourceId; existing.Order = qDto.Order; existing.Responses = qDto.Responses; existing.ValidationQuestionType = qDto.ValidationQuestionType; existing.PuzzleImageId = qDto.PuzzleImageId; existing.PuzzleRows = qDto.PuzzleRows; existing.PuzzleCols = qDto.PuzzleCols; existing.IsSlidingPuzzle = qDto.IsSlidingPuzzle; } else { qDto.Id = 0; QuizQuestions.Add(qDto); } } } } }