SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

IF OBJECT_ID(N'dbo.ProjectRestoreHistory', N'U') IS NULL
BEGIN
    CREATE TABLE dbo.ProjectRestoreHistory
    (
        ProjectRestoreHistoryID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ProjectRestoreHistory PRIMARY KEY,
        ProjectID int NOT NULL,
        SourceRestorePointID int NULL,
        SafetyRestorePointID int NULL,
        RestoredDateUtc datetime2 NOT NULL CONSTRAINT DF_ProjectRestoreHistory_RestoredDateUtc DEFAULT SYSUTCDATETIME(),
        PerformedBy nvarchar(100) NULL,
        Notes nvarchar(max) NULL,
        CONSTRAINT FK_ProjectRestoreHistory_Projects FOREIGN KEY(ProjectID) REFERENCES dbo.Projects(ProjectID),
        CONSTRAINT FK_ProjectRestoreHistory_SourceRestorePoint FOREIGN KEY(SourceRestorePointID) REFERENCES dbo.ProjectRestorePoints(ProjectRestorePointID),
        CONSTRAINT FK_ProjectRestoreHistory_SafetyRestorePoint FOREIGN KEY(SafetyRestorePointID) REFERENCES dbo.ProjectRestorePoints(ProjectRestorePointID)
    );
END;
GO

IF OBJECT_ID(N'dbo.ProjectRestoreHistory', N'U') IS NOT NULL
BEGIN
    IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SourceRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
        ALTER TABLE dbo.ProjectRestoreHistory DROP CONSTRAINT FK_ProjectRestoreHistory_SourceRestorePoint;

    IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SafetyRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
        ALTER TABLE dbo.ProjectRestoreHistory DROP CONSTRAINT FK_ProjectRestoreHistory_SafetyRestorePoint;

    IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory') AND name = N'SourceRestorePointID' AND is_nullable = 0)
        ALTER TABLE dbo.ProjectRestoreHistory ALTER COLUMN SourceRestorePointID int NULL;

    IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory') AND name = N'SafetyRestorePointID' AND is_nullable = 0)
        ALTER TABLE dbo.ProjectRestoreHistory ALTER COLUMN SafetyRestorePointID int NULL;

    IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SourceRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
        ALTER TABLE dbo.ProjectRestoreHistory ADD CONSTRAINT FK_ProjectRestoreHistory_SourceRestorePoint FOREIGN KEY(SourceRestorePointID) REFERENCES dbo.ProjectRestorePoints(ProjectRestorePointID);

    IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SafetyRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
        ALTER TABLE dbo.ProjectRestoreHistory ADD CONSTRAINT FK_ProjectRestoreHistory_SafetyRestorePoint FOREIGN KEY(SafetyRestorePointID) REFERENCES dbo.ProjectRestorePoints(ProjectRestorePointID);
END;
GO

IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ProjectRestoreHistory_ProjectDate' AND object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
    CREATE INDEX IX_ProjectRestoreHistory_ProjectDate ON dbo.ProjectRestoreHistory(ProjectID, RestoredDateUtc DESC);
GO

CREATE OR ALTER PROCEDURE dbo.ProjectRestore_InsertJsonTable
    @TableName sysname,
    @JsonPath nvarchar(400),
    @JsonData nvarchar(max),
    @ProjectID int,
    @ProjectScopedOnly bit = 0
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @FullTableName nvarchar(300) = N'dbo.' + QUOTENAME(@TableName);
    DECLARE @ObjectID int = OBJECT_ID(@FullTableName, N'U');
    IF @ObjectID IS NULL
        THROW 51000, 'Restore table not found.', 1;

    IF NOT EXISTS (SELECT 1 FROM OPENJSON(@JsonData, @JsonPath))
        RETURN;

    DECLARE @ColumnList nvarchar(max);
    DECLARE @WithList nvarchar(max);
    DECLARE @SelectList nvarchar(max);
    DECLARE @ProjectFilter nvarchar(max) = N'';
    DECLARE @HasIdentity bit = 0;

    DECLARE @Columns table
    (
        Name sysname NOT NULL,
        ColumnID int NOT NULL,
        IsIdentity bit NOT NULL,
        TypeName nvarchar(100) NOT NULL
    );

    INSERT INTO @Columns (Name, ColumnID, IsIdentity, TypeName)
    SELECT
        c.name,
        c.column_id,
        c.is_identity,
        CASE
            WHEN t.name IN (N'nvarchar', N'nchar') AND c.max_length = -1 THEN t.name + N'(max)'
            WHEN t.name IN (N'nvarchar', N'nchar') THEN t.name + N'(' + CONVERT(nvarchar(20), c.max_length / 2) + N')'
            WHEN t.name IN (N'varchar', N'char', N'varbinary', N'binary') AND c.max_length = -1 THEN t.name + N'(max)'
            WHEN t.name IN (N'varchar', N'char', N'varbinary', N'binary') THEN t.name + N'(' + CONVERT(nvarchar(20), c.max_length) + N')'
            WHEN t.name IN (N'decimal', N'numeric') THEN t.name + N'(' + CONVERT(nvarchar(20), c.precision) + N',' + CONVERT(nvarchar(20), c.scale) + N')'
            WHEN t.name IN (N'datetime2', N'time', N'datetimeoffset') THEN t.name + N'(' + CONVERT(nvarchar(20), c.scale) + N')'
            ELSE t.name
        END
    FROM sys.columns c
    INNER JOIN sys.types t ON t.user_type_id = c.user_type_id
    WHERE c.object_id = @ObjectID
        AND c.is_computed = 0
        AND t.name <> N'timestamp'
        AND EXISTS
        (
            SELECT 1
            FROM OPENJSON(@JsonData, @JsonPath) rowJson
            CROSS APPLY OPENJSON(rowJson.value) propertyJson
            WHERE propertyJson.[key] COLLATE DATABASE_DEFAULT = c.name COLLATE DATABASE_DEFAULT
        );

    SELECT @ColumnList = STUFF((SELECT N', ' + QUOTENAME(Name) FROM @Columns ORDER BY ColumnID FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 2, N'');
    SELECT @WithList = STUFF((SELECT N', ' + QUOTENAME(Name) + N' ' + TypeName + N' ''$.' + Name + N'''' FROM @Columns ORDER BY ColumnID FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 2, N'');
    SELECT @SelectList = STUFF((SELECT N', src.' + QUOTENAME(Name) FROM @Columns ORDER BY ColumnID FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 2, N'');
    SELECT @HasIdentity = CONVERT(bit, MAX(CONVERT(int, IsIdentity))) FROM @Columns;

    IF @ProjectScopedOnly = 1
        SET @ProjectFilter = N' WHERE src.ProjectID = @ProjectID';

    DECLARE @Sql nvarchar(max) = N'';

    IF @HasIdentity = 1
        SET @Sql += N'SET IDENTITY_INSERT dbo.' + QUOTENAME(@TableName) + N' ON;';

    SET @Sql += N'
        INSERT INTO dbo.' + QUOTENAME(@TableName) + N' (' + @ColumnList + N')
        SELECT ' + @SelectList + N'
        FROM OPENJSON(@JsonData, @JsonPath)
        WITH (' + @WithList + N') AS src' + @ProjectFilter + N';';

    IF @HasIdentity = 1
        SET @Sql += N'SET IDENTITY_INSERT dbo.' + QUOTENAME(@TableName) + N' OFF;';

    EXEC sp_executesql
        @Sql,
        N'@JsonData nvarchar(max), @JsonPath nvarchar(400), @ProjectID int',
        @JsonData = @JsonData,
        @JsonPath = @JsonPath,
        @ProjectID = @ProjectID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ProjectRestore_ApplyFromJson
    @ProjectID int,
    @SourceRestorePointID int = NULL,
    @SafetyRestorePointID int = NULL,
    @JsonData nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    IF ISJSON(@JsonData) <> 1
        THROW 51001, 'Restore JSON is invalid.', 1;

    IF JSON_VALUE(@JsonData, '$.Format') <> N'PlotDirector.ProjectBackup'
        THROW 51002, 'Restore JSON format is not recognised.', 1;

    IF JSON_VALUE(@JsonData, '$.FormatVersion') <> N'1.0'
        THROW 51003, 'Restore JSON format version is not supported.', 1;

    IF TRY_CONVERT(int, JSON_VALUE(@JsonData, '$.Project.ProjectID')) <> @ProjectID
        THROW 51004, 'Restore point does not belong to this project.', 1;

    DECLARE @RestoreTables table (TableName sysname NOT NULL PRIMARY KEY);
    INSERT INTO @RestoreTables (TableName)
    VALUES
        (N'ProjectTimelineSettings'), (N'ProjectTimelineMetricSettings'), (N'TimelineViewPresets'),
        (N'SceneMetricTypes'), (N'SceneMetricValues'), (N'Books'), (N'Chapters'), (N'Scenes'),
        (N'ScenePurposes'), (N'SceneOutcomes'), (N'PlotLines'), (N'PlotThreads'), (N'ThreadEvents'),
        (N'AssetStates'), (N'StoryAssets'), (N'AssetEvents'), (N'AssetDependencies'),
        (N'AssetCustodyEvents'), (N'AssetCustodyEventCharacters'), (N'Characters'), (N'SceneCharacters'),
        (N'CharacterAttributeTypes'), (N'CharacterAttributeEvents'), (N'CharacterKnowledge'),
        (N'CharacterRelationships'), (N'RelationshipEvents'), (N'Locations'), (N'LocationRelationships'),
        (N'SceneAssetLocations'), (N'SceneDependencies'), (N'ValidationRuns'), (N'ContinuityWarnings'),
        (N'SceneNotes'), (N'SceneWorkflow'), (N'SceneChecklistItems'), (N'SceneAttachments'),
        (N'ChapterGoals'), (N'Scenarios'), (N'ScenarioSceneOrder'), (N'ScenarioValidationRuns'),
        (N'ScenarioWarnings');

    BEGIN TRANSACTION;

    DECLARE @TableName sysname;
    DECLARE DisableCursor CURSOR LOCAL FAST_FORWARD FOR SELECT TableName FROM @RestoreTables;
    OPEN DisableCursor;
    FETCH NEXT FROM DisableCursor INTO @TableName;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        DECLARE @DisableSql nvarchar(max) = N'ALTER TABLE dbo.' + QUOTENAME(@TableName) + N' NOCHECK CONSTRAINT ALL;';
        EXEC (@DisableSql);
        FETCH NEXT FROM DisableCursor INTO @TableName;
    END
    CLOSE DisableCursor;
    DEALLOCATE DisableCursor;

    DELETE sw FROM dbo.ScenarioWarnings sw INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sw.ScenarioID WHERE sc.ProjectID = @ProjectID;
    DELETE svr FROM dbo.ScenarioValidationRuns svr INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = svr.ScenarioID WHERE sc.ProjectID = @ProjectID;
    DELETE sso FROM dbo.ScenarioSceneOrder sso INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sso.ScenarioID WHERE sc.ProjectID = @ProjectID;
    DELETE FROM dbo.Scenarios WHERE ProjectID = @ProjectID;

    DELETE FROM dbo.ContinuityWarnings WHERE ProjectID = @ProjectID;
    DELETE FROM dbo.ValidationRuns WHERE ProjectID = @ProjectID;

    DELETE sa FROM dbo.SceneAttachments sa 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;
    DELETE sci 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;
    DELETE sw 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;
    DELETE sn FROM dbo.SceneNotes sn 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;
    DELETE cg 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;

    DELETE sd FROM dbo.SceneDependencies sd
    LEFT JOIN dbo.Scenes ss ON ss.SceneID = sd.SourceSceneID
    LEFT JOIN dbo.Chapters sc ON sc.ChapterID = ss.ChapterID
    LEFT JOIN dbo.Books sb ON sb.BookID = sc.BookID
    LEFT JOIN dbo.Scenes ts ON ts.SceneID = sd.TargetSceneID
    LEFT JOIN dbo.Chapters tc ON tc.ChapterID = ts.ChapterID
    LEFT JOIN dbo.Books tb ON tb.BookID = tc.BookID
    WHERE sd.ProjectID = @ProjectID OR sb.ProjectID = @ProjectID OR tb.ProjectID = @ProjectID;

    DELETE re FROM dbo.RelationshipEvents re INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID WHERE cr.ProjectID = @ProjectID;
    DELETE FROM dbo.CharacterRelationships WHERE ProjectID = @ProjectID;
    DELETE ck FROM dbo.CharacterKnowledge ck INNER JOIN dbo.Characters ch ON ch.CharacterID = ck.CharacterID WHERE ch.ProjectID = @ProjectID;
    DELETE cae FROM dbo.CharacterAttributeEvents cae INNER JOIN dbo.Characters ch ON ch.CharacterID = cae.CharacterID WHERE ch.ProjectID = @ProjectID;
    DELETE sc FROM dbo.SceneCharacters sc INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID WHERE ch.ProjectID = @ProjectID;

    DELETE acec FROM dbo.AssetCustodyEventCharacters acec INNER JOIN dbo.AssetCustodyEvents ace ON ace.AssetCustodyEventID = acec.AssetCustodyEventID INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID WHERE sa.ProjectID = @ProjectID;
    DELETE ace FROM dbo.AssetCustodyEvents ace INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID WHERE sa.ProjectID = @ProjectID;
    DELETE ad FROM dbo.AssetDependencies ad INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ad.SourceAssetID WHERE sa.ProjectID = @ProjectID;
    DELETE ae FROM dbo.AssetEvents ae INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID WHERE sa.ProjectID = @ProjectID;
    DELETE sal FROM dbo.SceneAssetLocations sal INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID WHERE sa.ProjectID = @ProjectID;
    DELETE FROM dbo.StoryAssets WHERE ProjectID = @ProjectID;

    DELETE lr FROM dbo.LocationRelationships lr INNER JOIN dbo.Locations l ON l.LocationID = lr.FromLocationID WHERE l.ProjectID = @ProjectID;
    DELETE FROM dbo.Locations WHERE ProjectID = @ProjectID;

    DELETE te FROM dbo.ThreadEvents te INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID WHERE pl.ProjectID = @ProjectID;
    DELETE pt FROM dbo.PlotThreads pt INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID WHERE pl.ProjectID = @ProjectID;
    DELETE FROM dbo.PlotLines WHERE ProjectID = @ProjectID;

    DELETE FROM dbo.TimelineViewPresets WHERE ProjectID = @ProjectID;
    DELETE FROM dbo.ProjectTimelineMetricSettings WHERE ProjectID = @ProjectID;
    DELETE FROM dbo.ProjectTimelineSettings WHERE ProjectID = @ProjectID;

    DELETE smv 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 b.ProjectID = @ProjectID;
    DELETE FROM dbo.SceneMetricTypes WHERE ProjectID = @ProjectID;
    DELETE FROM dbo.CharacterAttributeTypes WHERE ProjectID = @ProjectID;
    DELETE FROM dbo.AssetStates WHERE ProjectID = @ProjectID;

    DELETE sp FROM dbo.ScenePurposes sp 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;
    DELETE so 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;
    DELETE s 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 b.ProjectID = @ProjectID;
    DELETE c FROM dbo.Chapters c INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE b.ProjectID = @ProjectID;
    DELETE FROM dbo.Books WHERE ProjectID = @ProjectID;
    DELETE FROM dbo.Characters WHERE ProjectID = @ProjectID;

    UPDATE p
        SET ProjectName = src.ProjectName,
            Description = src.Description,
            CreatedDate = src.CreatedDate,
            UpdatedDate = SYSUTCDATETIME(),
            IsArchived = src.IsArchived,
            ArchivedDate = src.ArchivedDate,
            ArchivedReason = src.ArchivedReason
    FROM dbo.Projects p
    CROSS APPLY OPENJSON(@JsonData, '$.Project')
    WITH
    (
        ProjectID int '$.ProjectID',
        ProjectName nvarchar(200) '$.ProjectName',
        Description nvarchar(max) '$.Description',
        CreatedDate datetime2 '$.CreatedDate',
        IsArchived bit '$.IsArchived',
        ArchivedDate datetime2 '$.ArchivedDate',
        ArchivedReason nvarchar(1000) '$.ArchivedReason'
    ) src
    WHERE p.ProjectID = @ProjectID AND src.ProjectID = @ProjectID;

    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneMetricTypes', N'$.Data.SceneMetricTypes', @JsonData, @ProjectID, 1;
    EXEC dbo.ProjectRestore_InsertJsonTable N'AssetStates', N'$.Data.AssetStates', @JsonData, @ProjectID, 1;
    EXEC dbo.ProjectRestore_InsertJsonTable N'CharacterAttributeTypes', N'$.Data.CharacterAttributeTypes', @JsonData, @ProjectID, 1;
    EXEC dbo.ProjectRestore_InsertJsonTable N'Books', N'$.Data.Books', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'Chapters', N'$.Data.Chapters', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'Characters', N'$.Data.Characters', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'Locations', N'$.Data.Locations', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'Scenes', N'$.Data.Scenes', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ScenePurposes', N'$.Data.ScenePurposes', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneOutcomes', N'$.Data.SceneOutcomes', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneMetricValues', N'$.Data.SceneMetricValues', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ProjectTimelineSettings', N'$.Data.TimelineSettings', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ProjectTimelineMetricSettings', N'$.Data.TimelineMetricSettings', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'TimelineViewPresets', N'$.Data.TimelineViewPresets', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'PlotLines', N'$.Data.PlotLines', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'PlotThreads', N'$.Data.PlotThreads', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ThreadEvents', N'$.Data.ThreadEvents', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'StoryAssets', N'$.Data.StoryAssets', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'AssetEvents', N'$.Data.AssetEvents', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'AssetDependencies', N'$.Data.AssetDependencies', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'AssetCustodyEvents', N'$.Data.AssetCustodyEvents', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'AssetCustodyEventCharacters', N'$.Data.AssetCustodyCharacters', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneCharacters', N'$.Data.SceneCharacters', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'CharacterAttributeEvents', N'$.Data.CharacterAttributeEvents', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'CharacterKnowledge', N'$.Data.CharacterKnowledge', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'CharacterRelationships', N'$.Data.CharacterRelationships', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'RelationshipEvents', N'$.Data.RelationshipEvents', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'LocationRelationships', N'$.Data.LocationRelationships', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneAssetLocations', N'$.Data.SceneAssetLocations', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneDependencies', N'$.Data.SceneDependencies', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ValidationRuns', N'$.Data.ValidationRuns', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ContinuityWarnings', N'$.Data.ContinuityWarnings', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneNotes', N'$.Data.SceneNotes', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneWorkflow', N'$.Data.SceneWorkflow', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneChecklistItems', N'$.Data.SceneChecklistItems', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'SceneAttachments', N'$.Data.SceneAttachments', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ChapterGoals', N'$.Data.ChapterGoals', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'Scenarios', N'$.Data.Scenarios', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ScenarioSceneOrder', N'$.Data.ScenarioSceneOrder', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ScenarioValidationRuns', N'$.Data.ScenarioValidationRuns', @JsonData, @ProjectID, 0;
    EXEC dbo.ProjectRestore_InsertJsonTable N'ScenarioWarnings', N'$.Data.ScenarioWarnings', @JsonData, @ProjectID, 0;

    DECLARE EnableCursor CURSOR LOCAL FAST_FORWARD FOR SELECT TableName FROM @RestoreTables;
    OPEN EnableCursor;
    FETCH NEXT FROM EnableCursor INTO @TableName;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        DECLARE @EnableSql nvarchar(max) = N'ALTER TABLE dbo.' + QUOTENAME(@TableName) + N' WITH CHECK CHECK CONSTRAINT ALL;';
        EXEC (@EnableSql);
        FETCH NEXT FROM EnableCursor INTO @TableName;
    END
    CLOSE EnableCursor;
    DEALLOCATE EnableCursor;

    IF @SourceRestorePointID IS NOT NULL AND @SafetyRestorePointID IS NOT NULL
    BEGIN
        INSERT INTO dbo.ProjectRestoreHistory (ProjectID, SourceRestorePointID, SafetyRestorePointID, PerformedBy, Notes)
        VALUES (@ProjectID, @SourceRestorePointID, @SafetyRestorePointID, SUSER_SNAME(), N'Project restored from saved restore point.');
    END;

    COMMIT TRANSACTION;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ProjectBackup_GetIdentityMaxValues
    @TableNames nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    SELECT t.name AS TableName,
        ISNULL(CONVERT(int, IDENT_CURRENT(N'dbo.' + t.name)), 0) AS MaxID
    FROM sys.tables t
    INNER JOIN string_split(@TableNames, N',') requested ON requested.value = t.name
    WHERE SCHEMA_NAME(t.schema_id) = N'dbo';
END;
GO

CREATE OR ALTER PROCEDURE dbo.ProjectBackup_GetRelationshipImportLookups
AS
BEGIN
    SET NOCOUNT ON;

    SELECT RevisionStatusID AS LookupID, StatusName AS LookupName FROM dbo.RevisionStatuses ORDER BY SortOrder, StatusName;
    SELECT TimeModeID AS LookupID, TimeModeName AS LookupName FROM dbo.TimeModes ORDER BY SortOrder, TimeModeName;
    SELECT DurationUnitID AS LookupID, DurationUnitName AS LookupName FROM dbo.DurationUnits ORDER BY SortOrder, DurationUnitName;
    SELECT TimeConfidenceID AS LookupID, TimeConfidenceName AS LookupName FROM dbo.TimeConfidences ORDER BY SortOrder, TimeConfidenceName;
    SELECT LocationTypeID AS LookupID, TypeName AS LookupName FROM dbo.LocationTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT LocationRelationshipTypeID AS LookupID, TypeName AS LookupName FROM dbo.LocationRelationshipTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT PlotLineTypeID AS LookupID, TypeName AS LookupName FROM dbo.PlotLineTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT ThreadTypeID AS LookupID, TypeName AS LookupName FROM dbo.ThreadTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT ThreadStatusID AS LookupID, StatusName AS LookupName FROM dbo.ThreadStatuses WHERE IsActive = 1 ORDER BY SortOrder, StatusName;
    SELECT ThreadEventTypeID AS LookupID, TypeName AS LookupName FROM dbo.ThreadEventTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT AssetKindID AS LookupID, KindName AS LookupName FROM dbo.AssetKinds WHERE IsActive = 1 ORDER BY SortOrder, KindName;
    SELECT AssetStateID AS LookupID, StateName AS LookupName FROM dbo.AssetStates WHERE IsActive = 1 AND ProjectID IS NULL ORDER BY SortOrder, StateName;
    SELECT AssetEventTypeID AS LookupID, TypeName AS LookupName FROM dbo.AssetEventTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT AssetDependencyTypeID AS LookupID, TypeName AS LookupName FROM dbo.AssetDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT AssetCustodyEventTypeID AS LookupID, TypeName AS LookupName FROM dbo.AssetCustodyEventTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT CustodyRoleID AS LookupID, RoleName AS LookupName FROM dbo.CustodyRoles WHERE IsActive = 1 ORDER BY SortOrder, RoleName;
    SELECT CharacterRoleInSceneTypeID AS LookupID, TypeName AS LookupName FROM dbo.CharacterRoleInSceneTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT PresenceTypeID AS LookupID, TypeName AS LookupName FROM dbo.PresenceTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT KnowledgeStateID AS LookupID, StateName AS LookupName FROM dbo.KnowledgeStates WHERE IsActive = 1 ORDER BY SortOrder, StateName;
    SELECT SceneDependencyTypeID AS LookupID, TypeName AS LookupName FROM dbo.SceneDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT SceneNoteTypeID AS LookupID, TypeName AS LookupName FROM dbo.SceneNoteTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT SceneAttachmentTypeID AS LookupID, TypeName AS LookupName FROM dbo.SceneAttachmentTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
    SELECT ScenePurposeTypeID AS LookupID, PurposeName AS LookupName FROM dbo.ScenePurposeTypes ORDER BY SortOrder, PurposeName;
    SELECT MetricTypeID AS LookupID, MetricName AS LookupName FROM dbo.SceneMetricTypes WHERE ProjectID IS NULL ORDER BY SortOrder, MetricName;

    SELECT rt.RelationshipTypeID, rt.TypeName, rt.IsPermanentDefault, rt.SortOrder, rt.IsActive,
        rt.RelationshipCategoryID, rc.CategoryName AS RelationshipCategoryName, COALESCE(rc.SortOrder, 900) AS CategorySortOrder
    FROM dbo.RelationshipTypes rt
    LEFT JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
    WHERE rt.IsActive = 1
    ORDER BY COALESCE(rc.SortOrder, 900), rt.SortOrder, rt.TypeName;

    SELECT RelationshipStateID, StateName, SortOrder, IsActive
    FROM dbo.RelationshipStates
    WHERE IsActive = 1
    ORDER BY SortOrder, StateName;
END;
GO

CREATE OR ALTER PROCEDURE dbo.ProjectBackup_RelationshipImportDiagnostics
    @ProjectID int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT
        (SELECT COUNT(1)
            FROM dbo.Characters c
            WHERE c.ProjectID = @ProjectID) AS CharacterCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            WHERE cr.ProjectID = @ProjectID) AS RelationshipCount,
        (SELECT COUNT(1)
            FROM dbo.RelationshipEvents re
            INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID
            WHERE cr.ProjectID = @ProjectID) AS RelationshipEventCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID AND ca.ProjectID = @ProjectID AND ca.IsArchived = 0
            INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID AND cb.ProjectID = @ProjectID AND cb.IsArchived = 0
            INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID AND rt.IsActive = 1
            LEFT JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
            WHERE cr.ProjectID = @ProjectID
                AND cr.IsArchived = 0) AS MapRelationshipCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            LEFT JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID AND ca.ProjectID = @ProjectID
            WHERE cr.ProjectID = @ProjectID
                AND ca.CharacterID IS NULL) AS MissingSourceCharacterCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            LEFT JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID AND cb.ProjectID = @ProjectID
            WHERE cr.ProjectID = @ProjectID
                AND cb.CharacterID IS NULL) AS MissingTargetCharacterCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            LEFT JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
            WHERE cr.ProjectID = @ProjectID
                AND rt.RelationshipTypeID IS NULL) AS MissingRelationshipTypeCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = cr.InitialRelationshipStateID
            WHERE cr.ProjectID = @ProjectID
                AND cr.InitialRelationshipStateID IS NOT NULL
                AND rs.RelationshipStateID IS NULL) AS MissingInitialStateCount,
        (SELECT COUNT(1)
            FROM dbo.RelationshipEvents re
            LEFT JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.ProjectID = @ProjectID
            WHERE cr.CharacterRelationshipID IS NULL) AS MissingEventRelationshipCount,
        (SELECT COUNT(1)
            FROM dbo.RelationshipEvents re
            INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.ProjectID = @ProjectID
            LEFT JOIN dbo.Scenes s ON s.SceneID = re.SceneID
            WHERE s.SceneID IS NULL) AS MissingEventSceneCount,
        (SELECT COUNT(1)
            FROM dbo.RelationshipEvents re
            INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.ProjectID = @ProjectID
            LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
            WHERE rs.RelationshipStateID IS NULL) AS MissingEventStateCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            WHERE cr.ProjectID = @ProjectID
                AND cr.IsArchived = 1) AS ArchivedRelationshipCount,
        (SELECT COUNT(1)
            FROM dbo.CharacterRelationships cr
            INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID AND ca.ProjectID = @ProjectID
            INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID AND cb.ProjectID = @ProjectID
            WHERE cr.ProjectID = @ProjectID
                AND cr.IsArchived = 0
                AND (ca.IsArchived = 1 OR cb.IsArchived = 1)) AS ArchivedCharacterEndpointCount;
END;
GO
