using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using PlotLine.Models; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] [Route("Writing")] public sealed class WritingController( IStoryContextService storyContext, IRelationshipMapService relationshipMap, IFloorPlanService floorPlans, IWriterWorkspaceService writerWorkspace) : Controller { [HttpGet("Context")] public async Task Context(int? projectId, int? bookId, int? chapterId, int? sceneId) { var model = new WritingContextPageViewModel { ProjectID = projectId, BookID = bookId, ChapterID = chapterId, SceneID = sceneId, Display = sceneId.HasValue ? await BuildDisplayAsync(projectId, bookId, chapterId, sceneId, "Manual context") : new WritingContextDisplayViewModel { FollowStatus = "Connected, waiting for scene" } }; return View(model); } [HttpGet("Intent")] public async Task Intent(int? projectId, int? bookId, int? chapterId, int? sceneId) { var model = new WritingIntentPageViewModel { ProjectID = projectId, BookID = bookId, ChapterID = chapterId, SceneID = sceneId, Display = sceneId.HasValue || chapterId.HasValue ? await BuildIntentDisplayAsync(projectId, bookId, chapterId, sceneId, "Manual intent") : new WritingIntentDisplayViewModel { FollowStatus = "Connected, waiting for scene" } }; return View(model); } [HttpGet("Context/Scene")] public async Task ContextScene(int? projectId, int? bookId, int? chapterId, int? sceneId) { if (!sceneId.HasValue) { return PartialView("~/Views/Writing/_WritingContextContent.cshtml", new WritingContextDisplayViewModel { FollowStatus = "Connected, waiting for scene" }); } var model = await BuildDisplayAsync(projectId, bookId, chapterId, sceneId, "Following Word"); return model?.HasScene == true ? PartialView("~/Views/Writing/_WritingContextContent.cshtml", model) : NotFound(); } [HttpGet("Intent/Scene")] public async Task IntentScene(int? projectId, int? bookId, int? chapterId, int? sceneId) { if (!sceneId.HasValue && !chapterId.HasValue) { return PartialView("~/Views/Writing/_WritingIntentContent.cshtml", new WritingIntentDisplayViewModel { FollowStatus = "Connected, waiting for scene" }); } var model = await BuildIntentDisplayAsync(projectId, bookId, chapterId, sceneId, "Following Word"); return model is not null && (model.HasScene || model.HasChapter) ? PartialView("~/Views/Writing/_WritingIntentContent.cshtml", model) : NotFound(); } private async Task BuildDisplayAsync(int? projectId, int? bookId, int? chapterId, int? sceneId, string followStatus) { if (!sceneId.HasValue) { return new WritingContextDisplayViewModel { FollowStatus = followStatus }; } var context = await storyContext.GetForSceneAsync(sceneId.Value, projectId, bookId); if (context?.Scene is null) { return null; } var position = context.Position; var relationships = await relationshipMap.GetAsync(position, null, true); var floorPlan = context.Scene.FloorPlanID.HasValue ? await floorPlans.GetEditorAsync(context.Scene.FloorPlanID.Value) : null; return new WritingContextDisplayViewModel { Context = context, RelationshipMap = relationships, FloorPlan = floorPlan, FollowStatus = followStatus, SceneOptions = BuildSceneOptions(context.Scene) }; } private async Task BuildIntentDisplayAsync(int? projectId, int? bookId, int? chapterId, int? sceneId, string followStatus) { StoryContextViewModel? context = null; if (sceneId.HasValue) { context = await storyContext.GetForSceneAsync(sceneId.Value, projectId, bookId); if (context?.Scene is null) { return null; } } var resolvedChapterId = context?.Position.ChapterId ?? chapterId; var chapterWorkflow = resolvedChapterId.HasValue ? await writerWorkspace.GetChapterWorkflowAsync(resolvedChapterId.Value) : null; if (chapterWorkflow is not null) { if (projectId.HasValue && chapterWorkflow.Project.ProjectID != projectId.Value) { return null; } if (bookId.HasValue && chapterWorkflow.Book.BookID != bookId.Value) { return null; } } if (context is null && chapterWorkflow is null) { return null; } return new WritingIntentDisplayViewModel { Context = context, ChapterWorkflow = chapterWorkflow, FollowStatus = followStatus, SceneOptions = context?.Scene is not null ? BuildSceneOptions(context.Scene) : BuildSceneOptions(chapterWorkflow) }; } private static IReadOnlyList BuildSceneOptions(SceneEditViewModel scene) { var options = scene.SceneOptions .Select(x => new SelectListItem(x.Text, x.Value, x.Value == scene.SceneID.ToString())) .ToList(); if (!options.Any(x => x.Value == scene.SceneID.ToString())) { options.Insert(0, new SelectListItem( $"{scene.Book?.BookDisplayTitle ?? "Book"} / Ch {scene.Chapter?.ChapterNumber:g} / Scene {scene.SceneNumber:g}: {scene.SceneTitle}", scene.SceneID.ToString(), true)); } return options .OrderByDescending(x => x.Selected) .ThenBy(x => x.Text) .ToList(); } private static IReadOnlyList BuildSceneOptions(ChapterWorkflowViewModel? chapterWorkflow) { return chapterWorkflow?.Scenes .Select(x => new SelectListItem( $"Scene {x.SceneNumber:g}: {x.SceneTitle}", x.SceneID.ToString())) .OrderBy(x => x.Text) .ToList() ?? []; } }