SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

/*
    Focused optimisation for dbo.AssetTimeline_GetByProject.

    Diagnosis:
    - Recent timeline logging showed this procedure at ~12 seconds after it had
      previously been ~18-40 ms.
    - The current procedure joins StoryAssets, AssetEvents, Scenes, Chapters,
      Books, AssetKinds, AssetEventTypes, AssetStates, and LocationPaths.
    - The first result set expands the recursive LocationPaths view for asset
      locations; the second result set can be vulnerable to a poor cached plan
      around the optional @BookID predicate.
    - This rewrite mirrors the safer CharacterTimeline shape: filter project/book
      scenes once, filter active project assets once, and materialise project
      location paths once before joining.
*/

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_StoryAssets_Project_TimelineList' AND object_id = OBJECT_ID(N'dbo.StoryAssets'))
BEGIN
    -- Supports project-scoped asset timeline list filtering and ordering.
    CREATE INDEX IX_StoryAssets_Project_TimelineList
    ON dbo.StoryAssets(ProjectID, IsArchived, IsResolved, Importance DESC, AssetName);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetEvents_StoryAssetID_SceneID' AND object_id = OBJECT_ID(N'dbo.AssetEvents'))
BEGIN
    -- Supports asset-event reads after project assets have been filtered.
    CREATE INDEX IX_AssetEvents_StoryAssetID_SceneID
    ON dbo.AssetEvents(StoryAssetID, SceneID, AssetEventTypeID);
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 location path materialisation for asset current locations.
    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 location path expansion from parent to child locations.
    CREATE INDEX IX_Locations_ParentLocationID
    ON dbo.Locations(ParentLocationID, IsArchived, LocationName);
END;
GO

IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_StoryAssets_Project_TimelineList' AND object_id = OBJECT_ID(N'dbo.StoryAssets'))
BEGIN
    UPDATE STATISTICS dbo.StoryAssets IX_StoryAssets_Project_TimelineList;
END;
GO

IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetEvents_StoryAssetID_SceneID' AND object_id = OBJECT_ID(N'dbo.AssetEvents'))
BEGIN
    UPDATE STATISTICS dbo.AssetEvents IX_AssetEvents_StoryAssetID_SceneID;
END;
GO

IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetEvents_SceneID' AND object_id = OBJECT_ID(N'dbo.AssetEvents'))
BEGIN
    UPDATE STATISTICS dbo.AssetEvents IX_AssetEvents_SceneID;
END;
GO

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

    CREATE TABLE #TimelineScenes
    (
        SceneID int NOT NULL PRIMARY KEY,
        BookSortOrder int NOT NULL,
        ChapterSortOrder int NOT NULL,
        SceneSortOrder int NOT NULL
    );

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

    CREATE TABLE #ProjectAssets
    (
        StoryAssetID int NOT NULL PRIMARY KEY,
        ProjectID int NOT NULL,
        AssetName nvarchar(200) NOT NULL,
        AssetKindID int NOT NULL,
        Description nvarchar(max) NULL,
        Importance int NOT NULL,
        CurrentStateID int NULL,
        CurrentLocationID int NULL,
        IsResolved bit NOT NULL,
        CreatedDate datetime2 NOT NULL,
        UpdatedDate datetime2 NOT NULL,
        IsArchived bit NOT NULL
    );

    INSERT #ProjectAssets (StoryAssetID, ProjectID, AssetName, AssetKindID, Description, Importance, CurrentStateID, CurrentLocationID, IsResolved, CreatedDate, UpdatedDate, IsArchived)
    SELECT StoryAssetID, ProjectID, AssetName, AssetKindID, Description, Importance, CurrentStateID, CurrentLocationID, IsResolved, CreatedDate, UpdatedDate, IsArchived
    FROM dbo.StoryAssets
    WHERE ProjectID = @ProjectID
        AND IsArchived = 0
    OPTION (RECOMPILE);

    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 sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description, sa.Importance,
        sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
        location.LocationName AS CurrentLocationName, location.LocationPath AS CurrentLocationPath,
        sa.IsResolved, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
    FROM #ProjectAssets sa
    INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
    LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
    LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sa.CurrentLocationID
    WHERE sa.Importance >= 5 OR sa.IsResolved = 0
    ORDER BY sa.Importance DESC, sa.AssetName;

    SELECT ae.AssetEventID, ae.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName,
        ae.SceneID, ae.AssetEventTypeID, aet.TypeName AS AssetEventTypeName, aet.MarkerText,
        ae.FromStateID, fs.StateName AS FromStateName, ae.ToStateID, ts.StateName AS ToStateName,
        ae.EventTitle, ae.EventDescription, ae.CreatedDate, ae.UpdatedDate
    FROM #TimelineScenes scene
    INNER JOIN dbo.AssetEvents ae ON ae.SceneID = scene.SceneID
    INNER JOIN #ProjectAssets sa ON sa.StoryAssetID = ae.StoryAssetID
    INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
    INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
    LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
    LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
    ORDER BY scene.BookSortOrder, scene.ChapterSortOrder, scene.SceneSortOrder, sa.AssetName;
END;
GO
