SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

/*
    Focused optimisation for dbo.CharacterTimeline_GetByProject.

    Diagnosis:
    - The timeline timing showed this procedure as the clear bottleneck.
    - The original appearance query joined SceneCharacters to Characters, Scenes,
      Chapters, Books, and the recursive LocationPaths view three times.
    - Filtering the project/book scene set once gives SQL Server a small driving
      set before joining appearances.
    - Materialising project location paths once avoids repeated recursive view
      expansion for location, entry location, and exit location labels.
*/

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Characters_ProjectID_Importance_Name' AND object_id = OBJECT_ID(N'dbo.Characters'))
BEGIN
    -- Supports CharacterTimeline_GetByProject character list ordering.
    CREATE INDEX IX_Characters_ProjectID_Importance_Name
    ON dbo.Characters(ProjectID, IsArchived, CharacterImportance DESC, CharacterName);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_CharacterID_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
BEGIN
    -- Supports appearance lookups when the optimizer starts from project characters.
    CREATE INDEX IX_SceneCharacters_CharacterID_SceneID
    ON dbo.SceneCharacters(CharacterID, SceneID, RoleInSceneTypeID);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_Project_Parent_Name' AND object_id = OBJECT_ID(N'dbo.Locations'))
BEGIN
    -- Supports project-scoped LocationPaths materialisation.
    CREATE INDEX IX_Locations_Project_Parent_Name
    ON dbo.Locations(ProjectID, IsArchived, ParentLocationID, LocationName);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_ParentLocationID' AND object_id = OBJECT_ID(N'dbo.Locations'))
BEGIN
    -- Supports recursive LocationPaths expansion from parent to child locations.
    CREATE INDEX IX_Locations_ParentLocationID
    ON dbo.Locations(ParentLocationID, IsArchived, LocationName);
END;
GO

CREATE OR ALTER PROCEDURE dbo.CharacterTimeline_GetByProject
    @ProjectID int,
    @BookID int = NULL
AS
BEGIN
    SET NOCOUNT ON;

    CREATE TABLE #TimelineScenes
    (
        SceneID int NOT NULL PRIMARY KEY,
        SceneNumber decimal(9,2) NOT NULL,
        SceneTitle nvarchar(200) NOT NULL,
        ChapterNumber decimal(9,2) NOT NULL,
        BookTitle nvarchar(200) NOT NULL,
        BookSortOrder int NOT NULL,
        ChapterSortOrder int NOT NULL,
        SceneSortOrder int NOT NULL
    );

    INSERT #TimelineScenes (SceneID, SceneNumber, SceneTitle, ChapterNumber, BookTitle, BookSortOrder, ChapterSortOrder, SceneSortOrder)
    SELECT s.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle, b.SortOrder, ch.SortOrder, s.SortOrder
    FROM dbo.Books b
    INNER JOIN dbo.Chapters ch ON ch.BookID = b.BookID
    INNER JOIN dbo.Scenes s ON s.ChapterID = ch.ChapterID
    WHERE b.ProjectID = @ProjectID
        AND b.IsArchived = 0
        AND ch.IsArchived = 0
        AND s.IsArchived = 0
        AND (@BookID IS NULL OR b.BookID = @BookID);

    CREATE TABLE #TimelineCharacters
    (
        CharacterID int NOT NULL PRIMARY KEY,
        ProjectID int NOT NULL,
        CharacterName nvarchar(200) NOT NULL,
        ShortName nvarchar(100) NULL,
        Sex nvarchar(50) NULL,
        BirthDate date NULL,
        AgeAtSeriesStart int NULL,
        AgeReferenceSceneID int NULL,
        Height nvarchar(50) NULL,
        EyeColour nvarchar(50) NULL,
        CharacterImportance int NULL,
        DefaultDescription nvarchar(max) NULL,
        CreatedDate datetime2 NOT NULL,
        UpdatedDate datetime2 NOT NULL,
        IsArchived bit NOT NULL
    );

    INSERT #TimelineCharacters (CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID, Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived)
    SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
        Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
    FROM dbo.Characters
    WHERE ProjectID = @ProjectID AND IsArchived = 0;

    CREATE TABLE #ProjectLocationPaths
    (
        LocationID int NOT NULL PRIMARY KEY,
        LocationName nvarchar(200) NOT NULL,
        LocationPath nvarchar(max) NOT NULL
    );

    ;WITH ProjectLocationTree AS
    (
        SELECT LocationID, LocationName, CAST(LocationName AS nvarchar(max)) AS LocationPath
        FROM dbo.Locations
        WHERE ProjectID = @ProjectID AND ParentLocationID IS NULL AND IsArchived = 0

        UNION ALL

        SELECT child.LocationID, child.LocationName, CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)) AS LocationPath
        FROM dbo.Locations child
        INNER JOIN ProjectLocationTree parent ON parent.LocationID = child.ParentLocationID
        WHERE child.ProjectID = @ProjectID AND child.IsArchived = 0
    )
    INSERT #ProjectLocationPaths (LocationID, LocationName, LocationPath)
    SELECT LocationID, LocationName, LocationPath
    FROM ProjectLocationTree;

    SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
        Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
    FROM #TimelineCharacters
    ORDER BY ISNULL(CharacterImportance, 0) DESC, CharacterName;

    SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
        sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
        sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
        sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
        sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, s.SceneNumber, s.SceneTitle, s.ChapterNumber, s.BookTitle,
        sc.CreatedDate, sc.UpdatedDate
    FROM #TimelineScenes s
    INNER JOIN dbo.SceneCharacters sc ON sc.SceneID = s.SceneID
    INNER JOIN #TimelineCharacters c ON c.CharacterID = sc.CharacterID
    LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
    LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
    LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sc.LocationID
    LEFT JOIN #ProjectLocationPaths entry ON entry.LocationID = sc.EntryLocationID
    LEFT JOIN #ProjectLocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
    ORDER BY s.BookSortOrder, s.ChapterSortOrder, s.SceneSortOrder, c.CharacterName;
END;
GO
