using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using RobotNet10.MapEditor.Shared.DTOs.LayoutData; using RobotNet10.MapEditor.Shared.DTOs.Requests; using RobotNet10.MapEditor.Shared.DTOs.Responses; using RobotNet10.MapManager.Services; namespace RobotNet10.MapManager.Controllers; /// /// Controller for retrieving complete layout data and merge/split operations /// [ApiController] [Route("api/layout-data")] [Authorize] public class LayoutDataController( ILayoutDataService layoutDataService, ILogger logger) : ControllerBase { private readonly ILayoutDataService _layoutDataService = layoutDataService; private readonly ILogger _logger = logger; /// /// Get complete layout data for a layout level /// Returns all nodes, edges, and stations with full nested properties /// /// Layout level ID /// Complete layout data [HttpGet("{layoutLevelId}")] [ProducesResponseType(typeof(LayoutDataDto), 200)] public async Task> GetLayoutData(Guid layoutLevelId) { try { var data = await _layoutDataService.GetLayoutDataAsync(layoutLevelId); return Ok(data); } catch (Exception ex) { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error retrieving layout data for level {LevelId}", layoutLevelId); return StatusCode(500, CreateErrorResponse("Internal server error while retrieving layout data", "INTERNAL_ERROR")); } } /// /// Merge multiple nodes into one node at center position /// /// Merge nodes request /// Merge result with merged node, updated edges, and deleted node IDs [HttpPost("merge-nodes")] [ProducesResponseType(typeof(MergeNodesResponse), 200)] [ProducesResponseType(400)] public async Task> MergeNodes([FromBody] MergeNodesRequest request) { try { var result = await _layoutDataService.MergeNodesAsync(request); return Ok(result); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Failed to merge nodes"); return BadRequest(CreateErrorResponse(ex.Message, "VALIDATION_ERROR")); } catch (Exception ex) { _logger.LogError(ex, "Error merging nodes"); return StatusCode(500, CreateErrorResponse("Internal server error while merging nodes", "INTERNAL_ERROR")); } } /// /// Split a node into multiple nodes (one for each connected edge) /// /// Split node request /// Split result with new nodes, updated edges, and deleted node ID [HttpPost("split-node")] [ProducesResponseType(typeof(SplitNodeResponse), 200)] [ProducesResponseType(400)] public async Task> SplitNode([FromBody] SplitNodeRequest request) { try { var result = await _layoutDataService.SplitNodeAsync(request); return Ok(result); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Failed to split node"); return BadRequest(CreateErrorResponse(ex.Message, "VALIDATION_ERROR")); } catch (Exception ex) { _logger.LogError(ex, "Error splitting node"); return StatusCode(500, CreateErrorResponse("Internal server error while splitting node", "INTERNAL_ERROR")); } } /// /// Save all layout changes (nodes and edges) in a batch operation /// Uses transaction to ensure atomicity /// /// Save request with nodes and edges to update /// Save result with counts and any skipped items [HttpPost("save")] [ProducesResponseType(typeof(SaveLayoutDataResponse), 200)] [ProducesResponseType(400)] public async Task> SaveLayoutData([FromBody] SaveLayoutDataRequest request) { try { var result = await _layoutDataService.SaveLayoutDataAsync(request); if (!result.Success) { return BadRequest(result); } return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error saving layout data"); return StatusCode(500, CreateErrorResponse("Internal server error while saving layout data", "INTERNAL_ERROR")); } } /// /// Copy selected nodes and edges with an offset /// Creates new nodes and edges at offset positions /// /// Copy request with node IDs, edge IDs, and offset /// Copy result with newly created nodes and edges [HttpPost("copy-nodes")] [ProducesResponseType(typeof(CopyNodesResponse), 200)] [ProducesResponseType(400)] public async Task> CopyNodes([FromBody] CopyNodesRequest request) { try { var result = await _layoutDataService.CopyNodesAsync(request); if (!result.Success) { return BadRequest(result); } return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error copying nodes"); return StatusCode(500, CreateErrorResponse("Internal server error while copying nodes", "INTERNAL_ERROR")); } } private static ErrorResponseDto CreateErrorResponse( string error, string? errorCode = null, Dictionary? details = null) { return new ErrorResponseDto { Error = error, ErrorCode = errorCode, Details = details }; } }