SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

IF OBJECT_ID(N'dbo.PlotImportance', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.PlotImportance
    (
        PlotImportanceID int NOT NULL CONSTRAINT PK_PlotImportance PRIMARY KEY,
        ImportanceName nvarchar(100) NOT NULL,
        SortOrder int NOT NULL,
        IsActive bit NOT NULL CONSTRAINT DF_PlotImportance_IsActive DEFAULT 1,
        CONSTRAINT UQ_PlotImportance_ImportanceName UNIQUE (ImportanceName)
    );
END;
GO

MERGE dbo.PlotImportance AS target
USING (VALUES
    (1, N'Primary', 10),
    (2, N'Secondary', 20),
    (3, N'Minor', 30)
) AS source (PlotImportanceID, ImportanceName, SortOrder)
ON target.PlotImportanceID = source.PlotImportanceID
WHEN MATCHED THEN
    UPDATE SET ImportanceName = source.ImportanceName, SortOrder = source.SortOrder, IsActive = 1
WHEN NOT MATCHED THEN
    INSERT (PlotImportanceID, ImportanceName, SortOrder, IsActive)
    VALUES (source.PlotImportanceID, source.ImportanceName, source.SortOrder, 1);
GO

IF OBJECT_ID(N'dbo.PlotEventTypes', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.PlotEventTypes
    (
        PlotEventTypeID int NOT NULL CONSTRAINT PK_PlotEventTypes PRIMARY KEY,
        TypeName nvarchar(100) NOT NULL,
        SortOrder int NOT NULL,
        IsActive bit NOT NULL CONSTRAINT DF_PlotEventTypes_IsActive DEFAULT 1,
        CONSTRAINT UQ_PlotEventTypes_TypeName UNIQUE (TypeName)
    );
END;
GO

MERGE dbo.PlotEventTypes AS target
USING (VALUES
    (1, N'Start', 10),
    (2, N'Progress', 20),
    (3, N'Reveal', 30),
    (4, N'Twist', 40),
    (5, N'Branch', 50),
    (6, N'Split', 60),
    (7, N'Merge', 70),
    (8, N'Resolve', 80),
    (9, N'Abandon', 90)
) AS source (PlotEventTypeID, TypeName, SortOrder)
ON target.PlotEventTypeID = source.PlotEventTypeID
WHEN MATCHED THEN
    UPDATE SET TypeName = source.TypeName, SortOrder = source.SortOrder, IsActive = 1
WHEN NOT MATCHED THEN
    INSERT (PlotEventTypeID, TypeName, SortOrder, IsActive)
    VALUES (source.PlotEventTypeID, source.TypeName, source.SortOrder, 1);
GO

IF COL_LENGTH(N'dbo.PlotLines', N'PlotImportanceID') IS NULL
BEGIN
    ALTER TABLE dbo.PlotLines ADD PlotImportanceID int NULL;
END;
GO

IF COL_LENGTH(N'dbo.PlotLines', N'EmergesFromPlotLineID') IS NULL
BEGIN
    ALTER TABLE dbo.PlotLines ADD EmergesFromPlotLineID int NULL;
END;
GO

IF COL_LENGTH(N'dbo.PlotLines', N'IsMainPlot') IS NOT NULL
BEGIN
    EXEC (N'UPDATE dbo.PlotLines SET PlotImportanceID = CASE WHEN IsMainPlot = 1 THEN 1 ELSE 2 END WHERE PlotImportanceID IS NULL;');
END
ELSE
BEGIN
    UPDATE dbo.PlotLines SET PlotImportanceID = 2 WHERE PlotImportanceID IS NULL;
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = N'DF_PlotLines_PlotImportanceID' AND parent_object_id = OBJECT_ID(N'dbo.PlotLines'))
BEGIN
    ALTER TABLE dbo.PlotLines ADD CONSTRAINT DF_PlotLines_PlotImportanceID DEFAULT 2 FOR PlotImportanceID;
END;
GO

ALTER TABLE dbo.PlotLines ALTER COLUMN PlotImportanceID int NOT NULL;
GO

IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_PlotLines_PlotImportance')
BEGIN
    ALTER TABLE dbo.PlotLines WITH CHECK ADD CONSTRAINT FK_PlotLines_PlotImportance
        FOREIGN KEY (PlotImportanceID) REFERENCES dbo.PlotImportance(PlotImportanceID);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_PlotLines_EmergesFromPlotLine')
BEGIN
    ALTER TABLE dbo.PlotLines WITH CHECK ADD CONSTRAINT FK_PlotLines_EmergesFromPlotLine
        FOREIGN KEY (EmergesFromPlotLineID) REFERENCES dbo.PlotLines(PlotLineID);
END;
GO

IF COL_LENGTH(N'dbo.PlotLines', N'IsMainPlot') IS NOT NULL
BEGIN
    DECLARE @DropIsMainPlotDefault nvarchar(max);
    SELECT @DropIsMainPlotDefault = N'ALTER TABLE dbo.PlotLines DROP CONSTRAINT ' + QUOTENAME(dc.name)
    FROM sys.default_constraints dc
    INNER JOIN sys.columns c ON c.default_object_id = dc.object_id
    WHERE dc.parent_object_id = OBJECT_ID(N'dbo.PlotLines') AND c.name = N'IsMainPlot';

    IF @DropIsMainPlotDefault IS NOT NULL EXEC (@DropIsMainPlotDefault);
    ALTER TABLE dbo.PlotLines DROP COLUMN IsMainPlot;
END;
GO

IF COL_LENGTH(N'dbo.ThreadEvents', N'PlotEventTypeID') IS NULL
BEGIN
    ALTER TABLE dbo.ThreadEvents ADD PlotEventTypeID int NULL;
END;
GO

IF COL_LENGTH(N'dbo.ThreadEvents', N'TargetPlotLineID') IS NULL
BEGIN
    ALTER TABLE dbo.ThreadEvents ADD TargetPlotLineID int NULL;
END;
GO

UPDATE dbo.ThreadEvents
SET PlotEventTypeID = 2
WHERE PlotEventTypeID IS NULL;
GO

IF NOT EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = N'DF_ThreadEvents_PlotEventTypeID' AND parent_object_id = OBJECT_ID(N'dbo.ThreadEvents'))
BEGIN
    ALTER TABLE dbo.ThreadEvents ADD CONSTRAINT DF_ThreadEvents_PlotEventTypeID DEFAULT 2 FOR PlotEventTypeID;
END;
GO

ALTER TABLE dbo.ThreadEvents ALTER COLUMN PlotEventTypeID int NOT NULL;
GO

IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ThreadEvents_PlotEventTypes')
BEGIN
    ALTER TABLE dbo.ThreadEvents WITH CHECK ADD CONSTRAINT FK_ThreadEvents_PlotEventTypes
        FOREIGN KEY (PlotEventTypeID) REFERENCES dbo.PlotEventTypes(PlotEventTypeID);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ThreadEvents_TargetPlotLines')
BEGIN
    ALTER TABLE dbo.ThreadEvents WITH CHECK ADD CONSTRAINT FK_ThreadEvents_TargetPlotLines
        FOREIGN KEY (TargetPlotLineID) REFERENCES dbo.PlotLines(PlotLineID);
END;
GO

IF OBJECT_ID(N'dbo.PlotEventTargetPlotLines', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.PlotEventTargetPlotLines
    (
        PlotEventID int NOT NULL,
        PlotLineID int NOT NULL,
        CreatedDate datetime2 NOT NULL CONSTRAINT DF_PlotEventTargetPlotLines_CreatedDate DEFAULT SYSUTCDATETIME(),
        CONSTRAINT PK_PlotEventTargetPlotLines PRIMARY KEY (PlotEventID, PlotLineID),
        CONSTRAINT FK_PlotEventTargetPlotLines_ThreadEvents FOREIGN KEY (PlotEventID) REFERENCES dbo.ThreadEvents(ThreadEventID) ON DELETE CASCADE,
        CONSTRAINT FK_PlotEventTargetPlotLines_PlotLines FOREIGN KEY (PlotLineID) REFERENCES dbo.PlotLines(PlotLineID)
    );
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_PlotLines_PlotImportanceID' AND object_id = OBJECT_ID(N'dbo.PlotLines'))
    CREATE INDEX IX_PlotLines_PlotImportanceID ON dbo.PlotLines(ProjectID, PlotImportanceID, IsArchived);
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ThreadEvents_PlotEventTypeID' AND object_id = OBJECT_ID(N'dbo.ThreadEvents'))
    CREATE INDEX IX_ThreadEvents_PlotEventTypeID ON dbo.ThreadEvents(PlotEventTypeID, TargetPlotLineID);
GO

CREATE OR ALTER PROCEDURE dbo.PlotLookup_All
AS
BEGIN
    SET NOCOUNT ON;
    SELECT PlotLineTypeID, TypeName, SortOrder, IsActive FROM dbo.PlotLineTypes WHERE IsActive = 1 ORDER BY SortOrder;
    SELECT PlotImportanceID, ImportanceName, SortOrder, IsActive FROM dbo.PlotImportance WHERE IsActive = 1 ORDER BY SortOrder;
    SELECT ThreadTypeID, TypeName, SortOrder, IsActive FROM dbo.ThreadTypes WHERE IsActive = 1 ORDER BY SortOrder;
    SELECT ThreadStatusID, StatusName, SortOrder, IsOpenStatus, IsResolvedStatus, IsActive FROM dbo.ThreadStatuses WHERE IsActive = 1 ORDER BY SortOrder;
    SELECT ThreadEventTypeID, TypeName, MarkerText, SortOrder, IsActive FROM dbo.ThreadEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
    SELECT PlotEventTypeID, TypeName, SortOrder, IsActive FROM dbo.PlotEventTypes WHERE IsActive = 1 ORDER BY SortOrder;
END;
GO

CREATE OR ALTER PROCEDURE dbo.PlotLine_ListByProject
    @ProjectID int
AS
BEGIN
    SET NOCOUNT ON;
    SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
        plt.TypeName AS PlotLineTypeName, pl.PlotImportanceID, pi.ImportanceName AS PlotImportanceName,
        pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
        pl.EmergesFromPlotLineID, emerges.PlotLineName AS EmergesFromPlotLineName,
        pl.SortOrder, pl.Colour, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
    FROM dbo.PlotLines pl
    INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
    INNER JOIN dbo.PlotImportance pi ON pi.PlotImportanceID = pl.PlotImportanceID
    LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
    LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
    LEFT JOIN dbo.PlotLines emerges ON emerges.PlotLineID = pl.EmergesFromPlotLineID
    WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0
    ORDER BY pl.SortOrder, pl.PlotLineName;
END;
GO

CREATE OR ALTER PROCEDURE dbo.PlotLine_Get
    @PlotLineID int
AS
BEGIN
    SET NOCOUNT ON;
    SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
        plt.TypeName AS PlotLineTypeName, pl.PlotImportanceID, pi.ImportanceName AS PlotImportanceName,
        pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
        pl.EmergesFromPlotLineID, emerges.PlotLineName AS EmergesFromPlotLineName,
        pl.SortOrder, pl.Colour, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
    FROM dbo.PlotLines pl
    INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
    INNER JOIN dbo.PlotImportance pi ON pi.PlotImportanceID = pl.PlotImportanceID
    LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
    LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
    LEFT JOIN dbo.PlotLines emerges ON emerges.PlotLineID = pl.EmergesFromPlotLineID
    WHERE pl.PlotLineID = @PlotLineID AND pl.IsArchived = 0;
END;
GO

CREATE OR ALTER PROCEDURE dbo.PlotLine_Save
    @PlotLineID int = NULL,
    @ProjectID int,
    @BookID int = NULL,
    @PlotLineName nvarchar(200),
    @PlotLineTypeID int,
    @PlotImportanceID int = 2,
    @Description nvarchar(max) = NULL,
    @ParentPlotLineID int = NULL,
    @EmergesFromPlotLineID int = NULL,
    @SortOrder int = NULL,
    @Colour nvarchar(20),
    @IsVisibleOnTimeline bit
AS
BEGIN
    SET NOCOUNT ON;
    IF @PlotLineID IS NULL OR @PlotLineID = 0
    BEGIN
        DECLARE @NextSortOrder int = COALESCE(@SortOrder, ISNULL((SELECT MAX(SortOrder) FROM dbo.PlotLines WHERE ProjectID = @ProjectID), 0) + 10);
        INSERT dbo.PlotLines (ProjectID, BookID, PlotLineName, PlotLineTypeID, PlotImportanceID, Description, ParentPlotLineID, EmergesFromPlotLineID, SortOrder, Colour, IsVisibleOnTimeline)
        VALUES (@ProjectID, @BookID, @PlotLineName, @PlotLineTypeID, @PlotImportanceID, @Description, @ParentPlotLineID, @EmergesFromPlotLineID, @NextSortOrder, @Colour, @IsVisibleOnTimeline);
        SELECT CAST(SCOPE_IDENTITY() AS int);
        RETURN;
    END;

    UPDATE dbo.PlotLines
    SET BookID = @BookID,
        PlotLineName = @PlotLineName,
        PlotLineTypeID = @PlotLineTypeID,
        PlotImportanceID = @PlotImportanceID,
        Description = @Description,
        ParentPlotLineID = @ParentPlotLineID,
        EmergesFromPlotLineID = @EmergesFromPlotLineID,
        SortOrder = COALESCE(@SortOrder, SortOrder),
        Colour = @Colour,
        IsVisibleOnTimeline = @IsVisibleOnTimeline,
        UpdatedDate = SYSUTCDATETIME()
    WHERE PlotLineID = @PlotLineID;

    SELECT @PlotLineID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ThreadEvent_ListByScene
    @SceneID int
AS
BEGIN
    SET NOCOUNT ON;
    SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour,
        te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText,
        te.PlotEventTypeID, pet.TypeName AS PlotEventTypeName, te.TargetPlotLineID, target.PlotLineName AS TargetPlotLineName,
        te.EventTitle, te.EventDescription, te.CreatedDate, te.UpdatedDate
    FROM dbo.ThreadEvents te
    INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
    INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
    INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
    INNER JOIN dbo.PlotEventTypes pet ON pet.PlotEventTypeID = te.PlotEventTypeID
    LEFT JOIN dbo.PlotLines target ON target.PlotLineID = te.TargetPlotLineID
    WHERE te.SceneID = @SceneID AND pt.IsArchived = 0 AND pl.IsArchived = 0
    ORDER BY pl.SortOrder, pt.Importance DESC, te.CreatedDate;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ThreadEvent_ListByPlotThread
    @PlotThreadID int
AS
BEGIN
    SET NOCOUNT ON;
    SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour,
        te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText,
        te.PlotEventTypeID, pet.TypeName AS PlotEventTypeName, te.TargetPlotLineID, target.PlotLineName AS TargetPlotLineName,
        te.EventTitle, te.EventDescription, te.CreatedDate, te.UpdatedDate
    FROM dbo.ThreadEvents te
    INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
    INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
    INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
    INNER JOIN dbo.PlotEventTypes pet ON pet.PlotEventTypeID = te.PlotEventTypeID
    LEFT JOIN dbo.PlotLines target ON target.PlotLineID = te.TargetPlotLineID
    WHERE te.PlotThreadID = @PlotThreadID
    ORDER BY te.CreatedDate;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ThreadEvent_Save
    @ThreadEventID int = NULL,
    @PlotThreadID int,
    @SceneID int,
    @EventTypeID int,
    @PlotEventTypeID int = 2,
    @TargetPlotLineID int = NULL,
    @EventTitle nvarchar(200),
    @EventDescription nvarchar(max) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    IF @ThreadEventID IS NULL OR @ThreadEventID = 0
    BEGIN
        INSERT dbo.ThreadEvents (PlotThreadID, SceneID, EventTypeID, PlotEventTypeID, TargetPlotLineID, EventTitle, EventDescription)
        VALUES (@PlotThreadID, @SceneID, @EventTypeID, @PlotEventTypeID, @TargetPlotLineID, @EventTitle, @EventDescription);
        SET @ThreadEventID = CAST(SCOPE_IDENTITY() AS int);
    END
    ELSE
    BEGIN
        UPDATE dbo.ThreadEvents
        SET PlotThreadID = @PlotThreadID,
            SceneID = @SceneID,
            EventTypeID = @EventTypeID,
            PlotEventTypeID = @PlotEventTypeID,
            TargetPlotLineID = @TargetPlotLineID,
            EventTitle = @EventTitle,
            EventDescription = @EventDescription,
            UpdatedDate = SYSUTCDATETIME()
        WHERE ThreadEventID = @ThreadEventID;
    END;

    DECLARE @EventTypeName nvarchar(100);
    SELECT @EventTypeName = TypeName FROM dbo.ThreadEventTypes WHERE ThreadEventTypeID = @EventTypeID;

    IF @EventTypeName = N'Introduced'
    BEGIN
        UPDATE pt
        SET IntroducedSceneID = COALESCE(IntroducedSceneID, @SceneID),
            ThreadStatusID = CASE WHEN ts.StatusName = N'Planned' THEN introduced.ThreadStatusID ELSE pt.ThreadStatusID END,
            UpdatedDate = SYSUTCDATETIME()
        FROM dbo.PlotThreads pt
        INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
        CROSS APPLY (SELECT ThreadStatusID FROM dbo.ThreadStatuses WHERE StatusName = N'Introduced') introduced
        WHERE pt.PlotThreadID = @PlotThreadID;
    END;

    IF @EventTypeName IN (N'Resolved', N'Payoff')
    BEGIN
        UPDATE pt
        SET ActualResolutionSceneID = COALESCE(ActualResolutionSceneID, @SceneID),
            ThreadStatusID = CASE WHEN @EventTypeName = N'Resolved' THEN resolved.ThreadStatusID ELSE pt.ThreadStatusID END,
            UpdatedDate = SYSUTCDATETIME()
        FROM dbo.PlotThreads pt
        CROSS APPLY (SELECT ThreadStatusID FROM dbo.ThreadStatuses WHERE StatusName = N'Resolved') resolved
        WHERE pt.PlotThreadID = @PlotThreadID;
    END;

    SELECT @ThreadEventID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.Timeline_GetByProject
    @ProjectID int,
    @BookID int = NULL,
    @IncludeMetrics bit = 1,
    @SelectedMetricTypeIDs nvarchar(max) = NULL,
    @IncludePlotLines bit = 1
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @MetricSelection TABLE (MetricTypeID int NOT NULL PRIMARY KEY);
    INSERT @MetricSelection (MetricTypeID)
    SELECT DISTINCT TRY_CONVERT(int, value)
    FROM string_split(COALESCE(@SelectedMetricTypeIDs, N''), N',')
    WHERE TRY_CONVERT(int, value) IS NOT NULL;

    SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
    FROM dbo.Projects
    WHERE ProjectID = @ProjectID AND IsArchived = 0;

    SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
    FROM dbo.Books
    WHERE ProjectID = @ProjectID AND IsArchived = 0 AND (@BookID IS NULL OR BookID = @BookID)
    ORDER BY SortOrder, BookNumber;

    SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary,
        c.SortOrder, c.RevisionStatusID, rs.StatusName AS RevisionStatusName,
        c.CreatedDate, c.UpdatedDate, c.IsArchived
    FROM dbo.Chapters c
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
    WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
    ORDER BY b.SortOrder, c.SortOrder, c.ChapterNumber;

    SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID,
        s.PrimaryLocationID, location.LocationName AS PrimaryLocationName, location.LocationPath AS PrimaryLocationPath,
        s.SortOrder, s.TimeModeID, tm.TimeModeName, s.StartDateTime,
        s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName, s.RelativeTimeText,
        s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
        s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
    FROM dbo.Scenes s
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
    LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
    INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
    INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
    LEFT JOIN dbo.LocationPaths location ON location.LocationID = s.PrimaryLocationID
    WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
    ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneNumber;

    SELECT sp.SceneID, spt.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder
    FROM dbo.ScenePurposes sp
    INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = sp.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0 AND c.IsArchived = 0 AND s.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
    ORDER BY sp.SceneID, spt.SortOrder;

    SELECT MetricTypeID, ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive
    FROM dbo.SceneMetricTypes
    WHERE @IncludeMetrics = 1
        AND IsActive = 1
        AND (ProjectID IS NULL OR ProjectID = @ProjectID)
        AND (NOT EXISTS (SELECT 1 FROM @MetricSelection) OR MetricTypeID IN (SELECT MetricTypeID FROM @MetricSelection))
    ORDER BY SortOrder, MetricName;

    SELECT s.SceneID, mt.MetricTypeID, mt.MetricName, COALESCE(smv.Value, mt.DefaultValue) AS Value, smv.Notes
    FROM dbo.Scenes s
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    CROSS JOIN dbo.SceneMetricTypes mt
    LEFT JOIN dbo.SceneMetricValues smv ON smv.SceneID = s.SceneID AND smv.MetricTypeID = mt.MetricTypeID
    WHERE b.ProjectID = @ProjectID
        AND @IncludeMetrics = 1
        AND b.IsArchived = 0
        AND c.IsArchived = 0
        AND s.IsArchived = 0
        AND mt.IsActive = 1
        AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID)
        AND (@BookID IS NULL OR b.BookID = @BookID)
        AND (NOT EXISTS (SELECT 1 FROM @MetricSelection) OR mt.MetricTypeID IN (SELECT MetricTypeID FROM @MetricSelection))
    ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, mt.SortOrder;

    SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
        plt.TypeName AS PlotLineTypeName, pl.PlotImportanceID, pi.ImportanceName AS PlotImportanceName,
        pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
        pl.EmergesFromPlotLineID, emerges.PlotLineName AS EmergesFromPlotLineName,
        pl.SortOrder, pl.Colour, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
    FROM dbo.PlotLines pl
    INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
    INNER JOIN dbo.PlotImportance pi ON pi.PlotImportanceID = pl.PlotImportanceID
    LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
    LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
    LEFT JOIN dbo.PlotLines emerges ON emerges.PlotLineID = pl.EmergesFromPlotLineID
    WHERE @IncludePlotLines = 1
        AND pl.ProjectID = @ProjectID
        AND pl.IsArchived = 0
        AND pl.IsVisibleOnTimeline = 1
        AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
    ORDER BY pl.SortOrder, pl.PlotLineName;

    SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pt.PlotLineID, pl.PlotLineName, pl.Colour,
        te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText,
        te.PlotEventTypeID, pet.TypeName AS PlotEventTypeName, te.TargetPlotLineID, target.PlotLineName AS TargetPlotLineName,
        te.EventTitle, te.EventDescription, te.CreatedDate, te.UpdatedDate
    FROM dbo.ThreadEvents te
    INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
    INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
    INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
    INNER JOIN dbo.PlotEventTypes pet ON pet.PlotEventTypeID = te.PlotEventTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = te.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    LEFT JOIN dbo.PlotLines target ON target.PlotLineID = te.TargetPlotLineID
    WHERE @IncludePlotLines = 1
        AND b.ProjectID = @ProjectID
        AND b.IsArchived = 0
        AND c.IsArchived = 0
        AND s.IsArchived = 0
        AND pl.IsArchived = 0
        AND pl.IsVisibleOnTimeline = 1
        AND pt.IsArchived = 0
        AND (@BookID IS NULL OR b.BookID = @BookID)
    ORDER BY pl.SortOrder, b.SortOrder, c.SortOrder, s.SortOrder, te.CreatedDate;
END;
GO


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

    IF @UserID IS NOT NULL
        AND NOT EXISTS
        (
            SELECT 1
            FROM dbo.ProjectUserAccess
            WHERE ProjectID = @ProjectID
                AND UserID = @UserID
                AND AccessRole = N'Owner'
                AND IsActive = 1
        )
        SET @ProjectID = -1;

    SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived, ArchivedDate, ArchivedReason
    FROM dbo.Projects
    WHERE ProjectID = @ProjectID;

    SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate,
        IsArchived, ArchivedDate, ArchivedReason
    FROM dbo.Books
    WHERE ProjectID = @ProjectID
    ORDER BY SortOrder, BookNumber, BookID;

    SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary, c.SortOrder,
        c.RevisionStatusID, rs.StatusName AS RevisionStatusName, c.CreatedDate, c.UpdatedDate,
        c.IsArchived, c.ArchivedDate, c.ArchivedReason
    FROM dbo.Chapters c
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
    WHERE b.ProjectID = @ProjectID
    ORDER BY b.SortOrder, c.SortOrder, c.ChapterID;

    SELECT s.SceneID, s.ChapterID, s.SceneNumber, s.SceneTitle, s.Summary, s.POVCharacterID, s.PrimaryLocationID,
        l.LocationName AS PrimaryLocationName, l.LocationName AS PrimaryLocationPath, s.SortOrder, s.TimeModeID, tm.TimeModeName,
        s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName,
        s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
        s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate,
        s.IsArchived, s.ArchivedDate, s.ArchivedReason
    FROM dbo.Scenes s
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
    INNER JOIN dbo.TimeConfidences tc ON tc.TimeConfidenceID = s.TimeConfidenceID
    INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
    LEFT JOIN dbo.DurationUnits du ON du.DurationUnitID = s.DurationUnitID
    LEFT JOIN dbo.Locations l ON l.LocationID = s.PrimaryLocationID
    WHERE b.ProjectID = @ProjectID
    ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneID;

    SELECT sp.SceneID, sp.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder
    FROM dbo.ScenePurposes sp
    INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = sp.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY sp.SceneID, spt.SortOrder;

    SELECT so.SceneOutcomeID, so.SceneID, so.OutcomeDescription, so.OutcomeTypeID, so.CreatedDate, so.UpdatedDate
    FROM dbo.SceneOutcomes so
    INNER JOIN dbo.Scenes s ON s.SceneID = so.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY so.SceneID, so.SceneOutcomeID;

    SELECT mt.MetricTypeID, mt.ProjectID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
        mt.DefaultValue, COALESCE(ptms.SortOrder, mt.SortOrder) AS SortOrder, mt.IsActive,
        CAST(CASE WHEN mt.IsActive = 1 AND ptms.MetricTypeID IS NOT NULL THEN 1 ELSE 0 END AS bit) AS IsEnabledForProject,
        CAST(CASE WHEN mt.ProjectID = @ProjectID THEN 1 ELSE 0 END AS bit) AS IsCustom,
        STUFF((
            SELECT N',' + gmp.PresetKey
            FROM dbo.GenreMetricPresetMetrics gpmm
            INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID
            WHERE gpmm.MetricTypeID = mt.MetricTypeID
            ORDER BY gmp.SortOrder, gmp.PresetName
            FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 1, N'') AS GenrePresetKeys,
        STUFF((
            SELECT N', ' + gmp.PresetName
            FROM dbo.GenreMetricPresetMetrics gpmm
            INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID
            WHERE gpmm.MetricTypeID = mt.MetricTypeID
            ORDER BY gmp.SortOrder, gmp.PresetName
            FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 2, N'') AS GenrePresetNames,
        (
            SELECT COUNT(1)
            FROM dbo.SceneMetricValues smv
            INNER JOIN dbo.Scenes s ON s.SceneID = smv.SceneID
            INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
            INNER JOIN dbo.Books b ON b.BookID = c.BookID
            WHERE smv.MetricTypeID = mt.MetricTypeID AND b.ProjectID = @ProjectID
        ) AS ValueCount
    FROM dbo.SceneMetricTypes mt
    LEFT JOIN dbo.ProjectTimelineMetricSettings ptms ON ptms.ProjectID = @ProjectID AND ptms.MetricTypeID = mt.MetricTypeID
    WHERE mt.ProjectID IS NULL
        OR mt.ProjectID = @ProjectID
        OR EXISTS
        (
            SELECT 1
            FROM dbo.SceneMetricValues smv
            INNER JOIN dbo.Scenes s ON s.SceneID = smv.SceneID
            INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
            INNER JOIN dbo.Books b ON b.BookID = c.BookID
            WHERE smv.MetricTypeID = mt.MetricTypeID AND b.ProjectID = @ProjectID
        )
    ORDER BY COALESCE(ptms.SortOrder, mt.SortOrder), mt.MetricName;

    SELECT smv.SceneID, smv.MetricTypeID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
        mt.DefaultValue, mt.SortOrder, mt.IsActive, smv.Value, smv.Notes
    FROM dbo.SceneMetricValues smv
    INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = smv.MetricTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = smv.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY smv.SceneID, mt.SortOrder;

    SELECT ProjectTimelineSettingsID, ProjectID, ShowSceneCards, ShowMetricShape, ShowPlotLines,
        ShowStoryAssets, ShowCharacterAppearances, ShowWarnings, CreatedDate, UpdatedDate
    FROM dbo.ProjectTimelineSettings
    WHERE ProjectID = @ProjectID;

    SELECT ptms.ProjectID, ptms.MetricTypeID, mt.MetricName, ptms.SortOrder, ptms.CreatedDate
    FROM dbo.ProjectTimelineMetricSettings ptms
    INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = ptms.MetricTypeID
    WHERE ptms.ProjectID = @ProjectID
    ORDER BY ptms.SortOrder;

    SELECT TimelineViewPresetID, ProjectID, BookID, PresetName, FilterJson, VisibilityJson, FocusType, FocusID,
        CreatedDate, UpdatedDate, IsArchived
    FROM dbo.TimelineViewPresets
    WHERE ProjectID = @ProjectID
    ORDER BY PresetName;

    SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
        plt.TypeName AS PlotLineTypeName, pl.PlotImportanceID, pi.ImportanceName AS PlotImportanceName,
        pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
        pl.EmergesFromPlotLineID, emerges.PlotLineName AS EmergesFromPlotLineName,
        pl.SortOrder, pl.Colour, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate,
        pl.IsArchived, pl.ArchivedDate, pl.ArchivedReason
    FROM dbo.PlotLines pl
    INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
    INNER JOIN dbo.PlotImportance pi ON pi.PlotImportanceID = pl.PlotImportanceID
    LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
    LEFT JOIN dbo.PlotLines parent ON parent.PlotLineID = pl.ParentPlotLineID
    LEFT JOIN dbo.PlotLines emerges ON emerges.PlotLineID = pl.EmergesFromPlotLineID
    WHERE pl.ProjectID = @ProjectID
    ORDER BY pl.SortOrder, pl.PlotLineName;

    SELECT pt.PlotThreadID, pt.PlotLineID, pl.ProjectID, pl.PlotLineName, pt.ThreadTitle, pt.ThreadTypeID,
        tt.TypeName AS ThreadTypeName, pt.ThreadStatusID, ts.StatusName AS ThreadStatusName, pt.Importance,
        pt.Summary, pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
        pt.CreatedDate, pt.UpdatedDate, pt.IsArchived, pt.ArchivedDate, pt.ArchivedReason
    FROM dbo.PlotThreads pt
    INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
    INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
    INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
    WHERE pl.ProjectID = @ProjectID
    ORDER BY pl.SortOrder, pt.Importance DESC, pt.ThreadTitle;

    SELECT te.ThreadEventID, te.PlotThreadID, pt.ThreadTitle, pl.PlotLineID, pl.PlotLineName, pl.Colour,
        te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText,
        te.PlotEventTypeID, pet.TypeName AS PlotEventTypeName, te.TargetPlotLineID, target.PlotLineName AS TargetPlotLineName,
        te.EventTitle, te.EventDescription, te.CreatedDate, te.UpdatedDate
    FROM dbo.ThreadEvents te
    INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
    INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
    INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
    INNER JOIN dbo.PlotEventTypes pet ON pet.PlotEventTypeID = te.PlotEventTypeID
    LEFT JOIN dbo.PlotLines target ON target.PlotLineID = te.TargetPlotLineID
    WHERE pl.ProjectID = @ProjectID
    ORDER BY te.SceneID, te.ThreadEventID;

    SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description,
        sa.Importance, sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
        l.LocationName AS CurrentLocationName, l.LocationName AS CurrentLocationPath, sa.IsResolved,
        sa.ShowInQuickAddBar, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived, sa.ArchivedDate, sa.ArchivedReason
    FROM dbo.StoryAssets sa
    INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
    LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
    LEFT JOIN dbo.Locations l ON l.LocationID = sa.CurrentLocationID
    WHERE sa.ProjectID = @ProjectID
    ORDER BY sa.Importance DESC, sa.AssetName;

    SELECT ast.AssetStateID, ast.ProjectID, ast.AssetKindID, ak.KindName, ast.StateName, ast.SortOrder, ast.Description,
        ast.IsResolvedState, ast.IsActive
    FROM dbo.AssetStates ast
    LEFT JOIN dbo.AssetKinds ak ON ak.AssetKindID = ast.AssetKindID
    WHERE ast.ProjectID IS NULL
        OR ast.ProjectID = @ProjectID
        OR EXISTS (SELECT 1 FROM dbo.StoryAssets sa WHERE sa.ProjectID = @ProjectID AND sa.CurrentStateID = ast.AssetStateID)
        OR EXISTS (SELECT 1 FROM dbo.AssetEvents ae INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID WHERE sa.ProjectID = @ProjectID AND (ae.FromStateID = ast.AssetStateID OR ae.ToStateID = ast.AssetStateID))
        OR EXISTS (SELECT 1 FROM dbo.AssetDependencies ad INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ad.SourceAssetID WHERE sa.ProjectID = @ProjectID AND (ad.SourceRequiredStateID = ast.AssetStateID OR ad.TargetBlockedStateID = ast.AssetStateID))
    ORDER BY ast.SortOrder, ast.StateName;

    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, b.BookTitle, c.ChapterNumber, s.SceneNumber, s.SceneTitle,
        ae.CreatedDate, ae.UpdatedDate
    FROM dbo.AssetEvents ae
    INNER JOIN dbo.StoryAssets 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
    INNER JOIN dbo.Scenes s ON s.SceneID = ae.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
    LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
    WHERE sa.ProjectID = @ProjectID
    ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, ae.AssetEventID;

    SELECT ad.AssetDependencyID, ad.SourceAssetID, source.AssetName AS SourceAssetName, ad.TargetAssetID,
        target.AssetName AS TargetAssetName, ad.AssetDependencyTypeID, adt.TypeName AS DependencyTypeName,
        adt.IsValidatingDependency, ad.SourceRequiredStateID, srs.StateName AS SourceRequiredStateName,
        ad.TargetBlockedStateID, tbs.StateName AS TargetBlockedStateName, ad.Description,
        ad.CreatedDate, ad.UpdatedDate, ad.IsArchived
    FROM dbo.AssetDependencies ad
    INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
    INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID
    INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID
    LEFT JOIN dbo.AssetStates srs ON srs.AssetStateID = ad.SourceRequiredStateID
    LEFT JOIN dbo.AssetStates tbs ON tbs.AssetStateID = ad.TargetBlockedStateID
    WHERE source.ProjectID = @ProjectID OR target.ProjectID = @ProjectID
    ORDER BY source.AssetName, target.AssetName;

    SELECT ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID, ace.AssetCustodyEventTypeID,
        acet.TypeName AS CustodyEventTypeName, ace.Description,
        STRING_AGG(COALESCE(ch.CharacterName, acec.CharacterNameText), ', ') AS CustodianSummary,
        b.BookTitle, c.ChapterNumber, s.SceneNumber, s.SceneTitle, ace.CreatedDate, ace.UpdatedDate
    FROM dbo.AssetCustodyEvents ace
    INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID
    INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = ace.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    LEFT JOIN dbo.AssetCustodyEventCharacters acec ON acec.AssetCustodyEventID = ace.AssetCustodyEventID
    LEFT JOIN dbo.Characters ch ON ch.CharacterID = acec.CharacterID
    WHERE sa.ProjectID = @ProjectID
    GROUP BY ace.AssetCustodyEventID, ace.StoryAssetID, sa.AssetName, ace.SceneID, ace.AssetCustodyEventTypeID,
        acet.TypeName, ace.Description, b.BookTitle, c.ChapterNumber, s.SceneNumber, s.SceneTitle, ace.CreatedDate, ace.UpdatedDate
    ORDER BY MIN(b.SortOrder), MIN(c.SortOrder), MIN(s.SortOrder), ace.AssetCustodyEventID;

    SELECT acec.AssetCustodyEventCharacterID, acec.AssetCustodyEventID, acec.CharacterID, ch.CharacterName,
        acec.CharacterNameText, acec.CustodyRoleID, cr.RoleName AS CustodyRoleName
    FROM dbo.AssetCustodyEventCharacters acec
    INNER JOIN dbo.AssetCustodyEvents ace ON ace.AssetCustodyEventID = acec.AssetCustodyEventID
    INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID
    INNER JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = acec.CustodyRoleID
    LEFT JOIN dbo.Characters ch ON ch.CharacterID = acec.CharacterID
    WHERE sa.ProjectID = @ProjectID
    ORDER BY acec.AssetCustodyEventID, acec.AssetCustodyEventCharacterID;

    SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart,
        AgeReferenceSceneID, Height, EyeColour, CharacterImportance, ShowInQuickAddBar,
        DefaultDescription, CreatedDate, UpdatedDate, IsArchived, ArchivedDate, ArchivedReason
    FROM dbo.Characters
    WHERE ProjectID = @ProjectID
    ORDER BY CharacterImportance DESC, CharacterName;

    SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, ch.ProjectID, ch.CharacterName, ch.ShortName,
        ch.BirthDate, ch.AgeAtSeriesStart, sc.RoleInSceneTypeID, rist.TypeName AS RoleInSceneTypeName,
        sc.PresenceTypeID, pt.TypeName AS PresenceTypeName, sc.LocationID, loc.LocationName,
        loc.LocationName AS LocationPath, sc.EntryLocationID, entryLoc.LocationName AS EntryLocationName,
        sc.ExitLocationID, exitLoc.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
        sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, s.SceneNumber, s.SceneTitle,
        c.ChapterNumber, b.BookTitle, sc.CreatedDate, sc.UpdatedDate
    FROM dbo.SceneCharacters sc
    INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID
    INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    LEFT JOIN dbo.CharacterRoleInSceneTypes rist ON rist.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
    LEFT JOIN dbo.PresenceTypes pt ON pt.PresenceTypeID = sc.PresenceTypeID
    LEFT JOIN dbo.Locations loc ON loc.LocationID = sc.LocationID
    LEFT JOIN dbo.Locations entryLoc ON entryLoc.LocationID = sc.EntryLocationID
    LEFT JOIN dbo.Locations exitLoc ON exitLoc.LocationID = sc.ExitLocationID
    WHERE ch.ProjectID = @ProjectID
    ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, ch.CharacterName;

    SELECT cat.CharacterAttributeTypeID, cat.ProjectID, cat.AttributeName, cat.DataType, cat.IsNormallyStable,
        cat.SortOrder, cat.IsActive
    FROM dbo.CharacterAttributeTypes cat
    WHERE cat.ProjectID IS NULL
        OR cat.ProjectID = @ProjectID
        OR EXISTS (SELECT 1 FROM dbo.CharacterAttributeEvents cae INNER JOIN dbo.Characters ch ON ch.CharacterID = cae.CharacterID WHERE ch.ProjectID = @ProjectID AND cae.CharacterAttributeTypeID = cat.CharacterAttributeTypeID)
    ORDER BY cat.SortOrder, cat.AttributeName;

    SELECT cae.CharacterAttributeEventID, cae.CharacterID, ch.CharacterName, cae.CharacterAttributeTypeID,
        cat.AttributeName, cae.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, b.BookTitle,
        cae.AttributeValue, cae.Description, cae.CreatedDate, cae.UpdatedDate
    FROM dbo.CharacterAttributeEvents cae
    INNER JOIN dbo.Characters ch ON ch.CharacterID = cae.CharacterID
    INNER JOIN dbo.CharacterAttributeTypes cat ON cat.CharacterAttributeTypeID = cae.CharacterAttributeTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = cae.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE ch.ProjectID = @ProjectID
    ORDER BY ch.CharacterName, b.SortOrder, c.SortOrder, s.SortOrder;

    SELECT ck.CharacterKnowledgeID, ck.CharacterID, ch.CharacterName, ck.StoryAssetID, sa.AssetName,
        ck.PlotThreadID, pt.ThreadTitle, ck.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, b.BookTitle,
        ck.KnowledgeStateID, ks.StateName AS KnowledgeStateName, ck.SourceEventID, ck.Description,
        ck.CreatedDate, ck.UpdatedDate
    FROM dbo.CharacterKnowledge ck
    INNER JOIN dbo.Characters ch ON ch.CharacterID = ck.CharacterID
    INNER JOIN dbo.KnowledgeStates ks ON ks.KnowledgeStateID = ck.KnowledgeStateID
    INNER JOIN dbo.Scenes s ON s.SceneID = ck.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ck.StoryAssetID
    LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = ck.PlotThreadID
    WHERE ch.ProjectID = @ProjectID
    ORDER BY ch.CharacterName, b.SortOrder, c.SortOrder, s.SortOrder;

    SELECT cr.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, a.CharacterName AS CharacterAName,
        cr.CharacterBID, bch.CharacterName AS CharacterBName, cr.RelationshipTypeID, rt.TypeName AS RelationshipTypeName,
        rt.RelationshipCategoryID, rc.CategoryName AS RelationshipCategoryName, rc.SortOrder AS CategorySortOrder,
        rt.SortOrder AS RelationshipTypeSortOrder, cr.IsPermanent, cr.StartSceneID, cr.EndSceneID,
        cr.IsKnownToReader, cr.Notes, cr.IsInitialRelationship, cr.InitialBookID, ib.BookTitle AS InitialBookTitle,
        cr.ReaderInitiallyKnows, cr.InitialRelationshipStateID, irs.StateName AS InitialRelationshipStateName,
        cr.InitialIntensity, cr.IsReciprocal, cr.CreatedDate, cr.UpdatedDate,
        cr.IsArchived, cr.ArchivedDate, cr.ArchivedReason
    FROM dbo.CharacterRelationships cr
    INNER JOIN dbo.Characters a ON a.CharacterID = cr.CharacterAID
    INNER JOIN dbo.Characters bch ON bch.CharacterID = cr.CharacterBID
    INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
    INNER JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
    LEFT JOIN dbo.Books ib ON ib.BookID = cr.InitialBookID
    LEFT JOIN dbo.RelationshipStates irs ON irs.RelationshipStateID = cr.InitialRelationshipStateID
    WHERE cr.ProjectID = @ProjectID
    ORDER BY a.CharacterName, bch.CharacterName, cr.CharacterRelationshipID;

    SELECT re.RelationshipEventID, re.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID,
        a.CharacterName AS CharacterAName, cr.CharacterBID, bch.CharacterName AS CharacterBName,
        rt.TypeName AS RelationshipTypeName, rt.RelationshipCategoryID, rc.CategoryName AS RelationshipCategoryName,
        rc.SortOrder AS CategorySortOrder, rt.SortOrder AS RelationshipTypeSortOrder, cr.IsReciprocal,
        re.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, bk.BookTitle, re.RelationshipStateID,
        rs.StateName AS RelationshipStateName, re.Intensity, re.IsKnownToOtherCharacter, re.Description,
        re.CreatedDate, re.UpdatedDate
    FROM dbo.RelationshipEvents re
    INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID
    INNER JOIN dbo.Characters a ON a.CharacterID = cr.CharacterAID
    INNER JOIN dbo.Characters bch ON bch.CharacterID = cr.CharacterBID
    INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
    INNER JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
    INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
    INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books bk ON bk.BookID = c.BookID
    WHERE cr.ProjectID = @ProjectID
    ORDER BY bk.SortOrder, c.SortOrder, s.SortOrder, re.RelationshipEventID;

    SELECT l.LocationID, l.ProjectID, l.ParentLocationID, parent.LocationName AS ParentLocationName,
        l.LocationName, l.LocationTypeID, lt.TypeName AS LocationTypeName, l.Description,
        l.LocationName AS LocationPath, CAST(0 AS int) AS Depth, l.ShowInQuickAddBar,
        l.CreatedDate, l.UpdatedDate, l.IsArchived, l.ArchivedDate, l.ArchivedReason
    FROM dbo.Locations l
    LEFT JOIN dbo.Locations parent ON parent.LocationID = l.ParentLocationID
    LEFT JOIN dbo.LocationTypes lt ON lt.LocationTypeID = l.LocationTypeID
    WHERE l.ProjectID = @ProjectID
    ORDER BY l.LocationName;

    SELECT lr.LocationRelationshipID, lr.FromLocationID, fl.LocationName AS FromLocationName,
        lr.ToLocationID, tl.LocationName AS ToLocationName, lr.LocationRelationshipTypeID,
        lrt.TypeName AS RelationshipTypeName, lr.IsBidirectional, lr.Strength,
        lr.CanHearNormalSpeech, lr.CanHearLoudNoise, lr.CanSeeMovement, lr.CanSeeClearly,
        lr.CanTravelDirectly, lr.Notes, lr.CreatedDate, lr.UpdatedDate, lr.IsArchived
    FROM dbo.LocationRelationships lr
    INNER JOIN dbo.Locations fl ON fl.LocationID = lr.FromLocationID
    INNER JOIN dbo.Locations tl ON tl.LocationID = lr.ToLocationID
    INNER JOIN dbo.LocationRelationshipTypes lrt ON lrt.LocationRelationshipTypeID = lr.LocationRelationshipTypeID
    WHERE fl.ProjectID = @ProjectID OR tl.ProjectID = @ProjectID
    ORDER BY fl.LocationName, tl.LocationName;

    SELECT sal.SceneAssetLocationID, sal.SceneID, sal.StoryAssetID, sa.AssetName, sal.LocationID,
        l.LocationName, l.LocationName AS LocationPath, sal.Description, sal.CreatedDate, sal.UpdatedDate
    FROM dbo.SceneAssetLocations sal
    INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID
    INNER JOIN dbo.Locations l ON l.LocationID = sal.LocationID
    WHERE sa.ProjectID = @ProjectID
    ORDER BY sal.SceneID, sa.AssetName;

    SELECT sd.SceneDependencyID, ISNULL(sd.ProjectID, @ProjectID) AS ProjectID, sd.SourceSceneID, sd.TargetSceneID,
        sd.SceneDependencyTypeID, sdt.TypeName AS SceneDependencyTypeName, sd.Description,
        sd.IsHardDependency, CAST(NULL AS nvarchar(50)) AS DependencyDirection, sb.BookID AS SourceBookID, sb.BookTitle AS SourceBookTitle,
        sc.ChapterID AS SourceChapterID, sc.ChapterNumber AS SourceChapterNumber, ss.SceneNumber AS SourceSceneNumber,
        ss.SceneTitle AS SourceSceneTitle, tb.BookID AS TargetBookID, tb.BookTitle AS TargetBookTitle,
        tc.ChapterID AS TargetChapterID, tc.ChapterNumber AS TargetChapterNumber, ts.SceneNumber AS TargetSceneNumber,
        ts.SceneTitle AS TargetSceneTitle, sd.CreatedDate, sd.UpdatedDate, sd.IsArchived
    FROM dbo.SceneDependencies sd
    INNER JOIN dbo.SceneDependencyTypes sdt ON sdt.SceneDependencyTypeID = sd.SceneDependencyTypeID
    INNER JOIN dbo.Scenes ss ON ss.SceneID = sd.SourceSceneID
    INNER JOIN dbo.Chapters sc ON sc.ChapterID = ss.ChapterID
    INNER JOIN dbo.Books sb ON sb.BookID = sc.BookID
    INNER JOIN dbo.Scenes ts ON ts.SceneID = sd.TargetSceneID
    INNER JOIN dbo.Chapters tc ON tc.ChapterID = ts.ChapterID
    INNER JOIN dbo.Books tb ON tb.BookID = tc.BookID
    WHERE sd.ProjectID = @ProjectID OR sb.ProjectID = @ProjectID OR tb.ProjectID = @ProjectID
    ORDER BY sb.SortOrder, sc.SortOrder, ss.SortOrder, sd.SceneDependencyID;

    SELECT ValidationRunID, ProjectID, BookID, StartedDate, CompletedDate, TriggeredBy, ScopeDescription
    FROM dbo.ValidationRuns
    WHERE ProjectID = @ProjectID
    ORDER BY StartedDate DESC;

    SELECT cw.ContinuityWarningID, cw.ValidationRunID, cw.ProjectID, cw.BookID, b.BookTitle, cw.ChapterID,
        c.ChapterNumber, c.ChapterTitle, cw.SceneID, s.SceneNumber, s.SceneTitle, cw.EntityType, cw.EntityID,
        cw.WarningTypeID, wt.TypeName AS WarningTypeName, cw.WarningSeverityID, ws.SeverityName,
        cw.Message, cw.Details, cw.IsDismissed, cw.IsIntentional, cw.CreatedDate, cw.LastDetectedDate
    FROM dbo.ContinuityWarnings cw
    INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
    INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
    LEFT JOIN dbo.Books b ON b.BookID = cw.BookID
    LEFT JOIN dbo.Chapters c ON c.ChapterID = cw.ChapterID
    LEFT JOIN dbo.Scenes s ON s.SceneID = cw.SceneID
    WHERE cw.ProjectID = @ProjectID
    ORDER BY cw.LastDetectedDate DESC, cw.ContinuityWarningID;

    SELECT sn.SceneNoteID, sn.SceneID, sn.SceneNoteTypeID, snt.TypeName, sn.NoteTitle, sn.NoteText,
        sn.SortOrder, sn.IsPinned, sn.IsResolved, sn.CreatedDate, sn.UpdatedDate
    FROM dbo.SceneNotes sn
    INNER JOIN dbo.SceneNoteTypes snt ON snt.SceneNoteTypeID = sn.SceneNoteTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = sn.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY sn.SceneID, sn.SortOrder, sn.SceneNoteID;

    SELECT sw.SceneWorkflowID, sw.SceneID, sw.DraftStatus, sw.EstimatedWordCount, sw.ActualWordCount,
        sw.DraftedDate, sw.LastWorkedOn, sw.ReadyForDraft, sw.ReadyForRevision, sw.ReadyForPolish,
        sw.IsBlocked, sw.BlockedReason, sw.Priority, sw.CreatedDate, sw.UpdatedDate
    FROM dbo.SceneWorkflow sw
    INNER JOIN dbo.Scenes s ON s.SceneID = sw.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY sw.SceneID;

    SELECT sci.SceneChecklistItemID, sci.SceneID, sci.ItemText, sci.IsCompleted, sci.SortOrder,
        sci.CreatedDate, sci.UpdatedDate
    FROM dbo.SceneChecklistItems sci
    INNER JOIN dbo.Scenes s ON s.SceneID = sci.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY sci.SceneID, sci.SortOrder, sci.SceneChecklistItemID;

    SELECT sa.SceneAttachmentID, sa.SceneID, sa.SceneAttachmentTypeID, sat.TypeName, sa.Title,
        sa.FilePath, sa.ExternalUrl, sa.Notes, sa.CreatedDate, sa.UpdatedDate
    FROM dbo.SceneAttachments sa
    INNER JOIN dbo.SceneAttachmentTypes sat ON sat.SceneAttachmentTypeID = sa.SceneAttachmentTypeID
    INNER JOIN dbo.Scenes s ON s.SceneID = sa.SceneID
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY sa.SceneID, sa.SceneAttachmentID;

    SELECT cg.ChapterGoalID, cg.ChapterID, cg.GoalSummary, cg.EmotionalGoal, cg.ReaderTakeaway,
        cg.MustInclude, cg.RisksOrConcerns, cg.CreatedDate, cg.UpdatedDate
    FROM dbo.ChapterGoals cg
    INNER JOIN dbo.Chapters c ON c.ChapterID = cg.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE b.ProjectID = @ProjectID
    ORDER BY b.SortOrder, c.SortOrder;

    SELECT sc.ScenarioID, sc.ProjectID, sc.BookID, sc.ScenarioName, sc.Description, sc.BaseCreatedDate,
        sc.IsAppliedToMain, sc.IsArchived, sc.ArchivedDate, sc.ArchivedReason,
        sc.CreatedDate, sc.UpdatedDate, p.ProjectName, b.BookTitle,
        (SELECT COUNT(1) FROM dbo.ScenarioSceneOrder sso WHERE sso.ScenarioID = sc.ScenarioID) AS SceneCount,
        (SELECT COUNT(1) FROM dbo.ScenarioWarnings sw WHERE sw.ScenarioID = sc.ScenarioID) AS WarningCount
    FROM dbo.Scenarios sc
    INNER JOIN dbo.Projects p ON p.ProjectID = sc.ProjectID
    LEFT JOIN dbo.Books b ON b.BookID = sc.BookID
    WHERE sc.ProjectID = @ProjectID
    ORDER BY sc.IsArchived, sc.UpdatedDate DESC;

    SELECT sso.ScenarioSceneOrderID, sso.ScenarioID, sso.SceneID, sso.ProposedChapterID, sso.ProposedSortOrder,
        sso.Notes, sso.CreatedDate, sso.UpdatedDate
    FROM dbo.ScenarioSceneOrder sso
    INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sso.ScenarioID
    WHERE sc.ProjectID = @ProjectID
    ORDER BY sso.ScenarioID, sso.ProposedSortOrder;

    SELECT svr.ScenarioValidationRunID, svr.ScenarioID, svr.StartedDate, svr.CompletedDate, svr.WarningCount
    FROM dbo.ScenarioValidationRuns svr
    INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = svr.ScenarioID
    WHERE sc.ProjectID = @ProjectID
    ORDER BY svr.StartedDate DESC;

    SELECT sw.ScenarioWarningID, sw.ScenarioValidationRunID, sw.ScenarioID, sw.SceneID, sw.EntityType,
        sw.EntityID, sw.WarningTypeName, sw.SeverityName, sw.Message, sw.Details, sw.CreatedDate
    FROM dbo.ScenarioWarnings sw
    INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sw.ScenarioID
    WHERE sc.ProjectID = @ProjectID
    ORDER BY sw.CreatedDate DESC, sw.ScenarioWarningID;

    SELECT GenreMetricPresetID, PresetKey, PresetName, Description, SortOrder, IsActive
    FROM dbo.GenreMetricPresets
    ORDER BY SortOrder, PresetName;

    SELECT gpmm.GenreMetricPresetID, gmp.PresetKey, gmp.PresetName,
        gpmm.MetricTypeID, mt.MetricName, gpmm.SortOrder
    FROM dbo.GenreMetricPresetMetrics gpmm
    INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID
    INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = gpmm.MetricTypeID
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.SceneMetricTypes projectMetric
        WHERE projectMetric.MetricTypeID = gpmm.MetricTypeID
            AND
            (
                projectMetric.ProjectID IS NULL
                OR projectMetric.ProjectID = @ProjectID
                OR EXISTS
                (
                    SELECT 1
                    FROM dbo.ProjectTimelineMetricSettings ptms
                    WHERE ptms.ProjectID = @ProjectID
                        AND ptms.MetricTypeID = projectMetric.MetricTypeID
                )
            )
    )
    ORDER BY gmp.SortOrder, gpmm.SortOrder, mt.MetricName;

    SELECT pua.ProjectUserAccessID, pua.ProjectID, pua.UserID, u.Email, u.DisplayName,
        pua.AccessRole, pua.InvitedByUserID, invitedBy.Email AS InvitedByEmail,
        pua.InvitedDateUTC, pua.AcceptedDateUTC, pua.IsActive
    FROM dbo.ProjectUserAccess pua
    INNER JOIN dbo.AppUser u ON u.UserID = pua.UserID
    LEFT JOIN dbo.AppUser invitedBy ON invitedBy.UserID = pua.InvitedByUserID
    WHERE pua.ProjectID = @ProjectID
        AND pua.IsActive = 1
    ORDER BY CASE WHEN pua.AccessRole = N'Owner' THEN 0 ELSE 1 END, u.Email;

    SELECT UserFileID, UserID, ProjectID, EntityType, EntityID, FileName, OriginalFileName, ContentType,
        FileSizeBytes, StoragePath, UploadedDateUTC, DeletedDateUTC, IsDeleted
    FROM dbo.UserFile
    WHERE ProjectID = @ProjectID
    ORDER BY IsDeleted, UploadedDateUTC, UserFileID;
END;
GO

