using System.Text.Json;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

public sealed record LegacyOperatorSettings(
    string? SceneDirectory,
    string? ResourceDirectory,
    string? BackgroundDirectory,
    bool NavigationExpanded,
    LegacyOperatorColorTheme ColorTheme = LegacyOperatorColorTheme.System,
    LegacyOperatorViewMode ViewMode = LegacyOperatorViewMode.Automatic,
    LegacyOperatorStartWorkspace StartWorkspace = LegacyOperatorStartWorkspace.StockCut)
{
    public const int CurrentSchemaVersion = 2;
    public const int LegacySchemaVersion = 1;

    public static LegacyOperatorSettings Default { get; } = new(
        SceneDirectory: null,
        ResourceDirectory: null,
        BackgroundDirectory: null,
        NavigationExpanded: true,
        ColorTheme: LegacyOperatorColorTheme.System,
        ViewMode: LegacyOperatorViewMode.Automatic,
        StartWorkspace: LegacyOperatorStartWorkspace.StockCut);
}

public enum LegacyOperatorColorTheme
{
    System,
    Light,
    Dark
}

public enum LegacyOperatorViewMode
{
    Automatic,
    Compact,
    Cards
}

public enum LegacyOperatorStartWorkspace
{
    StockCut,
    LastWorkspace
}

public enum LegacyOperatorSettingsFolderKind
{
    Scene,
    Resource,
    Background
}

public enum LegacyOperatorFolderValidationFailure
{
    None,
    InvalidPath,
    DirectoryNotFound,
    RootDirectory,
    NonFixedDrive,
    ReparsePoint,
    RequiredFileMissing,
    RequiredFileUnsafe,
    InvalidRuntimeCatalog,
    FileUnavailable
}

public sealed record LegacyOperatorValidatedFolder(
    string CanonicalPath,
    string AbbreviatedPath,
    bool DatabaseIniDetected,
    int MissingOptionalExternalAssetCount);

public sealed record LegacyOperatorFolderValidationResult(
    LegacyOperatorValidatedFolder? Folder,
    LegacyOperatorFolderValidationFailure Failure,
    string? WarningMessage)
{
    public bool IsValid => Failure == LegacyOperatorFolderValidationFailure.None &&
        Folder is not null;
}

/// <summary>
/// Validates only native folder-picker results. The returned messages intentionally
/// contain no source path; a separately bounded abbreviation is available for display.
/// </summary>
public static class LegacyOperatorFolderValidator
{
    public const int MaximumPathCharacters = 1024;
    public const int DefaultDisplayCharacters = 48;
    public const string UnconfiguredDisplay = "선택되지 않음";

    private const string InvalidFolderMessage =
        "선택한 폴더를 안전하게 사용할 수 없습니다.";
    private static readonly IReadOnlyList<string> RequiredSceneFileNameValues =
        Array.AsReadOnly(LegacySceneRuntimeCoverage.ExpectedCutCodes
            .Select(code => string.Concat(code, ".t2s"))
            .ToArray());

    private static readonly IReadOnlyList<string> RequiredBuiltInAssetRelativePathValues =
        Array.AsReadOnly(new[]
        {
            @"images\주유기merge.png",
            @"images\35752913_l.jpg",
            @"Images\그림_빨강.png",
            @"Images\그림_검정.png",
            @"Images\그림_파랑.png",
            @"Images\프리마켓.png",
            @"Images\애프터마켓.png",
            @"Images\KRX.png",
            @"Images\NXT.png"
        });

    private static readonly IReadOnlyList<string> OptionalExternalAssetRelativePathValues =
        Array.AsReadOnly(new[]
        {
            @"Video\큐브배경.vrv",
            @"Video\20201008_미국.vrv",
            @"Video\20201008_독일.vrv",
            @"Video\20201008_영국.vrv",
            @"Video\20201008_프랑스.vrv",
            @"Video\20201008_일본.vrv",
            @"Video\20201008_중국.vrv",
            @"Video\20201008_홍콩.vrv",
            @"Video\20201008_대만.vrv",
            @"Video\20201008_싱가포르.vrv",
            @"Video\20201008_태국.vrv",
            @"Video\20201008_필리핀.vrv",
            @"Video\20201008_말레이시아.vrv",
            @"Video\20201008_인도네시아.vrv"
        });

    public static IReadOnlyList<string> RequiredSceneFileNames =>
        RequiredSceneFileNameValues;

    public static IReadOnlyList<string> RequiredBuiltInAssetRelativePaths =>
        RequiredBuiltInAssetRelativePathValues;

    public static IReadOnlyList<string> OptionalExternalAssetRelativePaths =>
        OptionalExternalAssetRelativePathValues;

    public static bool TryValidate(
        LegacyOperatorSettingsFolderKind kind,
        string? path,
        out LegacyOperatorValidatedFolder? folder,
        out string? warningMessage)
    {
        var result = Validate(kind, path);
        folder = result.Folder;
        warningMessage = result.WarningMessage;
        return result.IsValid;
    }

    public static LegacyOperatorFolderValidationResult Validate(
        LegacyOperatorSettingsFolderKind kind,
        string? path)
    {
        var failure = LegacyOperatorFolderValidationFailure.InvalidPath;
        if (!Enum.IsDefined(kind) || !TryCanonicalizeDirectory(
                path,
                out var canonicalPath,
                out failure))
        {
            return Invalid(failure, InvalidFolderMessage);
        }

        var databaseIniDetected = false;
        var missingOptionalExternalAssetCount = 0;
        switch (kind)
        {
            case LegacyOperatorSettingsFolderKind.Scene:
            case LegacyOperatorSettingsFolderKind.Resource:
            case LegacyOperatorSettingsFolderKind.Background:
                // Folder selection stores only the canonical external path.
                // Individual Cuts/Res files are resolved when the feature that
                // needs them is used.
                break;

            default:
                return Invalid(
                    LegacyOperatorFolderValidationFailure.InvalidPath,
                    InvalidFolderMessage);
        }

        return new LegacyOperatorFolderValidationResult(
            new LegacyOperatorValidatedFolder(
                canonicalPath,
                AbbreviateForDisplay(canonicalPath),
                databaseIniDetected,
                missingOptionalExternalAssetCount),
            LegacyOperatorFolderValidationFailure.None,
            WarningMessage: null);
    }

    public static string AbbreviateForDisplay(
        string? path,
        int maximumCharacters = DefaultDisplayCharacters)
    {
        maximumCharacters = Math.Clamp(maximumCharacters, 12, 120);
        if (string.IsNullOrWhiteSpace(path) ||
            path.Any(char.IsControl) ||
            !Path.IsPathFullyQualified(path))
        {
            return UnconfiguredDisplay;
        }

        try
        {
            var canonicalPath = Path.TrimEndingDirectorySeparator(Path.GetFullPath(path));
            var root = Path.GetPathRoot(canonicalPath);
            var leaf = Path.GetFileName(canonicalPath);
            if (string.IsNullOrEmpty(root) || string.IsNullOrEmpty(leaf))
            {
                return UnconfiguredDisplay;
            }

            var display = string.Concat(
                Path.TrimEndingDirectorySeparator(root),
                Path.DirectorySeparatorChar,
                "…",
                Path.DirectorySeparatorChar,
                leaf);
            if (display.Length <= maximumCharacters)
            {
                return display;
            }

            var prefix = string.Concat(
                Path.TrimEndingDirectorySeparator(root),
                Path.DirectorySeparatorChar,
                "…",
                Path.DirectorySeparatorChar);
            var availableLeafCharacters = Math.Max(
                1,
                maximumCharacters - prefix.Length - 1);
            return string.Concat(
                prefix,
                "…",
                leaf[^Math.Min(availableLeafCharacters, leaf.Length)..]);
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or NotSupportedException or
            System.Security.SecurityException)
        {
            return UnconfiguredDisplay;
        }
    }

    internal static bool TryCanonicalizeDirectory(
        string? path,
        out string canonicalPath,
        out LegacyOperatorFolderValidationFailure failure)
    {
        canonicalPath = string.Empty;
        if (string.IsNullOrWhiteSpace(path) ||
            path.Length > MaximumPathCharacters ||
            path.Any(char.IsControl) ||
            !Path.IsPathFullyQualified(path))
        {
            failure = LegacyOperatorFolderValidationFailure.InvalidPath;
            return false;
        }

        try
        {
            canonicalPath = Path.TrimEndingDirectorySeparator(Path.GetFullPath(path));
            var root = Path.GetPathRoot(canonicalPath);
            if (string.IsNullOrEmpty(root) ||
                root.StartsWith("\\\\", StringComparison.Ordinal) ||
                !string.Equals(
                    Path.GetPathRoot(root),
                    root,
                    StringComparison.OrdinalIgnoreCase))
            {
                failure = LegacyOperatorFolderValidationFailure.InvalidPath;
                canonicalPath = string.Empty;
                return false;
            }

            if (string.Equals(
                    Path.TrimEndingDirectorySeparator(root),
                    canonicalPath,
                    StringComparison.OrdinalIgnoreCase))
            {
                failure = LegacyOperatorFolderValidationFailure.RootDirectory;
                canonicalPath = string.Empty;
                return false;
            }

            var drive = new DriveInfo(root);
            if (!drive.IsReady || drive.DriveType != DriveType.Fixed)
            {
                failure = LegacyOperatorFolderValidationFailure.NonFixedDrive;
                canonicalPath = string.Empty;
                return false;
            }

            if (!Directory.Exists(canonicalPath))
            {
                failure = LegacyOperatorFolderValidationFailure.DirectoryNotFound;
                canonicalPath = string.Empty;
                return false;
            }

            for (DirectoryInfo? current = new(canonicalPath);
                 current is not null;
                 current = current.Parent)
            {
                if ((current.Attributes & FileAttributes.ReparsePoint) != 0)
                {
                    failure = LegacyOperatorFolderValidationFailure.ReparsePoint;
                    canonicalPath = string.Empty;
                    return false;
                }
            }

            failure = LegacyOperatorFolderValidationFailure.None;
            return true;
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or UnauthorizedAccessException or
            NotSupportedException or System.Security.SecurityException)
        {
            failure = LegacyOperatorFolderValidationFailure.FileUnavailable;
            canonicalPath = string.Empty;
            return false;
        }
    }

    private static LegacyOperatorFolderValidationResult Invalid(
        LegacyOperatorFolderValidationFailure failure,
        string warningMessage) => new(
            Folder: null,
            failure,
            warningMessage);
}

public enum LegacyOperatorSettingsStoreFailure
{
    None,
    InvalidConfigurationPath,
    FileUnavailable,
    FileTooLarge,
    InvalidJson,
    UnsupportedSchema,
    InvalidSettings,
    SaveUnavailable
}

public sealed record LegacyOperatorSettingsLoadResult(
    LegacyOperatorSettings Settings,
    string? WarningMessage)
{
    public LegacyOperatorSettingsStoreFailure Failure { get; init; }

    public bool LoadedFromDisk { get; init; }
}

public sealed record LegacyOperatorSettingsSaveResult(
    bool Succeeded,
    string? WarningMessage)
{
    public LegacyOperatorSettingsStoreFailure Failure { get; init; }

    public LegacyOperatorSettings? SavedSettings { get; init; }
}

/// <summary>
/// Stores only native-approved runtime folders and closed presentation preferences.
/// Credentials, playout targets, live authorization and asset contents never enter
/// this file.
/// </summary>
public sealed class LegacyOperatorSettingsStore
{
    public const int MaximumFileBytes = 16 * 1024;
    public const string LoadWarningMessage =
        "저장된 앱 설정을 읽지 못해 안전한 기본값을 사용합니다.";
    public const string SaveWarningMessage =
        "앱 설정을 안전하게 저장하지 못했습니다.";

    private const string FileName = "runtime-folders.local.json";
    private readonly string _configurationPath;

    public LegacyOperatorSettingsStore(string? configurationPath = null)
    {
        _configurationPath = configurationPath ?? DefaultPath;
    }

    public static string DefaultPath => Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "MBN_STOCK_WEBVIEW",
        "Config",
        FileName);

    public string ConfigurationPath => _configurationPath;

    public LegacyOperatorSettingsLoadResult Load()
    {
        if (!TryNormalizeConfigurationPath(_configurationPath, out var path))
        {
            return FailedLoad(LegacyOperatorSettingsStoreFailure.InvalidConfigurationPath);
        }

        try
        {
            var directory = Path.GetDirectoryName(path);
            if (string.IsNullOrEmpty(directory))
            {
                return FailedLoad(
                    LegacyOperatorSettingsStoreFailure.InvalidConfigurationPath);
            }

            if (!Directory.Exists(directory))
            {
                return new LegacyOperatorSettingsLoadResult(
                    LegacyOperatorSettings.Default,
                    WarningMessage: null)
                {
                    Failure = LegacyOperatorSettingsStoreFailure.None,
                    LoadedFromDisk = false
                };
            }

            if (!LegacyOperatorFolderValidator.TryCanonicalizeDirectory(
                    directory,
                    out var canonicalDirectory,
                    out _))
            {
                return FailedLoad(
                    LegacyOperatorSettingsStoreFailure.InvalidConfigurationPath);
            }

            path = Path.Combine(canonicalDirectory, Path.GetFileName(path));
            if (!File.Exists(path))
            {
                return new LegacyOperatorSettingsLoadResult(
                    LegacyOperatorSettings.Default,
                    WarningMessage: null)
                {
                    Failure = LegacyOperatorSettingsStoreFailure.None,
                    LoadedFromDisk = false
                };
            }

            var attributes = File.GetAttributes(path);
            if ((attributes & (FileAttributes.Directory |
                    FileAttributes.ReparsePoint |
                    FileAttributes.Device)) != 0)
            {
                return FailedLoad(LegacyOperatorSettingsStoreFailure.FileUnavailable);
            }

            using var stream = new FileStream(
                path,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read,
                bufferSize: 4096,
                FileOptions.SequentialScan);
            if (stream.Length is <= 0 or > MaximumFileBytes)
            {
                return FailedLoad(
                    stream.Length > MaximumFileBytes
                        ? LegacyOperatorSettingsStoreFailure.FileTooLarge
                        : LegacyOperatorSettingsStoreFailure.InvalidJson);
            }

            var json = new byte[(int)stream.Length];
            stream.ReadExactly(json);
            if (!TryDeserialize(
                    json,
                    out var settings,
                    out var failure) ||
                !TryNormalizeSettings(settings, out var normalizedSettings))
            {
                return FailedLoad(
                    failure == LegacyOperatorSettingsStoreFailure.None
                        ? LegacyOperatorSettingsStoreFailure.InvalidSettings
                        : failure);
            }

            return new LegacyOperatorSettingsLoadResult(
                normalizedSettings,
                WarningMessage: null)
            {
                Failure = LegacyOperatorSettingsStoreFailure.None,
                LoadedFromDisk = true
            };
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or UnauthorizedAccessException or
            NotSupportedException or System.Security.SecurityException)
        {
            return FailedLoad(LegacyOperatorSettingsStoreFailure.FileUnavailable);
        }
    }

    public LegacyOperatorSettingsSaveResult Save(LegacyOperatorSettings settings)
    {
        ArgumentNullException.ThrowIfNull(settings);
        if (!TryNormalizeSettings(settings, out var normalizedSettings))
        {
            return FailedSave(LegacyOperatorSettingsStoreFailure.InvalidSettings);
        }

        if (!TryNormalizeConfigurationPath(_configurationPath, out var path))
        {
            return FailedSave(
                LegacyOperatorSettingsStoreFailure.InvalidConfigurationPath);
        }

        string? temporaryPath = null;
        try
        {
            var directory = Path.GetDirectoryName(path);
            if (string.IsNullOrEmpty(directory))
            {
                return FailedSave(
                    LegacyOperatorSettingsStoreFailure.InvalidConfigurationPath);
            }

            if (!TryCreateSafeConfigurationDirectory(
                    directory,
                    out var canonicalDirectory))
            {
                return FailedSave(
                    LegacyOperatorSettingsStoreFailure.InvalidConfigurationPath);
            }

            path = Path.Combine(canonicalDirectory, Path.GetFileName(path));
            if (File.Exists(path))
            {
                var attributes = File.GetAttributes(path);
                if ((attributes & (FileAttributes.Directory |
                        FileAttributes.ReparsePoint |
                        FileAttributes.Device)) != 0)
                {
                    return FailedSave(
                        LegacyOperatorSettingsStoreFailure.SaveUnavailable);
                }
            }

            var json = Serialize(normalizedSettings);
            if (json.Length > MaximumFileBytes)
            {
                return FailedSave(LegacyOperatorSettingsStoreFailure.FileTooLarge);
            }

            temporaryPath = Path.Combine(
                canonicalDirectory,
                $".{FileName}.{Guid.NewGuid():N}.tmp");
            using (var stream = new FileStream(
                       temporaryPath,
                       FileMode.CreateNew,
                       FileAccess.Write,
                       FileShare.None,
                       bufferSize: 4096,
                       FileOptions.WriteThrough))
            {
                stream.Write(json);
                stream.Flush(flushToDisk: true);
            }

            File.Move(temporaryPath, path, overwrite: true);
            temporaryPath = null;
            return new LegacyOperatorSettingsSaveResult(
                Succeeded: true,
                WarningMessage: null)
            {
                Failure = LegacyOperatorSettingsStoreFailure.None,
                SavedSettings = normalizedSettings
            };
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or UnauthorizedAccessException or
            NotSupportedException or System.Security.SecurityException)
        {
            return FailedSave(LegacyOperatorSettingsStoreFailure.SaveUnavailable);
        }
        finally
        {
            if (temporaryPath is not null)
            {
                try
                {
                    File.Delete(temporaryPath);
                }
                catch (Exception exception) when (
                    exception is IOException or UnauthorizedAccessException or
                    NotSupportedException or System.Security.SecurityException)
                {
                    // A failed save remains failed. Never replace that result with a
                    // cleanup exception or expose the temporary path.
                }
            }
        }
    }

    private static bool TryNormalizeSettings(
        LegacyOperatorSettings settings,
        out LegacyOperatorSettings normalizedSettings)
    {
        normalizedSettings = LegacyOperatorSettings.Default;
        if (!TryNormalizeOptionalFolder(
                LegacyOperatorSettingsFolderKind.Scene,
                settings.SceneDirectory,
                out var sceneDirectory) ||
            !TryNormalizeOptionalFolder(
                LegacyOperatorSettingsFolderKind.Resource,
                settings.ResourceDirectory,
                out var resourceDirectory) ||
            !TryNormalizeOptionalFolder(
                LegacyOperatorSettingsFolderKind.Background,
                settings.BackgroundDirectory,
                out var backgroundDirectory))
        {
            return false;
        }

        normalizedSettings = new LegacyOperatorSettings(
            sceneDirectory,
            resourceDirectory,
            backgroundDirectory,
            settings.NavigationExpanded,
            settings.ColorTheme,
            settings.ViewMode,
            settings.StartWorkspace);
        if (!Enum.IsDefined(normalizedSettings.ColorTheme) ||
            !Enum.IsDefined(normalizedSettings.ViewMode) ||
            !Enum.IsDefined(normalizedSettings.StartWorkspace))
        {
            normalizedSettings = LegacyOperatorSettings.Default;
            return false;
        }

        return true;
    }

    private static bool TryNormalizeOptionalFolder(
        LegacyOperatorSettingsFolderKind kind,
        string? path,
        out string? canonicalPath)
    {
        canonicalPath = null;
        if (path is null)
        {
            return true;
        }

        if (!LegacyOperatorFolderValidator.TryValidate(
                kind,
                path,
                out var folder,
                out _))
        {
            return false;
        }

        canonicalPath = folder!.CanonicalPath;
        return true;
    }

    private static byte[] Serialize(LegacyOperatorSettings settings)
    {
        using var stream = new MemoryStream(capacity: 512);
        using (var writer = new Utf8JsonWriter(
                   stream,
                   new JsonWriterOptions { Indented = true }))
        {
            writer.WriteStartObject();
            writer.WriteNumber("schemaVersion", LegacyOperatorSettings.CurrentSchemaVersion);
            WriteOptionalString(writer, "sceneDirectory", settings.SceneDirectory);
            WriteOptionalString(writer, "resourceDirectory", settings.ResourceDirectory);
            WriteOptionalString(writer, "backgroundDirectory", settings.BackgroundDirectory);
            writer.WriteBoolean("navigationExpanded", settings.NavigationExpanded);
            writer.WriteString("colorTheme", ColorThemeValue(settings.ColorTheme));
            writer.WriteString("viewMode", ViewModeValue(settings.ViewMode));
            writer.WriteString("startWorkspace", StartWorkspaceValue(settings.StartWorkspace));
            writer.WriteEndObject();
        }

        return stream.ToArray();
    }

    private static void WriteOptionalString(
        Utf8JsonWriter writer,
        string name,
        string? value)
    {
        if (value is null)
        {
            writer.WriteNull(name);
        }
        else
        {
            writer.WriteString(name, value);
        }
    }

    private static string ColorThemeValue(LegacyOperatorColorTheme value) => value switch
    {
        LegacyOperatorColorTheme.System => "system",
        LegacyOperatorColorTheme.Light => "light",
        LegacyOperatorColorTheme.Dark => "dark",
        _ => throw new ArgumentOutOfRangeException(nameof(value))
    };

    private static string ViewModeValue(LegacyOperatorViewMode value) => value switch
    {
        LegacyOperatorViewMode.Automatic => "automatic",
        LegacyOperatorViewMode.Compact => "compact",
        LegacyOperatorViewMode.Cards => "cards",
        _ => throw new ArgumentOutOfRangeException(nameof(value))
    };

    private static string StartWorkspaceValue(LegacyOperatorStartWorkspace value) => value switch
    {
        LegacyOperatorStartWorkspace.StockCut => "stockCut",
        LegacyOperatorStartWorkspace.LastWorkspace => "lastWorkspace",
        _ => throw new ArgumentOutOfRangeException(nameof(value))
    };

    private static bool TryDeserialize(
        ReadOnlyMemory<byte> json,
        out LegacyOperatorSettings settings,
        out LegacyOperatorSettingsStoreFailure failure)
    {
        settings = LegacyOperatorSettings.Default;
        failure = LegacyOperatorSettingsStoreFailure.InvalidJson;
        try
        {
            using var document = JsonDocument.Parse(
                json,
                new JsonDocumentOptions
                {
                    AllowTrailingCommas = false,
                    CommentHandling = JsonCommentHandling.Disallow,
                    MaxDepth = 4
                });
            if (document.RootElement.ValueKind != JsonValueKind.Object)
            {
                return false;
            }

            var properties = new Dictionary<string, JsonElement>(StringComparer.Ordinal);
            foreach (var property in document.RootElement.EnumerateObject())
            {
                if (!IsExpectedProperty(property.Name) ||
                    !properties.TryAdd(property.Name, property.Value))
                {
                    return false;
                }
            }

            if (!TryReadInt32(properties, "schemaVersion", out var schemaVersion))
            {
                return false;
            }

            if (schemaVersion is not (
                    LegacyOperatorSettings.LegacySchemaVersion or
                    LegacyOperatorSettings.CurrentSchemaVersion))
            {
                failure = LegacyOperatorSettingsStoreFailure.UnsupportedSchema;
                return false;
            }

            var expectedPropertyCount = schemaVersion ==
                LegacyOperatorSettings.LegacySchemaVersion ? 5 : 8;
            if (properties.Count != expectedPropertyCount ||
                !TryReadOptionalString(properties, "sceneDirectory", out var sceneDirectory) ||
                !TryReadOptionalString(
                    properties,
                    "resourceDirectory",
                    out var resourceDirectory) ||
                !TryReadOptionalString(
                    properties,
                    "backgroundDirectory",
                    out var backgroundDirectory) ||
                !TryReadBoolean(
                    properties,
                    "navigationExpanded",
                    out var navigationExpanded))
            {
                return false;
            }

            var colorTheme = LegacyOperatorColorTheme.System;
            var viewMode = LegacyOperatorViewMode.Automatic;
            var startWorkspace = LegacyOperatorStartWorkspace.StockCut;
            if (schemaVersion == LegacyOperatorSettings.CurrentSchemaVersion &&
                (!TryReadColorTheme(properties, out colorTheme) ||
                    !TryReadViewMode(properties, out viewMode) ||
                    !TryReadStartWorkspace(properties, out startWorkspace)))
            {
                return false;
            }

            settings = new LegacyOperatorSettings(
                sceneDirectory,
                resourceDirectory,
                backgroundDirectory,
                navigationExpanded,
                colorTheme,
                viewMode,
                startWorkspace);
            failure = LegacyOperatorSettingsStoreFailure.None;
            return true;
        }
        catch (JsonException)
        {
            return false;
        }
        catch (InvalidOperationException)
        {
            return false;
        }
        catch (FormatException)
        {
            return false;
        }
        catch (OverflowException)
        {
            return false;
        }
    }

    private static bool IsExpectedProperty(string name) => name is
        "schemaVersion" or
        "sceneDirectory" or
        "resourceDirectory" or
        "backgroundDirectory" or
        "navigationExpanded" or
        "colorTheme" or
        "viewMode" or
        "startWorkspace";

    private static bool TryReadInt32(
        IReadOnlyDictionary<string, JsonElement> properties,
        string name,
        out int value)
    {
        value = default;
        return properties.TryGetValue(name, out var element) &&
            element.ValueKind == JsonValueKind.Number &&
            element.TryGetInt32(out value);
    }

    private static bool TryReadBoolean(
        IReadOnlyDictionary<string, JsonElement> properties,
        string name,
        out bool value)
    {
        value = default;
        if (!properties.TryGetValue(name, out var element) ||
            element.ValueKind is not (JsonValueKind.True or JsonValueKind.False))
        {
            return false;
        }

        value = element.GetBoolean();
        return true;
    }

    private static bool TryReadColorTheme(
        IReadOnlyDictionary<string, JsonElement> properties,
        out LegacyOperatorColorTheme value)
    {
        value = default;
        if (!TryReadRequiredString(properties, "colorTheme", out var text))
        {
            return false;
        }

        var parsed = text switch
        {
            "system" => LegacyOperatorColorTheme.System,
            "light" => LegacyOperatorColorTheme.Light,
            "dark" => LegacyOperatorColorTheme.Dark,
            _ => (LegacyOperatorColorTheme?)null
        };
        if (!parsed.HasValue)
        {
            return false;
        }

        value = parsed.Value;
        return true;
    }

    private static bool TryReadViewMode(
        IReadOnlyDictionary<string, JsonElement> properties,
        out LegacyOperatorViewMode value)
    {
        value = default;
        if (!TryReadRequiredString(properties, "viewMode", out var text))
        {
            return false;
        }

        var parsed = text switch
        {
            "automatic" => LegacyOperatorViewMode.Automatic,
            "compact" => LegacyOperatorViewMode.Compact,
            "cards" => LegacyOperatorViewMode.Cards,
            _ => (LegacyOperatorViewMode?)null
        };
        if (!parsed.HasValue)
        {
            return false;
        }

        value = parsed.Value;
        return true;
    }

    private static bool TryReadStartWorkspace(
        IReadOnlyDictionary<string, JsonElement> properties,
        out LegacyOperatorStartWorkspace value)
    {
        value = default;
        if (!TryReadRequiredString(properties, "startWorkspace", out var text))
        {
            return false;
        }

        var parsed = text switch
        {
            "stockCut" => LegacyOperatorStartWorkspace.StockCut,
            "lastWorkspace" => LegacyOperatorStartWorkspace.LastWorkspace,
            _ => (LegacyOperatorStartWorkspace?)null
        };
        if (!parsed.HasValue)
        {
            return false;
        }

        value = parsed.Value;
        return true;
    }

    private static bool TryReadRequiredString(
        IReadOnlyDictionary<string, JsonElement> properties,
        string name,
        out string value)
    {
        value = string.Empty;
        return properties.TryGetValue(name, out var element) &&
            element.ValueKind == JsonValueKind.String &&
            (value = element.GetString() ?? string.Empty).Length > 0;
    }

    private static bool TryReadOptionalString(
        IReadOnlyDictionary<string, JsonElement> properties,
        string name,
        out string? value)
    {
        value = null;
        if (!properties.TryGetValue(name, out var element))
        {
            return false;
        }

        if (element.ValueKind == JsonValueKind.Null)
        {
            return true;
        }

        if (element.ValueKind != JsonValueKind.String)
        {
            return false;
        }

        value = element.GetString();
        return value is not null;
    }

    private static bool TryNormalizeConfigurationPath(
        string? path,
        out string normalizedPath)
    {
        normalizedPath = string.Empty;
        if (string.IsNullOrWhiteSpace(path) ||
            path.Length > LegacyOperatorFolderValidator.MaximumPathCharacters ||
            path.Any(char.IsControl) ||
            !Path.IsPathFullyQualified(path))
        {
            return false;
        }

        try
        {
            normalizedPath = Path.GetFullPath(path);
            var root = Path.GetPathRoot(normalizedPath);
            var fileName = Path.GetFileName(normalizedPath);
            return !string.IsNullOrEmpty(root) &&
                !root.StartsWith("\\\\", StringComparison.Ordinal) &&
                !string.IsNullOrWhiteSpace(fileName) &&
                !fileName.Any(char.IsControl);
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or NotSupportedException or
            System.Security.SecurityException)
        {
            normalizedPath = string.Empty;
            return false;
        }
    }

    private static bool TryCreateSafeConfigurationDirectory(
        string directory,
        out string canonicalDirectory)
    {
        canonicalDirectory = string.Empty;
        try
        {
            var existingAncestor = directory;
            while (!Directory.Exists(existingAncestor))
            {
                var parent = Path.GetDirectoryName(existingAncestor);
                if (string.IsNullOrEmpty(parent) ||
                    string.Equals(parent, existingAncestor, StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }

                existingAncestor = parent;
            }

            if (!LegacyOperatorFolderValidator.TryCanonicalizeDirectory(
                    existingAncestor,
                    out _,
                    out _))
            {
                return false;
            }

            Directory.CreateDirectory(directory);
            return LegacyOperatorFolderValidator.TryCanonicalizeDirectory(
                directory,
                out canonicalDirectory,
                out _);
        }
        catch (Exception exception) when (
            exception is ArgumentException or IOException or UnauthorizedAccessException or
            NotSupportedException or System.Security.SecurityException)
        {
            canonicalDirectory = string.Empty;
            return false;
        }
    }

    private static LegacyOperatorSettingsLoadResult FailedLoad(
        LegacyOperatorSettingsStoreFailure failure) => new(
            LegacyOperatorSettings.Default,
            LoadWarningMessage)
        {
            Failure = failure,
            LoadedFromDisk = false
        };

    private static LegacyOperatorSettingsSaveResult FailedSave(
        LegacyOperatorSettingsStoreFailure failure) => new(
            Succeeded: false,
            SaveWarningMessage)
        {
            Failure = failure,
            SavedSettings = null
        };
}
