SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

IF OBJECT_ID(N'dbo.WarningTypes', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.WarningTypes
    (
        WarningTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WarningTypes PRIMARY KEY,
        TypeName nvarchar(150) NOT NULL,
        Description nvarchar(max) NULL,
        SortOrder int NOT NULL,
        IsActive bit NOT NULL CONSTRAINT DF_WarningTypes_IsActive DEFAULT 1,
        CONSTRAINT UQ_WarningTypes_TypeName UNIQUE (TypeName)
    );
END;
GO

IF OBJECT_ID(N'dbo.WarningSeverities', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.WarningSeverities
    (
        WarningSeverityID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WarningSeverities PRIMARY KEY,
        SeverityName nvarchar(50) NOT NULL,
        SortOrder int NOT NULL,
        IsActive bit NOT NULL CONSTRAINT DF_WarningSeverities_IsActive DEFAULT 1,
        CONSTRAINT UQ_WarningSeverities_SeverityName UNIQUE (SeverityName)
    );
END;
GO

IF OBJECT_ID(N'dbo.ValidationRuns', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.ValidationRuns
    (
        ValidationRunID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ValidationRuns PRIMARY KEY,
        ProjectID int NOT NULL,
        BookID int NULL,
        StartedDate datetime2 NOT NULL CONSTRAINT DF_ValidationRuns_StartedDate DEFAULT SYSUTCDATETIME(),
        CompletedDate datetime2 NULL,
        TriggeredBy nvarchar(100) NULL,
        ScopeDescription nvarchar(200) NULL,
        CONSTRAINT FK_ValidationRuns_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
        CONSTRAINT FK_ValidationRuns_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID)
    );
END;
GO

IF OBJECT_ID(N'dbo.ContinuityWarnings', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.ContinuityWarnings
    (
        ContinuityWarningID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ContinuityWarnings PRIMARY KEY,
        ValidationRunID int NULL,
        ProjectID int NOT NULL,
        BookID int NULL,
        ChapterID int NULL,
        SceneID int NULL,
        EntityType nvarchar(80) NOT NULL,
        EntityID int NULL,
        WarningTypeID int NOT NULL,
        WarningSeverityID int NOT NULL,
        Message nvarchar(500) NOT NULL,
        Details nvarchar(max) NULL,
        IsDismissed bit NOT NULL CONSTRAINT DF_ContinuityWarnings_IsDismissed DEFAULT 0,
        IsIntentional bit NOT NULL CONSTRAINT DF_ContinuityWarnings_IsIntentional DEFAULT 0,
        CreatedDate datetime2 NOT NULL CONSTRAINT DF_ContinuityWarnings_CreatedDate DEFAULT SYSUTCDATETIME(),
        LastDetectedDate datetime2 NOT NULL CONSTRAINT DF_ContinuityWarnings_LastDetectedDate DEFAULT SYSUTCDATETIME(),
        CONSTRAINT FK_ContinuityWarnings_ValidationRuns FOREIGN KEY (ValidationRunID) REFERENCES dbo.ValidationRuns(ValidationRunID),
        CONSTRAINT FK_ContinuityWarnings_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
        CONSTRAINT FK_ContinuityWarnings_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID),
        CONSTRAINT FK_ContinuityWarnings_Chapters FOREIGN KEY (ChapterID) REFERENCES dbo.Chapters(ChapterID),
        CONSTRAINT FK_ContinuityWarnings_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
        CONSTRAINT FK_ContinuityWarnings_Types FOREIGN KEY (WarningTypeID) REFERENCES dbo.WarningTypes(WarningTypeID),
        CONSTRAINT FK_ContinuityWarnings_Severities FOREIGN KEY (WarningSeverityID) REFERENCES dbo.WarningSeverities(WarningSeverityID)
    );
END;
GO

IF OBJECT_ID(N'dbo.SceneDependencyTypes', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.SceneDependencyTypes
    (
        SceneDependencyTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneDependencyTypes PRIMARY KEY,
        TypeName nvarchar(100) NOT NULL,
        SortOrder int NOT NULL,
        IsActive bit NOT NULL CONSTRAINT DF_SceneDependencyTypes_IsActive DEFAULT 1,
        CONSTRAINT UQ_SceneDependencyTypes_TypeName UNIQUE (TypeName)
    );
END;
GO

IF OBJECT_ID(N'dbo.SceneDependencies', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.SceneDependencies
    (
        SceneDependencyID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneDependencies PRIMARY KEY,
        SourceSceneID int NOT NULL,
        TargetSceneID int NOT NULL,
        SceneDependencyTypeID int NOT NULL,
        Description nvarchar(max) NULL,
        CreatedDate datetime2 NOT NULL CONSTRAINT DF_SceneDependencies_CreatedDate DEFAULT SYSUTCDATETIME(),
        UpdatedDate datetime2 NOT NULL CONSTRAINT DF_SceneDependencies_UpdatedDate DEFAULT SYSUTCDATETIME(),
        IsArchived bit NOT NULL CONSTRAINT DF_SceneDependencies_IsArchived DEFAULT 0,
        CONSTRAINT FK_SceneDependencies_SourceScene FOREIGN KEY (SourceSceneID) REFERENCES dbo.Scenes(SceneID),
        CONSTRAINT FK_SceneDependencies_TargetScene FOREIGN KEY (TargetSceneID) REFERENCES dbo.Scenes(SceneID),
        CONSTRAINT FK_SceneDependencies_Types FOREIGN KEY (SceneDependencyTypeID) REFERENCES dbo.SceneDependencyTypes(SceneDependencyTypeID)
    );
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ContinuityWarnings_Project' AND object_id = OBJECT_ID(N'dbo.ContinuityWarnings'))
    CREATE INDEX IX_ContinuityWarnings_Project ON dbo.ContinuityWarnings(ProjectID, BookID, SceneID, IsDismissed, IsIntentional, WarningSeverityID);
GO

MERGE dbo.WarningSeverities AS target
USING (VALUES (N'Info', 10), (N'Warning', 20), (N'Error', 30)) AS source (SeverityName, SortOrder)
ON target.SeverityName = source.SeverityName
WHEN NOT MATCHED THEN INSERT (SeverityName, SortOrder) VALUES (source.SeverityName, source.SortOrder);
GO

MERGE dbo.WarningTypes AS target
USING (VALUES
    (N'Thread Resolved Before Introduced', N'Plot thread appears resolved before introduction.', 10),
    (N'Question Answered Before Raised', N'Question event appears answered before raised.', 20),
    (N'Clue Without Payoff', N'Clue has no payoff or resolution event.', 30),
    (N'Thread Dormant', N'Open plot thread has gone quiet for many scenes.', 40),
    (N'Asset Used Before Introduced', N'Asset appears used before it is introduced.', 50),
    (N'Asset State Dependency Missing', N'Asset dependency may not have been satisfied.', 60),
    (N'Asset Custody Conflict', N'Asset custody may not match use.', 70),
    (N'Asset Location Conflict', N'Asset location changes without a movement event.', 80),
    (N'Character Knowledge Conflict', N'Character knowledge may need a record.', 90),
    (N'Scene Dependency Broken', N'Scene dependency order may be broken.', 100),
    (N'Character Location Conflict', N'Character appears in conflicting scene locations.', 110),
    (N'Scene Date Order Conflict', N'Narrative order and exact dates may conflict.', 120),
    (N'Long Unexplained Time Gap', N'Long gap without time passage explanation.', 130),
    (N'Sustained High Intensity', N'Metric remains high for many scenes.', 140),
    (N'Sustained Low Intensity', N'Metric remains low for many scenes.', 150),
    (N'Missing Scene Location', N'Scene has no primary location.', 160),
    (N'Missing Scene Purpose', N'Scene has no purpose.', 170),
    (N'Missing Scene Outcome', N'Scene has no outcome.', 180),
    (N'Too Many Open Major Threads', N'Many major plot threads appear open at the same time.', 190)
) AS source (TypeName, Description, SortOrder)
ON target.TypeName = source.TypeName
WHEN NOT MATCHED THEN INSERT (TypeName, Description, SortOrder) VALUES (source.TypeName, source.Description, source.SortOrder);
GO

MERGE dbo.SceneDependencyTypes AS target
USING (VALUES
    (N'Must Occur Before', 10), (N'Emotional Setup', 20), (N'Reveal Setup', 30), (N'Action Consequence', 40),
    (N'Travel Dependency', 50), (N'Relationship Dependency', 60), (N'Knowledge Dependency', 70), (N'Object Dependency', 80)
) AS source (TypeName, SortOrder)
ON target.TypeName = source.TypeName
WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder);
GO

CREATE OR ALTER PROCEDURE dbo.WarningLookup_All
AS
BEGIN
    SET NOCOUNT ON;
    SELECT WarningTypeID, TypeName, Description, SortOrder, IsActive FROM dbo.WarningTypes WHERE IsActive = 1 ORDER BY SortOrder;
    SELECT WarningSeverityID, SeverityName, SortOrder, IsActive FROM dbo.WarningSeverities WHERE IsActive = 1 ORDER BY SortOrder;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ValidationRun_Create
    @ProjectID int,
    @BookID int = NULL,
    @TriggeredBy nvarchar(100) = NULL,
    @ScopeDescription nvarchar(200) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    INSERT dbo.ValidationRuns (ProjectID, BookID, TriggeredBy, ScopeDescription)
    VALUES (@ProjectID, @BookID, @TriggeredBy, @ScopeDescription);
    DECLARE @ValidationRunID int = CAST(SCOPE_IDENTITY() AS int);
    RETURN @ValidationRunID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ValidationRun_Complete
    @ValidationRunID int
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE dbo.ValidationRuns SET CompletedDate = SYSUTCDATETIME() WHERE ValidationRunID = @ValidationRunID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ClearActive
    @ProjectID int,
    @BookID int = NULL,
    @SceneID int = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DELETE cw
    FROM dbo.ContinuityWarnings cw
    WHERE cw.ProjectID = @ProjectID
        AND (@BookID IS NULL OR cw.BookID = @BookID)
        AND (@SceneID IS NULL OR cw.SceneID = @SceneID)
        AND cw.IsDismissed = 0
        AND cw.IsIntentional = 0;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Upsert
    @ValidationRunID int = NULL,
    @ProjectID int,
    @BookID int = NULL,
    @ChapterID int = NULL,
    @SceneID int = NULL,
    @EntityType nvarchar(80),
    @EntityID int = NULL,
    @WarningTypeName nvarchar(150),
    @SeverityName nvarchar(50),
    @Message nvarchar(500),
    @Details nvarchar(max) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @WarningTypeID int = (SELECT WarningTypeID FROM dbo.WarningTypes WHERE TypeName = @WarningTypeName);
    DECLARE @WarningSeverityID int = (SELECT WarningSeverityID FROM dbo.WarningSeverities WHERE SeverityName = @SeverityName);

    IF @WarningTypeID IS NULL OR @WarningSeverityID IS NULL
        RETURN;

    DECLARE @ExistingID int;
    SELECT TOP (1) @ExistingID = ContinuityWarningID
    FROM dbo.ContinuityWarnings
    WHERE ProjectID = @ProjectID
        AND ISNULL(BookID, 0) = ISNULL(@BookID, 0)
        AND ISNULL(ChapterID, 0) = ISNULL(@ChapterID, 0)
        AND ISNULL(SceneID, 0) = ISNULL(@SceneID, 0)
        AND EntityType = @EntityType
        AND ISNULL(EntityID, 0) = ISNULL(@EntityID, 0)
        AND WarningTypeID = @WarningTypeID
        AND IsDismissed = 0
        AND IsIntentional = 0;

    IF @ExistingID IS NULL
    BEGIN
        INSERT dbo.ContinuityWarnings (ValidationRunID, ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
        VALUES (@ValidationRunID, @ProjectID, @BookID, @ChapterID, @SceneID, @EntityType, @EntityID, @WarningTypeID, @WarningSeverityID, @Message, @Details);
    END
    ELSE
    BEGIN
        UPDATE dbo.ContinuityWarnings
        SET ValidationRunID = @ValidationRunID,
            WarningSeverityID = @WarningSeverityID,
            Message = @Message,
            Details = @Details,
            LastDetectedDate = SYSUTCDATETIME()
        WHERE ContinuityWarningID = @ExistingID;
    END
END;
GO

CREATE OR ALTER VIEW dbo.NarrativeSceneOrder
AS
SELECT p.ProjectID, b.BookID, c.ChapterID, s.SceneID,
    b.SortOrder AS BookSortOrder, c.SortOrder AS ChapterSortOrder, s.SortOrder AS SceneSortOrder,
    ROW_NUMBER() OVER (PARTITION BY p.ProjectID ORDER BY b.SortOrder, b.BookNumber, c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID) AS GlobalSceneIndex,
    ROW_NUMBER() OVER (PARTITION BY b.BookID ORDER BY c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID) AS BookSceneIndex,
    b.BookTitle, b.BookNumber, c.ChapterNumber, c.ChapterTitle, s.SceneNumber, s.SceneTitle,
    s.PrimaryLocationID, s.StartDateTime, s.RelativeTimeText, s.ScenePurposeNotes, s.SceneOutcomeNotes
FROM dbo.Projects p
INNER JOIN dbo.Books b ON b.ProjectID = p.ProjectID AND b.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.BookID = b.BookID AND c.IsArchived = 0
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0
WHERE p.IsArchived = 0;
GO

CREATE OR ALTER PROCEDURE dbo.NarrativeSceneOrder_GetByProject
    @ProjectID int,
    @BookID int = NULL
AS
BEGIN
    SET NOCOUNT ON;
    SELECT * FROM dbo.NarrativeSceneOrder
    WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
    ORDER BY GlobalSceneIndex;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateProject
    @ProjectID int,
    @BookID int = NULL,
    @SceneID int = NULL,
    @TriggeredBy nvarchar(100) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @ValidationRunID int;
    DECLARE @Scope nvarchar(200) = CASE WHEN @SceneID IS NOT NULL THEN N'Scene validation' WHEN @BookID IS NOT NULL THEN N'Book validation' ELSE N'Project validation' END;
    EXEC @ValidationRunID = dbo.ValidationRun_Create @ProjectID = @ProjectID, @BookID = @BookID, @TriggeredBy = @TriggeredBy, @ScopeDescription = @Scope;
    EXEC dbo.ContinuityWarning_ClearActive @ProjectID = @ProjectID, @BookID = @BookID, @SceneID = @SceneID;

    DECLARE @Warnings TABLE
    (
        BookID int NULL, ChapterID int NULL, SceneID int NULL, EntityType nvarchar(80), EntityID int NULL,
        WarningTypeName nvarchar(150), SeverityName nvarchar(50), Message nvarchar(500), Details nvarchar(max) NULL
    );

    ;WITH ScopeScenes AS
    (
        SELECT * FROM dbo.NarrativeSceneOrder
        WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Missing Scene Location', N'Info',
        N'Possible issue: this scene has no primary location yet.',
        N'Add a primary location if the scene depends on where characters or assets are.'
    FROM ScopeScenes WHERE PrimaryLocationID IS NULL;

    ;WITH ScopeScenes AS
    (
        SELECT nso.*
        FROM dbo.NarrativeSceneOrder nso
        WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR nso.SceneID = @SceneID)
    )
    INSERT @Warnings
    SELECT ss.BookID, ss.ChapterID, ss.SceneID, N'Scene', ss.SceneID, N'Missing Scene Purpose', N'Info',
        N'Possible issue: this scene has no purpose label or purpose notes.',
        N'Add a scene purpose if this is not intentional.'
    FROM ScopeScenes ss
    WHERE ISNULL(NULLIF(LTRIM(RTRIM(ss.ScenePurposeNotes)), N''), N'') = N''
        AND NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = ss.SceneID);

    ;WITH ScopeScenes AS
    (
        SELECT * FROM dbo.NarrativeSceneOrder
        WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Missing Scene Outcome', N'Info',
        N'Possible issue: this scene has no outcome notes.',
        N'Add an outcome if the result of the scene needs tracking.'
    FROM ScopeScenes WHERE ISNULL(NULLIF(LTRIM(RTRIM(SceneOutcomeNotes)), N''), N'') = N'';

    ;WITH Ordered AS
    (
        SELECT *, LAG(SceneID) OVER (ORDER BY GlobalSceneIndex) AS PreviousSceneID,
            LAG(StartDateTime) OVER (ORDER BY GlobalSceneIndex) AS PreviousStartDateTime
        FROM dbo.NarrativeSceneOrder
        WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Scene Date Order Conflict', N'Warning',
        N'Possible issue: this narrative scene has an earlier story date than the scene before it.',
        CONCAT(N'Previous date: ', CONVERT(nvarchar(30), PreviousStartDateTime, 120), N'. Current date: ', CONVERT(nvarchar(30), StartDateTime, 120), N'.')
    FROM Ordered
    WHERE (@SceneID IS NULL OR SceneID = @SceneID)
        AND PreviousStartDateTime IS NOT NULL AND StartDateTime IS NOT NULL AND StartDateTime < PreviousStartDateTime;

    ;WITH Ordered AS
    (
        SELECT *, LAG(StartDateTime) OVER (ORDER BY GlobalSceneIndex) AS PreviousStartDateTime
        FROM dbo.NarrativeSceneOrder
        WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Long Unexplained Time Gap', N'Info',
        N'Possible issue: there is a long story-time gap before this scene without a clear time-passage note.',
        CONCAT(N'Gap: ', DATEDIFF(day, PreviousStartDateTime, StartDateTime), N' days.')
    FROM Ordered o
    WHERE (@SceneID IS NULL OR SceneID = @SceneID)
        AND PreviousStartDateTime IS NOT NULL AND StartDateTime IS NOT NULL
        AND DATEDIFF(day, PreviousStartDateTime, StartDateTime) > 14
        AND ISNULL(NULLIF(LTRIM(RTRIM(RelativeTimeText)), N''), N'') = N''
        AND NOT EXISTS (
            SELECT 1 FROM dbo.ScenePurposes sp
            INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID
            WHERE sp.SceneID = o.SceneID AND spt.PurposeName = N'Time Passage'
        );

    ;WITH Events AS
    (
        SELECT pt.PlotThreadID, pt.ThreadTitle, pt.Importance, ts.IsOpenStatus, te.SceneID, tet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
        FROM dbo.ThreadEvents te
        INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
        INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0
        INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
        INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
        WHERE (@BookID IS NULL OR nso.BookID = @BookID)
    ),
    ThreadRanges AS
    (
        SELECT PlotThreadID, ThreadTitle, Importance, IsOpenStatus,
            MIN(CASE WHEN TypeName = N'Introduced' THEN GlobalSceneIndex END) AS IntroducedIndex,
            MIN(CASE WHEN TypeName IN (N'Resolved', N'Payoff') THEN GlobalSceneIndex END) AS ResolvedIndex,
            MIN(CASE WHEN TypeName = N'Question Raised' THEN GlobalSceneIndex END) AS QuestionRaisedIndex,
            MIN(CASE WHEN TypeName = N'Question Answered' THEN GlobalSceneIndex END) AS QuestionAnsweredIndex,
            MAX(GlobalSceneIndex) AS LastEventIndex,
            MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID, MIN(SceneID) AS AnySceneID
        FROM Events GROUP BY PlotThreadID, ThreadTitle, Importance, IsOpenStatus
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, AnySceneID, N'PlotThread', PlotThreadID, N'Thread Resolved Before Introduced', N'Warning',
        CONCAT(N'Possible issue: "', ThreadTitle, N'" appears resolved before it is introduced.'),
        NULL
    FROM ThreadRanges
    WHERE ResolvedIndex IS NOT NULL AND IntroducedIndex IS NOT NULL AND ResolvedIndex < IntroducedIndex;

    ;WITH Events AS
    (
        SELECT pt.PlotThreadID, pt.ThreadTitle, te.SceneID, tet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
        FROM dbo.ThreadEvents te
        INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
        INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0
        INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
        WHERE (@BookID IS NULL OR nso.BookID = @BookID)
    ),
    ThreadRanges AS
    (
        SELECT PlotThreadID, ThreadTitle,
            MIN(CASE WHEN TypeName = N'Question Raised' THEN GlobalSceneIndex END) AS QuestionRaisedIndex,
            MIN(CASE WHEN TypeName = N'Question Answered' THEN GlobalSceneIndex END) AS QuestionAnsweredIndex,
            MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID, MIN(SceneID) AS AnySceneID
        FROM Events GROUP BY PlotThreadID, ThreadTitle
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, AnySceneID, N'PlotThread', PlotThreadID, N'Question Answered Before Raised', N'Warning',
        CONCAT(N'Possible issue: "', ThreadTitle, N'" is answered before it is raised.'),
        NULL
    FROM ThreadRanges
    WHERE QuestionAnsweredIndex IS NOT NULL AND QuestionRaisedIndex IS NOT NULL AND QuestionAnsweredIndex < QuestionRaisedIndex;

    ;WITH Clues AS
    (
        SELECT pt.PlotThreadID, pt.ThreadTitle, MIN(nso.SceneID) AS SceneID, MIN(nso.BookID) AS BookID, MIN(nso.ChapterID) AS ChapterID
        FROM dbo.ThreadEvents te
        INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
        INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0
        INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
        WHERE tet.TypeName = N'Clue Planted' AND (@BookID IS NULL OR nso.BookID = @BookID)
        GROUP BY pt.PlotThreadID, pt.ThreadTitle
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'PlotThread', PlotThreadID, N'Clue Without Payoff', N'Info',
        CONCAT(N'Possible issue: clue thread "', ThreadTitle, N'" has no payoff, answer, reveal, or resolution yet.'),
        NULL
    FROM Clues c
    WHERE NOT EXISTS (
        SELECT 1 FROM dbo.ThreadEvents te
        INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
        WHERE te.PlotThreadID = c.PlotThreadID AND tet.TypeName IN (N'Payoff', N'Question Answered', N'Revealed', N'Resolved')
    );

    ;WITH LastThreadEvent AS
    (
        SELECT pt.PlotThreadID, pt.ThreadTitle, MAX(nso.GlobalSceneIndex) AS LastEventIndex, MAX(nso.BookID) AS BookID, MAX(nso.ChapterID) AS ChapterID, MAX(nso.SceneID) AS SceneID
        FROM dbo.PlotThreads pt
        INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID AND ts.IsOpenStatus = 1
        INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
        INNER JOIN dbo.ThreadEvents te ON te.PlotThreadID = pt.PlotThreadID
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
        WHERE pt.IsArchived = 0 AND (@BookID IS NULL OR nso.BookID = @BookID)
        GROUP BY pt.PlotThreadID, pt.ThreadTitle
    ),
    MaxScope AS
    (
        SELECT MAX(GlobalSceneIndex) AS MaxSceneIndex FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'PlotThread', PlotThreadID, N'Thread Dormant', N'Info',
        CONCAT(N'Possible issue: "', ThreadTitle, N'" has no event for more than 8 scenes.'),
        CONCAT(N'Scenes since last event: ', MaxSceneIndex - LastEventIndex, N'.')
    FROM LastThreadEvent CROSS JOIN MaxScope
    WHERE MaxSceneIndex - LastEventIndex > 8;

    ;WITH OpenMajor AS
    (
        SELECT COUNT(*) AS OpenCount
        FROM dbo.PlotThreads pt
        INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID AND ts.IsOpenStatus = 1
        INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
        WHERE pt.IsArchived = 0 AND pt.Importance >= 8 AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
    )
    INSERT @Warnings
    SELECT @BookID, NULL, NULL, N'Project', @ProjectID, N'Too Many Open Major Threads', N'Info',
        CONCAT(N'Possible issue: there are ', OpenCount, N' high-importance open threads.'),
        N'This may be intentional, but it can make the active story load feel heavy.'
    FROM OpenMajor WHERE OpenCount > 10;

    ;WITH AssetEventsOrdered AS
    (
        SELECT ae.StoryAssetID, sa.AssetName, ae.SceneID, aet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
        FROM dbo.AssetEvents ae
        INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0
        INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID
        WHERE (@BookID IS NULL OR nso.BookID = @BookID)
    ),
    AssetFirsts AS
    (
        SELECT StoryAssetID, AssetName,
            MIN(CASE WHEN TypeName IN (N'First Mentioned', N'Mentioned', N'Found') THEN GlobalSceneIndex END) AS IntroducedIndex,
            MIN(CASE WHEN TypeName IN (N'Used', N'Opened', N'Revealed', N'Destroyed', N'Given', N'Taken', N'Hidden', N'Lost', N'Stolen') THEN GlobalSceneIndex END) AS UsedIndex,
            MIN(CASE WHEN TypeName IN (N'Used', N'Opened', N'Revealed', N'Destroyed', N'Given', N'Taken', N'Hidden', N'Lost', N'Stolen') THEN SceneID END) AS UsedSceneID,
            MIN(BookID) AS BookID, MIN(ChapterID) AS ChapterID
        FROM AssetEventsOrdered GROUP BY StoryAssetID, AssetName
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, UsedSceneID, N'StoryAsset', StoryAssetID, N'Asset Used Before Introduced', N'Warning',
        CONCAT(N'Possible issue: "', AssetName, N'" appears used before it is introduced.'),
        NULL
    FROM AssetFirsts
    WHERE UsedIndex IS NOT NULL AND (IntroducedIndex IS NULL OR UsedIndex < IntroducedIndex);

    ;WITH TargetEvents AS
    (
        SELECT ad.AssetDependencyID, ad.SourceAssetID, source.AssetName AS SourceAssetName, ad.TargetAssetID, target.AssetName AS TargetAssetName,
            ad.SourceRequiredStateID, ad.TargetBlockedStateID, ae.SceneID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex
        FROM dbo.AssetDependencies ad
        INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID AND adt.IsValidatingDependency = 1
        INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
        INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID AND target.ProjectID = @ProjectID
        INNER JOIN dbo.AssetEvents ae ON ae.StoryAssetID = ad.TargetAssetID AND ae.ToStateID = ad.TargetBlockedStateID
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID
        WHERE ad.IsArchived = 0 AND ad.SourceRequiredStateID IS NOT NULL AND ad.TargetBlockedStateID IS NOT NULL AND (@BookID IS NULL OR nso.BookID = @BookID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'AssetDependency', AssetDependencyID, N'Asset State Dependency Missing', N'Warning',
        CONCAT(N'Possible issue: "', TargetAssetName, N'" reaches a blocked state before "', SourceAssetName, N'" reaches its required state.'),
        NULL
    FROM TargetEvents te
    WHERE NOT EXISTS (
        SELECT 1 FROM dbo.AssetEvents sourceEvent
        INNER JOIN dbo.NarrativeSceneOrder sourceScene ON sourceScene.SceneID = sourceEvent.SceneID
        WHERE sourceEvent.StoryAssetID = te.SourceAssetID
            AND sourceEvent.ToStateID = te.SourceRequiredStateID
            AND sourceScene.GlobalSceneIndex <= te.GlobalSceneIndex
    );

    ;WITH AssetPlaces AS
    (
        SELECT sal.StoryAssetID, sa.AssetName, sal.LocationID, sal.SceneID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
            LAG(sal.LocationID) OVER (PARTITION BY sal.StoryAssetID ORDER BY nso.GlobalSceneIndex) AS PreviousLocationID
        FROM dbo.SceneAssetLocations sal
        INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sal.SceneID
        WHERE (@BookID IS NULL OR nso.BookID = @BookID)
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'StoryAsset', StoryAssetID, N'Asset Location Conflict', N'Warning',
        CONCAT(N'Possible issue: "', AssetName, N'" appears in a new location without a movement-style event.'),
        N'Add a Move, Given, Taken, Found, or Location Changed event if the location change needs explaining.'
    FROM AssetPlaces ap
    WHERE PreviousLocationID IS NOT NULL AND PreviousLocationID <> LocationID
        AND NOT EXISTS (
            SELECT 1 FROM dbo.AssetEvents ae
            INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
            WHERE ae.StoryAssetID = ap.StoryAssetID AND ae.SceneID = ap.SceneID AND aet.TypeName IN (N'Moved', N'Given', N'Taken', N'Found', N'Location Changed')
        );

    INSERT @Warnings
    SELECT DISTINCT nso.BookID, nso.ChapterID, sc.SceneID, N'Character', sc.CharacterID, N'Character Knowledge Conflict', N'Info',
        CONCAT(N'Possible issue: ', ch.CharacterName, N' has knowledge notes in this scene but no structured knowledge record.'),
        N'This is only a low-severity prompt to add Character Knowledge if useful.'
    FROM dbo.SceneCharacters sc
    INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID
    INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
    WHERE ch.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID)
        AND ISNULL(NULLIF(LTRIM(RTRIM(sc.KnowledgeNotes)), N''), N'') <> N''
        AND NOT EXISTS (SELECT 1 FROM dbo.CharacterKnowledge ck WHERE ck.SceneID = sc.SceneID AND ck.CharacterID = sc.CharacterID);

    INSERT @Warnings
    SELECT targetOrder.BookID, targetOrder.ChapterID, sd.TargetSceneID, N'SceneDependency', sd.SceneDependencyID, N'Scene Dependency Broken', N'Warning',
        N'Possible issue: a scene dependency says an earlier setup should occur before this scene, but the order is reversed.',
        sd.Description
    FROM dbo.SceneDependencies sd
    INNER JOIN dbo.NarrativeSceneOrder sourceOrder ON sourceOrder.SceneID = sd.SourceSceneID
    INNER JOIN dbo.NarrativeSceneOrder targetOrder ON targetOrder.SceneID = sd.TargetSceneID
    WHERE sd.IsArchived = 0 AND sourceOrder.ProjectID = @ProjectID
        AND (@BookID IS NULL OR targetOrder.BookID = @BookID) AND (@SceneID IS NULL OR targetOrder.SceneID = @SceneID)
        AND sourceOrder.GlobalSceneIndex > targetOrder.GlobalSceneIndex;

    ;WITH CharacterSceneLocations AS
    (
        SELECT sc.SceneID, sc.CharacterID, COUNT(DISTINCT sc.LocationID) AS LocationCount, MAX(ch.CharacterName) AS CharacterName,
            MAX(nso.BookID) AS BookID, MAX(nso.ChapterID) AS ChapterID
        FROM dbo.SceneCharacters sc
        INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID AND ch.ProjectID = @ProjectID
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
        LEFT JOIN dbo.PresenceTypes pt ON pt.PresenceTypeID = sc.PresenceTypeID
        WHERE sc.LocationID IS NOT NULL AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID)
            AND ISNULL(pt.TypeName, N'') NOT IN (N'Nearby', N'Hidden', N'Overhearing', N'Off-screen')
        GROUP BY sc.SceneID, sc.CharacterID
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, SceneID, N'Character', CharacterID, N'Character Location Conflict', N'Warning',
        CONCAT(N'Possible issue: ', CharacterName, N' appears in more than one location in the same scene.'),
        NULL
    FROM CharacterSceneLocations WHERE LocationCount > 1;

    ;WITH MetricRows AS
    (
        SELECT smv.MetricTypeID, smt.MetricName, smv.SceneID, smv.Value, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
            ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID ORDER BY nso.GlobalSceneIndex) AS rn,
            ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID, CASE WHEN smv.Value >= 8 THEN 1 ELSE 0 END ORDER BY nso.GlobalSceneIndex) AS grpRn
        FROM dbo.SceneMetricValues smv
        INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = smv.SceneID
        WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
    ),
    HighGroups AS
    (
        SELECT MetricTypeID, MetricName, rn - grpRn AS grp, COUNT(*) AS SceneCount, MIN(SceneID) AS FirstSceneID, MAX(SceneID) AS LastSceneID,
            MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID
        FROM MetricRows WHERE Value >= 8 GROUP BY MetricTypeID, MetricName, rn - grpRn HAVING COUNT(*) > 6
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, LastSceneID, N'Metric', MetricTypeID, N'Sustained High Intensity', N'Info',
        CONCAT(N'Possible reader fatigue: ', MetricName, N' remains high for ', SceneCount, N' consecutive scenes.'),
        CONCAT(N'Scene range: ', FirstSceneID, N' to ', LastSceneID, N'.')
    FROM HighGroups;

    ;WITH MetricRows AS
    (
        SELECT smv.MetricTypeID, smt.MetricName, smv.SceneID, smv.Value, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
            ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID ORDER BY nso.GlobalSceneIndex) AS rn,
            ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID, CASE WHEN smv.Value <= 2 THEN 1 ELSE 0 END ORDER BY nso.GlobalSceneIndex) AS grpRn
        FROM dbo.SceneMetricValues smv
        INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
        INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = smv.SceneID
        WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
            AND smt.MetricName IN (N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action')
    ),
    LowGroups AS
    (
        SELECT MetricTypeID, MetricName, rn - grpRn AS grp, COUNT(*) AS SceneCount, MIN(SceneID) AS FirstSceneID, MAX(SceneID) AS LastSceneID,
            MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID
        FROM MetricRows WHERE Value <= 2 GROUP BY MetricTypeID, MetricName, rn - grpRn HAVING COUNT(*) > 8
    )
    INSERT @Warnings
    SELECT BookID, ChapterID, LastSceneID, N'Metric', MetricTypeID, N'Sustained Low Intensity', N'Info',
        CONCAT(N'Possible issue: ', MetricName, N' remains low for ', SceneCount, N' consecutive scenes.'),
        CONCAT(N'Scene range: ', FirstSceneID, N' to ', LastSceneID, N'.')
    FROM LowGroups;

    INSERT dbo.ContinuityWarnings (ValidationRunID, ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
    SELECT @ValidationRunID, @ProjectID, w.BookID, w.ChapterID, w.SceneID, w.EntityType, w.EntityID, wt.WarningTypeID, ws.WarningSeverityID, w.Message, w.Details
    FROM @Warnings w
    INNER JOIN dbo.WarningTypes wt ON wt.TypeName = w.WarningTypeName
    INNER JOIN dbo.WarningSeverities ws ON ws.SeverityName = w.SeverityName
    WHERE NOT EXISTS
    (
        SELECT 1 FROM dbo.ContinuityWarnings existing
        WHERE existing.ProjectID = @ProjectID
            AND ISNULL(existing.BookID, 0) = ISNULL(w.BookID, 0)
            AND ISNULL(existing.ChapterID, 0) = ISNULL(w.ChapterID, 0)
            AND ISNULL(existing.SceneID, 0) = ISNULL(w.SceneID, 0)
            AND existing.EntityType = w.EntityType
            AND ISNULL(existing.EntityID, 0) = ISNULL(w.EntityID, 0)
            AND existing.WarningTypeID = wt.WarningTypeID
            AND (existing.IsDismissed = 1 OR existing.IsIntentional = 1)
    );

    EXEC dbo.ValidationRun_Complete @ValidationRunID;

    SELECT @ValidationRunID AS ValidationRunID,
        SUM(CASE WHEN ws.SeverityName = N'Error' THEN 1 ELSE 0 END) AS ErrorCount,
        SUM(CASE WHEN ws.SeverityName = N'Warning' THEN 1 ELSE 0 END) AS WarningCount,
        SUM(CASE WHEN ws.SeverityName = N'Info' THEN 1 ELSE 0 END) AS InfoCount,
        COUNT(*) AS TotalCount
    FROM dbo.ContinuityWarnings cw
    INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
    WHERE cw.ValidationRunID = @ValidationRunID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateBook
    @BookID int,
    @TriggeredBy nvarchar(100) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @ProjectID int = (SELECT ProjectID FROM dbo.Books WHERE BookID = @BookID);
    EXEC dbo.ContinuityValidation_ValidateProject @ProjectID = @ProjectID, @BookID = @BookID, @TriggeredBy = @TriggeredBy;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateScene
    @SceneID int,
    @TriggeredBy nvarchar(100) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @ProjectID int, @BookID int;
    SELECT @ProjectID = b.ProjectID, @BookID = b.BookID
    FROM dbo.Scenes s
    INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
    INNER JOIN dbo.Books b ON b.BookID = c.BookID
    WHERE s.SceneID = @SceneID;
    EXEC dbo.ContinuityValidation_ValidateProject @ProjectID = @ProjectID, @BookID = @BookID, @SceneID = @SceneID, @TriggeredBy = @TriggeredBy;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_List
    @ProjectID int,
    @BookID int = NULL,
    @SceneID int = NULL,
    @EntityType nvarchar(80) = NULL,
    @EntityID int = NULL,
    @WarningTypeID int = NULL,
    @WarningSeverityID int = NULL,
    @IncludeDismissed bit = 0,
    @IncludeIntentional bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    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
        AND (@BookID IS NULL OR cw.BookID = @BookID)
        AND (@SceneID IS NULL OR cw.SceneID = @SceneID)
        AND (@EntityType IS NULL OR cw.EntityType = @EntityType)
        AND (@EntityID IS NULL OR cw.EntityID = @EntityID)
        AND (@WarningTypeID IS NULL OR cw.WarningTypeID = @WarningTypeID)
        AND (@WarningSeverityID IS NULL OR cw.WarningSeverityID = @WarningSeverityID)
        AND (@IncludeDismissed = 1 OR cw.IsDismissed = 0)
        AND (@IncludeIntentional = 1 OR cw.IsIntentional = 0)
    ORDER BY ws.SortOrder DESC, cw.LastDetectedDate DESC, cw.ContinuityWarningID DESC;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByProject
    @ProjectID int,
    @IncludeDismissed bit = 0,
    @IncludeIntentional bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByBook
    @ProjectID int,
    @BookID int,
    @IncludeDismissed bit = 0,
    @IncludeIntentional bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @BookID = @BookID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByScene
    @SceneID int,
    @IncludeDismissed bit = 0,
    @IncludeIntentional bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @ProjectID int = (SELECT b.ProjectID FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE s.SceneID = @SceneID);
    EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @SceneID = @SceneID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByEntity
    @ProjectID int,
    @EntityType nvarchar(80),
    @EntityID int,
    @IncludeDismissed bit = 0,
    @IncludeIntentional bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @EntityType = @EntityType, @EntityID = @EntityID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_SceneCounts
    @ProjectID int,
    @BookID int = NULL
AS
BEGIN
    SET NOCOUNT ON;
    SELECT cw.SceneID, COUNT(*) AS WarningCount
    FROM dbo.ContinuityWarnings cw
    WHERE cw.ProjectID = @ProjectID
        AND cw.SceneID IS NOT NULL
        AND cw.IsDismissed = 0
        AND cw.IsIntentional = 0
        AND (@BookID IS NULL OR cw.BookID = @BookID)
    GROUP BY cw.SceneID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_SetState
    @ContinuityWarningID int,
    @IsDismissed bit = NULL,
    @IsIntentional bit = NULL
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE dbo.ContinuityWarnings
    SET IsDismissed = COALESCE(@IsDismissed, IsDismissed),
        IsIntentional = COALESCE(@IsIntentional, IsIntentional)
    WHERE ContinuityWarningID = @ContinuityWarningID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Dismiss
    @ContinuityWarningID int
AS
BEGIN
    SET NOCOUNT ON;
    EXEC dbo.ContinuityWarning_SetState @ContinuityWarningID = @ContinuityWarningID, @IsDismissed = 1;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_MarkIntentional
    @ContinuityWarningID int
AS
BEGIN
    SET NOCOUNT ON;
    EXEC dbo.ContinuityWarning_SetState @ContinuityWarningID = @ContinuityWarningID, @IsIntentional = 1;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Restore
    @ContinuityWarningID int
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE dbo.ContinuityWarnings
    SET IsDismissed = 0, IsIntentional = 0
    WHERE ContinuityWarningID = @ContinuityWarningID;
END;
GO
