using System;
using System.Globalization;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using KAsyncEngineLib;
using Tornado3_2026Election.Services;

if (args.Length > 0 && string.Equals(args[0], "--reflect-api", StringComparison.OrdinalIgnoreCase))
{
    PrintApiSurface();
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--test-counter", StringComparison.OrdinalIgnoreCase))
{
    var counterOptions = CounterProbeOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma counter probe starting. target={counterOptions.Connection.Host}:{counterOptions.Connection.Port} " +
        $"scene={counterOptions.ScenePath} object={counterOptions.ObjectName} keyIndex={counterOptions.KeyIndex} value={counterOptions.Number:0.###}");
    var counterResult = await ProbeCounterAsync(counterOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(counterResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {counterResult.SceneLoadOutcome}");
    Console.WriteLine($"- Counter Update: {counterResult.CounterOutcome}");
    Console.WriteLine($"- Detail: {counterResult.Detail}");
    Environment.ExitCode = counterResult.ConnectRequestAccepted &&
                           counterResult.SceneLoadOutcome == "SUCCESS" &&
                           counterResult.CounterOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--inspect-chart", StringComparison.OrdinalIgnoreCase))
{
    var chartOptions = ChartInspectionOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma chart inspection starting. target={chartOptions.Connection.Host}:{chartOptions.Connection.Port} " +
        $"scene={chartOptions.ScenePath} object={chartOptions.ObjectName}");
    var chartResult = await InspectChartAsync(chartOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(chartResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {chartResult.SceneLoadOutcome}");
    Console.WriteLine($"- Chart Inspection: {chartResult.InspectionOutcome}");
    Console.WriteLine($"- Rows: {chartResult.RowCount}");
    Console.WriteLine($"- Columns: {chartResult.ColumnCount}");
    if (chartResult.Cells.Count > 0)
    {
        Console.WriteLine("- Cells:");
        foreach (var cell in chartResult.Cells)
        {
            Console.WriteLine($"  - ({cell.Row},{cell.Column}) = {cell.Value}");
        }
    }
    Console.WriteLine($"- Detail: {chartResult.Detail}");
    Environment.ExitCode = chartResult.ConnectRequestAccepted &&
                           chartResult.SceneLoadOutcome == "SUCCESS" &&
                           chartResult.InspectionOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--catalog-scenes", StringComparison.OrdinalIgnoreCase))
{
    var catalogOptions = SceneCatalogOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma scene catalog starting. target={catalogOptions.Connection.Host}:{catalogOptions.Connection.Port} " +
        $"root={catalogOptions.RootPath} output={catalogOptions.OutputPath}");
    var catalogResult = await CatalogScenesAsync(catalogOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(catalogResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scenes Found: {catalogResult.SceneCount}");
    Console.WriteLine($"- Scenes Cataloged: {catalogResult.CatalogedCount}");
    Console.WriteLine($"- Failures: {catalogResult.Failures.Count}");
    Console.WriteLine($"- Output: {catalogResult.OutputPath}");
    if (catalogResult.Failures.Count > 0)
    {
        Console.WriteLine("- Failure Details:");
        foreach (var failure in catalogResult.Failures.Take(20))
        {
            Console.WriteLine($"  - {failure.ScenePath}: {failure.Reason}");
        }
    }

    Environment.ExitCode = catalogResult.ConnectRequestAccepted && catalogResult.Failures.Count == 0
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--inspect-scene-objects", StringComparison.OrdinalIgnoreCase))
{
    var inspectionOptions = SceneObjectInspectionOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma scene object inspection starting. target={inspectionOptions.Connection.Host}:{inspectionOptions.Connection.Port} " +
        $"scene={inspectionOptions.ScenePath} output={inspectionOptions.OutputPath}");
    var inspectionResult = await InspectSceneObjectsAsync(inspectionOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(inspectionResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {inspectionResult.SceneLoadOutcome}");
    Console.WriteLine($"- Object Infos: {inspectionResult.ObjectInfoOutcome}");
    Console.WriteLine($"- Objects: {inspectionResult.Objects.Count}");
    Console.WriteLine($"- Output: {inspectionResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(inspectionResult.Detail))
    {
        Console.WriteLine($"- Detail: {inspectionResult.Detail}");
    }

    Environment.ExitCode = inspectionResult.ConnectRequestAccepted &&
                           inspectionResult.SceneLoadOutcome == "SUCCESS" &&
                           inspectionResult.ObjectInfoOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--query-object-types", StringComparison.OrdinalIgnoreCase))
{
    var objectTypeOptions = ObjectTypeQueryOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma object type query starting. target={objectTypeOptions.Connection.Host}:{objectTypeOptions.Connection.Port} " +
        $"scene={objectTypeOptions.ScenePath} objects={objectTypeOptions.ObjectNames.Count}");
    var objectTypeResult = await QueryObjectTypesAsync(objectTypeOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(objectTypeResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {objectTypeResult.SceneLoadOutcome}");
    Console.WriteLine($"- Queried: {objectTypeResult.Items.Count}");
    Console.WriteLine($"- Output: {objectTypeResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(objectTypeResult.Detail))
    {
        Console.WriteLine($"- Detail: {objectTypeResult.Detail}");
    }

    foreach (var item in objectTypeResult.Items)
    {
        Console.WriteLine($"  - {item.ObjectName}: {item.Result} {item.ObjectType}");
    }

    Environment.ExitCode = objectTypeResult.ConnectRequestAccepted &&
                           objectTypeResult.SceneLoadOutcome == "SUCCESS" &&
                           objectTypeResult.Items.All(item => string.Equals(item.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--inspect-object-runtime", StringComparison.OrdinalIgnoreCase))
{
    var runtimeOptions = ObjectTypeQueryOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma object runtime inspection starting. target={runtimeOptions.Connection.Host}:{runtimeOptions.Connection.Port} " +
        $"scene={runtimeOptions.ScenePath} objects={runtimeOptions.ObjectNames.Count} output={runtimeOptions.OutputPath}");
    var runtimeResult = await InspectObjectRuntimeAsync(runtimeOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(runtimeResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {runtimeResult.SceneLoadOutcome}");
    Console.WriteLine($"- Objects Inspected: {runtimeResult.Items.Count}");
    Console.WriteLine($"- Output: {runtimeResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(runtimeResult.Detail))
    {
        Console.WriteLine($"- Detail: {runtimeResult.Detail}");
    }

    Environment.ExitCode = runtimeResult.ConnectRequestAccepted &&
                           runtimeResult.SceneLoadOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--query-objects-by-screen-point", StringComparison.OrdinalIgnoreCase))
{
    var screenPointOptions = ScreenPointInspectionOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma screen-point inspection starting. target={screenPointOptions.Connection.Host}:{screenPointOptions.Connection.Port} " +
        $"scene={screenPointOptions.ScenePath} points={screenPointOptions.Points.Count} output={screenPointOptions.OutputPath}");
    var screenPointResult = await InspectScreenPointsAsync(screenPointOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(screenPointResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {screenPointResult.SceneLoadOutcome}");
    Console.WriteLine($"- Points Inspected: {screenPointResult.Items.Count}");
    Console.WriteLine($"- Output: {screenPointResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(screenPointResult.Detail))
    {
        Console.WriteLine($"- Detail: {screenPointResult.Detail}");
    }

    Environment.ExitCode = screenPointResult.ConnectRequestAccepted &&
                           screenPointResult.SceneLoadOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--inspect-scene-capabilities", StringComparison.OrdinalIgnoreCase))
{
    var capabilityOptions = SceneCapabilityInspectionOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma scene capability inspection starting. target={capabilityOptions.Connection.Host}:{capabilityOptions.Connection.Port} " +
        $"scene={capabilityOptions.ScenePath} output={capabilityOptions.OutputPath}");
    var capabilityResult = await InspectSceneCapabilitiesAsync(capabilityOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(capabilityResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {capabilityResult.SceneLoadOutcome}");
    Console.WriteLine($"- Candidates Inspected: {capabilityResult.Items.Count}");
    Console.WriteLine($"- Output: {capabilityResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(capabilityResult.Detail))
    {
        Console.WriteLine($"- Detail: {capabilityResult.Detail}");
    }

    Environment.ExitCode = capabilityResult.ConnectRequestAccepted &&
                           capabilityResult.SceneLoadOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--validate-scene-values", StringComparison.OrdinalIgnoreCase))
{
    var validationOptions = SceneValidationOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma scene validation starting. target={validationOptions.Connection.Host}:{validationOptions.Connection.Port} " +
        $"scene={validationOptions.ScenePath} ops={validationOptions.OperationsPath} output={validationOptions.OutputPath}");
    var validationResult = await ValidateSceneOperationsAsync(validationOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(validationResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {validationResult.SceneLoadOutcome}");
    Console.WriteLine($"- Success Count: {validationResult.SuccessCount}");
    Console.WriteLine($"- Failure Count: {validationResult.FailureCount}");
    Console.WriteLine($"- Output: {validationResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(validationResult.Detail))
    {
        Console.WriteLine($"- Detail: {validationResult.Detail}");
    }

    Environment.ExitCode = validationResult.ConnectRequestAccepted &&
                           validationResult.SceneLoadOutcome == "SUCCESS" &&
                           validationResult.FailureCount == 0
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--inspect-tscn-folder", StringComparison.OrdinalIgnoreCase))
{
    var inspectionOptions = FolderInspectionOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma tscn inspection starting. target={inspectionOptions.Connection.Host}:{inspectionOptions.Connection.Port} " +
        $"root={inspectionOptions.RootPath} output={inspectionOptions.OutputPath}");
    var inspectionResult = await InspectTscnFolderAsync(inspectionOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(inspectionResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scenes Found: {inspectionResult.SceneCount}");
    Console.WriteLine($"- Scenes Processed: {inspectionResult.ProcessedSceneCount}");
    Console.WriteLine($"- Variables Found: {inspectionResult.DiscoveredVariableCount}");
    Console.WriteLine($"- Failures: {inspectionResult.FailureCount}");
    Console.WriteLine($"- Output: {inspectionResult.OutputPath}");
    if (!string.IsNullOrWhiteSpace(inspectionResult.Detail))
    {
        Console.WriteLine($"- Detail: {inspectionResult.Detail}");
    }

    Environment.ExitCode = inspectionResult.ConnectRequestAccepted && inspectionResult.FailureCount == 0
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--save-scene-image", StringComparison.OrdinalIgnoreCase))
{
    var saveImageOptions = SaveSceneImageOptions.Parse(args[1..]);
    Console.WriteLine(
        $"Karisma scene image save starting. target={saveImageOptions.Connection.Host}:{saveImageOptions.Connection.Port} " +
        $"scene={saveImageOptions.ScenePath} output={saveImageOptions.OutputPath} size={saveImageOptions.Width}x{saveImageOptions.Height} frame={saveImageOptions.Frame}");
    var saveImageResult = await SaveSceneImageAsync(saveImageOptions).ConfigureAwait(false);

    Console.WriteLine();
    Console.WriteLine("Summary");
    Console.WriteLine($"- SDK Connect(): {(saveImageResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
    Console.WriteLine($"- Scene Load: {saveImageResult.SceneLoadOutcome}");
    Console.WriteLine($"- Save Scene Image: {saveImageResult.SaveOutcome}");
    Console.WriteLine($"- Output: {saveImageResult.OutputPath}");
    Console.WriteLine($"- Detail: {saveImageResult.Detail}");
    Environment.ExitCode = saveImageResult.ConnectRequestAccepted &&
                           saveImageResult.SceneLoadOutcome == "SUCCESS" &&
                           saveImageResult.SaveOutcome == "SUCCESS"
        ? 0
        : 1;
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--validate-live-cuts", StringComparison.OrdinalIgnoreCase))
{
    Environment.ExitCode = await LiveCutValidation.RunAsync(args[1..]).ConfigureAwait(false);
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--audit-party-colors-live", StringComparison.OrdinalIgnoreCase))
{
    Environment.ExitCode = await LiveCutValidation.RunPartyColorAuditAsync(args[1..]).ConfigureAwait(false);
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--validate-current-api-cuts", StringComparison.OrdinalIgnoreCase))
{
    Environment.ExitCode = await CurrentApiCutDiagnostics.RunAsync(args[1..]).ConfigureAwait(false);
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--sweep-cut-debug", StringComparison.OrdinalIgnoreCase))
{
    Environment.ExitCode = await LiveCutValidation.RunCutDebugSweepAsync(args[1..]).ConfigureAwait(false);
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--report-cut-debug-coverage", StringComparison.OrdinalIgnoreCase))
{
    Environment.ExitCode = LiveCutValidation.RunCutDebugCoverageReport(args[1..]);
    return;
}

if (args.Length > 0 && string.Equals(args[0], "--audit-cut-files", StringComparison.OrdinalIgnoreCase))
{
    Environment.ExitCode = await CutFileAudit.RunAsync(args[1..]).ConfigureAwait(false);
    return;
}

var options = ProbeOptions.Parse(args);

Console.WriteLine($"Karisma TCP probe starting. target={options.Host}:{options.Port} timeout={options.Timeout.TotalSeconds:0}s");

var rawTcpOk = await ProbeRawTcpAsync(options).ConfigureAwait(false);
var sdkResult = await ProbeSdkAsync(options).ConfigureAwait(false);

Console.WriteLine();
Console.WriteLine("Summary");
Console.WriteLine($"- Raw TCP: {(rawTcpOk ? "OK" : "FAILED")}");
Console.WriteLine($"- SDK Connect(): {(sdkResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}");
Console.WriteLine($"- SDK OnConnect: {sdkResult.ConnectOutcome}");
Console.WriteLine($"- SDK Detail: {sdkResult.Detail}");

Environment.ExitCode = rawTcpOk && sdkResult.ConnectRequestAccepted && sdkResult.ConnectOutcome == "SUCCESS"
    ? 0
    : 1;

static void PrintApiSurface()
{
    PrintTypeMethods(typeof(IKAObject));
    PrintTypeMethods(typeof(KAObject));
    PrintTypeMethods(typeof(IKAScene));
    PrintTypeMethods(typeof(KAScene));
    PrintTypeMethods(typeof(IKAEngine));
    PrintTypeMethods(typeof(KAEngine));
    PrintTypeMethods(typeof(IKACounter));
    PrintTypeMethods(typeof(KACounter));
    PrintTypeMethods(typeof(IKAObjectInfos));
    PrintTypeMethods(typeof(KAObjectInfos));
    PrintStructFields(typeof(sKObjectInfo));
    PrintEnumValues(typeof(eKObjectType));
}

static void PrintTypeMethods(Type type)
{
    Console.WriteLine(type.FullName);
    foreach (var method in type.GetMethods())
    {
        if (!method.Name.Contains("Counter", StringComparison.OrdinalIgnoreCase) &&
            !string.Equals(method.Name, "SetValue", StringComparison.OrdinalIgnoreCase) &&
            !method.Name.Contains("GetObject", StringComparison.OrdinalIgnoreCase) &&
            !method.Name.Contains("QueryObjectInfos", StringComparison.OrdinalIgnoreCase) &&
            !method.Name.Contains("GetCount", StringComparison.OrdinalIgnoreCase))
        {
            continue;
        }

        var parameters = string.Join(
            ", ",
            method.GetParameters().Select(parameter => $"{parameter.ParameterType.Name} {parameter.Name}"));
        Console.WriteLine($"- {method.ReturnType.Name} {method.Name}({parameters})");
    }

    Console.WriteLine();
}

static void PrintStructFields(Type type)
{
    Console.WriteLine(type.FullName);
    foreach (var field in type.GetFields())
    {
        Console.WriteLine($"- {field.FieldType.Name} {field.Name}");
    }

    Console.WriteLine();
}

static void PrintEnumValues(Type type)
{
    Console.WriteLine(type.FullName);
    foreach (var name in Enum.GetNames(type))
    {
        Console.WriteLine($"- {name}");
    }

    Console.WriteLine();
}

static async Task<bool> ProbeRawTcpAsync(ProbeOptions options)
{
    try
    {
        using var client = new TcpClient();
        using var cts = new CancellationTokenSource(options.Timeout);
        await client.ConnectAsync(options.Host, options.Port, cts.Token).ConfigureAwait(false);
        Console.WriteLine($"[RAW] Connected local={client.Client.LocalEndPoint} remote={client.Client.RemoteEndPoint}");
        return true;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[RAW] Failed: {ex.Message}");
        return false;
    }
}

static Task<CounterProbeResult> ProbeCounterAsync(CounterProbeOptions options)
{
    var completion = new TaskCompletionSource<CounterProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        try
        {
            var handler = new ProbeEventHandler();
            var engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[COUNTER] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[COUNTER] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(new CounterProbeResult(false, "NOT_RUN", "NOT_RUN", "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new CounterProbeResult(true, "NOT_RUN", "TIMEOUT", "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new CounterProbeResult(true, "NOT_RUN", "FAILED", $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[COUNTER] Loading scene...");
            var scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(new CounterProbeResult(true, "FAILED", "NOT_RUN", "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new CounterProbeResult(true, "TIMEOUT", "NOT_RUN", "OnLoadScene timed out."));
                return;
            }

            var loadSceneResult = handler.LoadSceneTask.Result;
            if (loadSceneResult != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new CounterProbeResult(true, loadSceneResult.ToString(), "NOT_RUN", $"OnLoadScene result={loadSceneResult}"));
                return;
            }

            Console.WriteLine("[COUNTER] Resolving object...");
            var sceneObject = scene.GetObject(options.ObjectName);
            if (sceneObject is not IKACounter counter)
            {
                completion.TrySetResult(
                    new CounterProbeResult(true, "SUCCESS", "FAILED", $"Object '{options.ObjectName}' is not an IKACounter."));
                return;
            }

            Console.WriteLine("[COUNTER] Calling SetCounterNumberKey()...");
            counter.SetCounterNumberKey(options.KeyIndex, options.Number);

            if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new CounterProbeResult(true, "SUCCESS", "TIMEOUT", "OnSetCounterNumberKey timed out."));
                return;
            }

            var counterResult = handler.CounterNumberKeyTask.Result;
            completion.TrySetResult(
                new CounterProbeResult(
                    true,
                    "SUCCESS",
                    counterResult == eKResult.RESULT_SUCCESS ? "SUCCESS" : counterResult.ToString(),
                    $"OnSetCounterNumberKey result={counterResult}"));

            try
            {
                engine.Disconnect();
                handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
            }
            catch
            {
            }
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new CounterProbeResult(false, "EXCEPTION", "EXCEPTION", ex.ToString()));
        }
    })
    {
        IsBackground = true,
        Name = "KarismaCounterProbe"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<ChartInspectionProbeResult> InspectChartAsync(ChartInspectionOptions options)
{
    var completion = new TaskCompletionSource<ChartInspectionProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;
        IKAScene? scene = null;
        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[CHART] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[CHART] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(new ChartInspectionProbeResult(false, "NOT_RUN", "NOT_RUN", 0, 0, Array.Empty<ChartCellSnapshot>(), "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new ChartInspectionProbeResult(true, "NOT_RUN", "TIMEOUT", 0, 0, Array.Empty<ChartCellSnapshot>(), "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new ChartInspectionProbeResult(true, "NOT_RUN", "FAILED", 0, 0, Array.Empty<ChartCellSnapshot>(), $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[CHART] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(new ChartInspectionProbeResult(true, "FAILED", "NOT_RUN", 0, 0, Array.Empty<ChartCellSnapshot>(), "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new ChartInspectionProbeResult(true, "TIMEOUT", "NOT_RUN", 0, 0, Array.Empty<ChartCellSnapshot>(), "OnLoadScene timed out."));
                return;
            }

            var loadSceneResult = handler.LoadSceneTask.Result;
            if (loadSceneResult != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new ChartInspectionProbeResult(true, loadSceneResult.ToString(), "NOT_RUN", 0, 0, Array.Empty<ChartCellSnapshot>(), $"OnLoadScene result={loadSceneResult}"));
                return;
            }

            Console.WriteLine("[CHART] Resolving object...");
            var sceneObject = scene.GetObject(options.ObjectName);
            if (sceneObject is not IKAChart chart)
            {
                completion.TrySetResult(
                    new ChartInspectionProbeResult(
                        true,
                        "SUCCESS",
                        "FAILED",
                        0,
                        0,
                        Array.Empty<ChartCellSnapshot>(),
                        sceneObject is null
                            ? $"Object '{options.ObjectName}' was not found."
                            : $"Object '{options.ObjectName}' is not an IKAChart ({sceneObject.GetType().FullName})."));
                return;
            }

            Console.WriteLine("[CHART] Calling QueryChartDataTable()...");
            handler.ResetChartDataTableTask();
            chart.QueryChartDataTable();

            if (!WaitForTaskWithMessagePump(handler.ChartDataTableTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ChartInspectionProbeResult(true, "SUCCESS", "TIMEOUT", 0, 0, Array.Empty<ChartCellSnapshot>(), "OnQueryChartDataTable timed out."));
                return;
            }

            var chartResult = handler.ChartDataTableTask.Result;
            completion.TrySetResult(
                new ChartInspectionProbeResult(
                    true,
                    "SUCCESS",
                    chartResult.Result == eKResult.RESULT_SUCCESS ? "SUCCESS" : chartResult.Result.ToString(),
                    chartResult.RowCount,
                    chartResult.ColumnCount,
                    chartResult.Cells,
                    string.IsNullOrWhiteSpace(chartResult.Detail)
                        ? $"OnQueryChartDataTable result={chartResult.Result}"
                        : chartResult.Detail));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new ChartInspectionProbeResult(false, "EXCEPTION", "EXCEPTION", 0, 0, Array.Empty<ChartCellSnapshot>(), ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaChartInspectionProbe"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<SaveSceneImageProbeResult> SaveSceneImageAsync(SaveSceneImageOptions options)
{
    var completion = new TaskCompletionSource<SaveSceneImageProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;
        IKAScene? scene = null;
        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[SAVE-IMAGE] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[SAVE-IMAGE] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(new SaveSceneImageProbeResult(false, "NOT_RUN", "NOT_RUN", options.OutputPath, "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new SaveSceneImageProbeResult(true, "NOT_RUN", "TIMEOUT", options.OutputPath, "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new SaveSceneImageProbeResult(true, "NOT_RUN", "FAILED", options.OutputPath, $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[SAVE-IMAGE] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(new SaveSceneImageProbeResult(true, "FAILED", "NOT_RUN", options.OutputPath, "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new SaveSceneImageProbeResult(true, "TIMEOUT", "NOT_RUN", options.OutputPath, "OnLoadScene timed out."));
                return;
            }

            if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new SaveSceneImageProbeResult(true, handler.LoadSceneTask.Result.ToString(), "NOT_RUN", options.OutputPath, $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                return;
            }

            if (options.CloneObject is not null)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Adding clone source={options.CloneObject.SourceObjectName} " +
                    $"variable={options.CloneObject.VariableName}...");
                var sceneObject = scene.GetObject(options.CloneObject.SourceObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.CloneObject.SourceObjectName}' was not found."));
                    return;
                }

                handler.ResetAddCloneObjectTask();
                scene.AddCloneObject(sceneObject, options.CloneObject.VariableName);

                if (!WaitForTaskWithMessagePump(handler.AddCloneObjectTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnAddCloneObject timed out for '{options.CloneObject.SourceObjectName}'." ));
                    return;
                }

                var cloneResult = handler.AddCloneObjectTask.Result;
                if (cloneResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", cloneResult.ToString(), options.OutputPath, $"OnAddCloneObject result={cloneResult} source={options.CloneObject.SourceObjectName} variable={options.CloneObject.VariableName}"));
                    return;
                }
            }

            if (options.VariableName is not null)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting variable name object={options.VariableName.ObjectName} " +
                    $"value={options.VariableName.VariableName}...");
                var sceneObject = scene.GetObject(options.VariableName.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.VariableName.ObjectName}' was not found."));
                    return;
                }

                handler.ResetVariableNameTask();
                sceneObject.SetVariableName(options.VariableName.VariableName);

                if (!WaitForTaskWithMessagePump(handler.VariableNameTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetVariableName timed out for '{options.VariableName.ObjectName}'." ));
                    return;
                }

                var variableNameResult = handler.VariableNameTask.Result;
                if (variableNameResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", variableNameResult.ToString(), options.OutputPath, $"OnSetVariableName result={variableNameResult} object={options.VariableName.ObjectName}"));
                    return;
                }
            }

            if (!string.IsNullOrWhiteSpace(options.SetObjectName))
            {
                Console.WriteLine($"[SAVE-IMAGE] Setting value object={options.SetObjectName}...");
                var sceneObject = scene.GetObject(options.SetObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.SetObjectName}' was not found."));
                    return;
                }

                handler.ResetSetValueTask();
                sceneObject.SetValue(options.SetObjectValue ?? string.Empty);

                if (!WaitForTaskWithMessagePump(handler.SetValueTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetValue timed out for '{options.SetObjectName}'."));
                    return;
                }

                var setValueResult = handler.SetValueTask.Result;
                if (setValueResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", setValueResult.ToString(), options.OutputPath, $"OnSetValue result={setValueResult} object={options.SetObjectName}"));
                    return;
                }
            }

            if (!string.IsNullOrWhiteSpace(options.VisibleObjectName) && options.VisibleObjectValue.HasValue)
            {
                Console.WriteLine($"[SAVE-IMAGE] Setting visibility object={options.VisibleObjectName} visible={options.VisibleObjectValue.Value}...");
                var sceneObject = scene.GetObject(options.VisibleObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.VisibleObjectName}' was not found."));
                    return;
                }

                handler.ResetVisibleTask();
                sceneObject.SetVisible(options.VisibleObjectValue.Value ? 1 : 0);

                if (!WaitForTaskWithMessagePump(handler.VisibleTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetVisible timed out for '{options.VisibleObjectName}'."));
                    return;
                }

                var visibleResult = handler.VisibleTask.Result;
                if (visibleResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", visibleResult.ToString(), options.OutputPath, $"OnSetVisible result={visibleResult} object={options.VisibleObjectName}"));
                    return;
                }
            }

            IReadOnlyList<SceneValidationOperation> operations = string.IsNullOrWhiteSpace(options.OperationsPath)
                ? Array.Empty<SceneValidationOperation>()
                : LoadSceneOperations(options.ScenePath, options.OperationsPath);
            foreach (var operation in operations)
            {
                var operationResult = ApplySceneOperation(handler, scene!, operation, options.Connection.Timeout);
                if (!string.Equals(operationResult.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))
                {
                    if (operation.ContinueOnFailure)
                    {
                        Console.WriteLine(
                            $"[SAVE-IMAGE] Optional operation {operationResult.Method} failed for '{operationResult.ObjectName}': " +
                            $"{operationResult.Result} {operationResult.Detail}");
                        continue;
                    }

                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(
                            true,
                            "SUCCESS",
                            operationResult.Result,
                            options.OutputPath,
                            $"Operation {operationResult.Method} failed for '{operationResult.ObjectName}': {operationResult.Detail}"));
                    return;
                }
            }

            if (options.MaterialOpacity is not null)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting material opacity object={options.MaterialOpacity.ObjectName} " +
                    $"value={options.MaterialOpacity.Opacity}...");
                var sceneObject = scene.GetObject(options.MaterialOpacity.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.MaterialOpacity.ObjectName}' was not found."));
                    return;
                }

                var material = sceneObject.GetTargetMaterial(eKMaterialTarget.MATERIAL_TARGET_DEFAULT);
                if (material is not IKAMaterial targetMaterial)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.MaterialOpacity.ObjectName}' did not return a material."));
                    return;
                }

                handler.ResetMaterialOpacityTask();
                targetMaterial.SetTransparencyOpacity(options.MaterialOpacity.Opacity);

                if (!WaitForTaskWithMessagePump(handler.MaterialOpacityTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetTransparencyOpacity timed out for '{options.MaterialOpacity.ObjectName}'." ));
                    return;
                }

                var opacityResult = handler.MaterialOpacityTask.Result;
                if (opacityResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", opacityResult.ToString(), options.OutputPath, $"OnSetTransparencyOpacity result={opacityResult} object={options.MaterialOpacity.ObjectName}"));
                    return;
                }
            }

            if (options.Size is not null)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting size object={options.Size.ObjectName} value=({options.Size.Width},{options.Size.Height})...");
                var sceneObject = scene.GetObject(options.Size.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.Size.ObjectName}' was not found."));
                    return;
                }

                handler.ResetSizeTask();
                sceneObject.SetSize(options.Size.Width, options.Size.Height);

                if (!WaitForTaskWithMessagePump(handler.SizeTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetSize timed out for '{options.Size.ObjectName}'." ));
                    return;
                }

                var sizeResult = handler.SizeTask.Result;
                if (sizeResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", sizeResult.ToString(), options.OutputPath, $"OnSetSize result={sizeResult} object={options.Size.ObjectName}"));
                    return;
                }
            }

            var positionUpdates = new List<PositionUpdate>();
            if (options.Position is not null)
            {
                positionUpdates.Add(options.Position);
            }

            positionUpdates.AddRange(options.Positions);
            foreach (var positionUpdate in positionUpdates)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting position object={positionUpdate.ObjectName} " +
                    $"value=({positionUpdate.X},{positionUpdate.Y},{positionUpdate.Z}) vector={positionUpdate.VectorType}...");
                var sceneObject = scene.GetObject(positionUpdate.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionUpdate.ObjectName}' was not found."));
                    return;
                }

                handler.ResetPositionTask();
                sceneObject.SetPosition(
                    positionUpdate.X,
                    positionUpdate.Y,
                    positionUpdate.Z,
                    positionUpdate.VectorType);

                if (!WaitForTaskWithMessagePump(handler.PositionTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPosition timed out for '{positionUpdate.ObjectName}'." ));
                    return;
                }

                var positionResult = handler.PositionTask.Result;
                if (positionResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", positionResult.ToString(), options.OutputPath, $"OnSetPosition result={positionResult} object={positionUpdate.ObjectName}"));
                    return;
                }
            }

            var positionKeyUpdates = new List<PositionKeyUpdate>();
            if (options.PositionKey is not null)
            {
                positionKeyUpdates.Add(options.PositionKey);
            }

            positionKeyUpdates.AddRange(options.PositionKeys);
            foreach (var positionKeyUpdate in positionKeyUpdates)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting position key object={positionKeyUpdate.ObjectName} index={positionKeyUpdate.KeyIndex} " +
                    $"value=({positionKeyUpdate.X},{positionKeyUpdate.Y},{positionKeyUpdate.Z}) vector={positionKeyUpdate.VectorType}...");
                var sceneObject = scene.GetObject(positionKeyUpdate.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionKeyUpdate.ObjectName}' was not found."));
                    return;
                }

                handler.ResetPositionKeyTask();
                sceneObject.SetPositionKey(
                    positionKeyUpdate.KeyIndex,
                    positionKeyUpdate.X,
                    positionKeyUpdate.Y,
                    positionKeyUpdate.Z,
                    positionKeyUpdate.VectorType);

                if (!WaitForTaskWithMessagePump(handler.PositionKeyTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPositionKey timed out for '{positionKeyUpdate.ObjectName}'." ));
                    return;
                }

                var positionKeyResult = handler.PositionKeyTask.Result;
                if (positionKeyResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", positionKeyResult.ToString(), options.OutputPath, $"OnSetPositionKey result={positionKeyResult} object={positionKeyUpdate.ObjectName}"));
                    return;
                }
            }

            if (!string.IsNullOrWhiteSpace(options.ChartObjectName))
            {
                var sceneObject = scene.GetObject(options.ChartObjectName);
                if (sceneObject is not IKAChart chart)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(
                            true,
                            "SUCCESS",
                            "FAILED",
                            options.OutputPath,
                            sceneObject is null
                                ? $"Object '{options.ChartObjectName}' was not found."
                                : $"Object '{options.ChartObjectName}' does not implement IKAChart ({sceneObject.GetType().FullName})."));
                    return;
                }

                if (!string.IsNullOrWhiteSpace(options.ChartCsvPath))
                {
                    Console.WriteLine($"[SAVE-IMAGE] Setting chart csv object={options.ChartObjectName} csv={options.ChartCsvPath}...");
                    handler.ResetChartMutationTask();
                    chart.SetChartCSVFile(options.ChartCsvPath);
                    if (!WaitForTaskWithMessagePump(handler.ChartMutationTask, options.Connection.Timeout))
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetChartCSVFile timed out for '{options.ChartObjectName}'." ));
                        return;
                    }

                    if (handler.ChartMutationTask.Result != eKResult.RESULT_SUCCESS)
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", handler.ChartMutationTask.Result.ToString(), options.OutputPath, $"OnSetChartCSVFile result={handler.ChartMutationTask.Result} object={options.ChartObjectName}"));
                        return;
                    }
                }

                foreach (var cell in options.ChartCells)
                {
                    Console.WriteLine($"[SAVE-IMAGE] Setting chart cell object={options.ChartObjectName} row={cell.Row} col={cell.Column} value={cell.Value}...");
                    handler.ResetChartMutationTask();
                    chart.SetChartCellData(cell.Row, cell.Column, cell.Value);
                    if (!WaitForTaskWithMessagePump(handler.ChartMutationTask, options.Connection.Timeout))
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetChartCellData timed out for '{options.ChartObjectName}'." ));
                        return;
                    }

                    if (handler.ChartMutationTask.Result != eKResult.RESULT_SUCCESS)
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", handler.ChartMutationTask.Result.ToString(), options.OutputPath, $"OnSetChartCellData result={handler.ChartMutationTask.Result} object={options.ChartObjectName} row={cell.Row} col={cell.Column}"));
                        return;
                    }
                }
            }

            if (!string.IsNullOrWhiteSpace(options.PathObjectName))
            {
                Console.WriteLine($"[SAVE-IMAGE] Rebuilding path object={options.PathObjectName}...");
                var sceneObject = scene.GetObject(options.PathObjectName);
                if (sceneObject is not IKAPath path)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(
                            true,
                            "SUCCESS",
                            "FAILED",
                            options.OutputPath,
                            sceneObject is null
                                ? $"Object '{options.PathObjectName}' was not found."
                                : $"Object '{options.PathObjectName}' does not implement IKAPath ({sceneObject.GetType().FullName})."));
                    return;
                }

                if (options.PathPoints.Count > 0)
                {
                    handler.ResetPathPointTask();
                    path.ClearPathPoints();
                    if (!WaitForTaskWithMessagePump(handler.PathPointTask, options.Connection.Timeout))
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnClearPathPoints timed out for '{options.PathObjectName}'."));
                        return;
                    }

                    if (handler.PathPointTask.Result != eKResult.RESULT_SUCCESS)
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", handler.PathPointTask.Result.ToString(), options.OutputPath, $"OnClearPathPoints result={handler.PathPointTask.Result} object={options.PathObjectName}"));
                        return;
                    }

                    foreach (var point in options.PathPoints)
                    {
                        handler.ResetPathPointTask();
                        path.AddPathPoint(point.X, point.Y, point.Z);

                        if (!WaitForTaskWithMessagePump(handler.PathPointTask, options.Connection.Timeout))
                        {
                            completion.TrySetResult(
                                new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnAddPathPoint timed out for '{options.PathObjectName}'."));
                            return;
                        }

                        if (handler.PathPointTask.Result != eKResult.RESULT_SUCCESS)
                        {
                            completion.TrySetResult(
                                new SaveSceneImageProbeResult(true, "SUCCESS", handler.PathPointTask.Result.ToString(), options.OutputPath, $"OnAddPathPoint result={handler.PathPointTask.Result} object={options.PathObjectName}"));
                            return;
                        }
                    }
                }

                foreach (var modification in options.PathModifications)
                {
                    handler.ResetPathPointTask();
                    path.ModifyPathPoint(
                        modification.Index,
                        modification.X,
                        modification.Y,
                        modification.Z,
                        modification.VectorType);

                    if (!WaitForTaskWithMessagePump(handler.PathPointTask, options.Connection.Timeout))
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnModifyPathPoint timed out for '{options.PathObjectName}'."));
                        return;
                    }

                    if (handler.PathPointTask.Result != eKResult.RESULT_SUCCESS)
                    {
                        completion.TrySetResult(
                            new SaveSceneImageProbeResult(true, "SUCCESS", handler.PathPointTask.Result.ToString(), options.OutputPath, $"OnModifyPathPoint result={handler.PathPointTask.Result} object={options.PathObjectName} index={modification.Index}"));
                        return;
                    }
                }
            }

            foreach (var positionUpdate in options.PostPositions)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting post-chart position object={positionUpdate.ObjectName} " +
                    $"value=({positionUpdate.X},{positionUpdate.Y},{positionUpdate.Z}) vector={positionUpdate.VectorType}...");
                var sceneObject = scene.GetObject(positionUpdate.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionUpdate.ObjectName}' was not found."));
                    return;
                }

                handler.ResetPositionTask();
                sceneObject.SetPosition(
                    positionUpdate.X,
                    positionUpdate.Y,
                    positionUpdate.Z,
                    positionUpdate.VectorType);

                if (!WaitForTaskWithMessagePump(handler.PositionTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPosition timed out for '{positionUpdate.ObjectName}'." ));
                    return;
                }

                var positionResult = handler.PositionTask.Result;
                if (positionResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", positionResult.ToString(), options.OutputPath, $"OnSetPosition result={positionResult} object={positionUpdate.ObjectName}"));
                    return;
                }
            }

            foreach (var positionKeyUpdate in options.PostPositionKeys)
            {
                Console.WriteLine(
                    $"[SAVE-IMAGE] Setting post-chart position key object={positionKeyUpdate.ObjectName} index={positionKeyUpdate.KeyIndex} " +
                    $"value=({positionKeyUpdate.X},{positionKeyUpdate.Y},{positionKeyUpdate.Z}) vector={positionKeyUpdate.VectorType}...");
                var sceneObject = scene.GetObject(positionKeyUpdate.ObjectName);
                if (sceneObject is null)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionKeyUpdate.ObjectName}' was not found."));
                    return;
                }

                handler.ResetPositionKeyTask();
                sceneObject.SetPositionKey(
                    positionKeyUpdate.KeyIndex,
                    positionKeyUpdate.X,
                    positionKeyUpdate.Y,
                    positionKeyUpdate.Z,
                    positionKeyUpdate.VectorType);

                if (!WaitForTaskWithMessagePump(handler.PositionKeyTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPositionKey timed out for '{positionKeyUpdate.ObjectName}'." ));
                    return;
                }

                var positionKeyResult = handler.PositionKeyTask.Result;
                if (positionKeyResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", positionKeyResult.ToString(), options.OutputPath, $"OnSetPositionKey result={positionKeyResult} object={positionKeyUpdate.ObjectName}"));
                    return;
                }
            }

            var captures = new List<(string OutputPath, int Frame)>();
            if (options.Frames.Count > 0)
            {
                var captureDirectory = options.OutputDirectory ?? options.OutputPath;
                foreach (var captureFrame in options.Frames)
                {
                    captures.Add((
                        Path.GetFullPath(Path.Combine(
                            captureDirectory,
                            string.Format(CultureInfo.InvariantCulture, options.OutputPattern, captureFrame))),
                        captureFrame));
                }
            }
            else
            {
                captures.Add((options.OutputPath, options.Frame));
            }

            long totalBytes = 0;
            foreach (var capture in captures)
            {
                var outputDirectory = Path.GetDirectoryName(capture.OutputPath);
                if (!string.IsNullOrWhiteSpace(outputDirectory))
                {
                    Directory.CreateDirectory(outputDirectory);
                }

                if (File.Exists(capture.OutputPath))
                {
                    File.Delete(capture.OutputPath);
                }

                Console.WriteLine($"[SAVE-IMAGE] Calling SaveSceneImage() frame={capture.Frame} output={capture.OutputPath}...");
                handler.ResetSaveSceneImageTask();
                scene.SaveSceneImage(capture.OutputPath, options.Width, options.Height, capture.Frame);

                if (!WaitForTaskWithMessagePump(handler.SaveSceneImageTask, options.Connection.Timeout))
                {
                    completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", capture.OutputPath, "OnSaveSceneImage timed out."));
                    return;
                }

                var saveResult = handler.SaveSceneImageTask.Result;
                if (saveResult != eKResult.RESULT_SUCCESS)
                {
                    completion.TrySetResult(
                        new SaveSceneImageProbeResult(true, "SUCCESS", saveResult.ToString(), capture.OutputPath, $"OnSaveSceneImage result={saveResult}"));
                    return;
                }

                var savedThisFrame = false;
                var fileWaitDeadline = DateTime.UtcNow + options.Connection.Timeout;
                while (DateTime.UtcNow < fileWaitDeadline)
                {
                    if (File.Exists(capture.OutputPath))
                    {
                        var info = new FileInfo(capture.OutputPath);
                        if (info.Length > 0)
                        {
                            totalBytes += info.Length;
                            savedThisFrame = true;
                            break;
                        }
                    }

                    Thread.Sleep(50);
                }

                if (!savedThisFrame)
                {
                    completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", capture.OutputPath, "Image file was not created."));
                    return;
                }
            }

            var resultOutput = options.Frames.Count > 0
                ? options.OutputDirectory ?? options.OutputPath
                : options.OutputPath;
            var detail = captures.Count == 1
                ? $"Saved {totalBytes} bytes."
                : $"Saved {captures.Count} frames ({totalBytes} bytes).";
            completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "SUCCESS", resultOutput, detail));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new SaveSceneImageProbeResult(false, "EXCEPTION", "EXCEPTION", options.OutputPath, ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaSaveSceneImageProbe"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<SceneCatalogProbeResult> CatalogScenesAsync(SceneCatalogOptions options)
{
    var completion = new TaskCompletionSource<SceneCatalogProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        var entries = new List<SceneCatalogEntry>();
        var failures = new List<SceneCatalogFailure>();

        try
        {
            var handler = new ProbeEventHandler();
            var engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[CATALOG] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[CATALOG] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(new SceneCatalogProbeResult(false, 0, 0, options.OutputPath, failures));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                failures.Add(new SceneCatalogFailure("(connect)", "OnConnect timed out."));
                completion.TrySetResult(new SceneCatalogProbeResult(true, 0, 0, options.OutputPath, failures));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                failures.Add(new SceneCatalogFailure("(connect)", $"OnConnect errorCode={handler.ConnectTask.Result}"));
                completion.TrySetResult(new SceneCatalogProbeResult(true, 0, 0, options.OutputPath, failures));
                return;
            }

            var scenePaths = Directory
                .EnumerateFiles(options.RootPath, "*.tscn", SearchOption.AllDirectories)
                .Where(path => string.IsNullOrWhiteSpace(options.SceneFilter) ||
                               path.Contains(options.SceneFilter, StringComparison.OrdinalIgnoreCase))
                .Select(path => new
                {
                    Path = path,
                    RelativePath = Path.GetRelativePath(options.RootPath, path)
                })
                .OrderBy(item => item.RelativePath.Count(character =>
                    character == Path.DirectorySeparatorChar ||
                    character == Path.AltDirectorySeparatorChar))
                .ThenBy(item => item.RelativePath, StringComparer.OrdinalIgnoreCase)
                .Select(item => item.Path)
                .Take(options.MaxScenes ?? int.MaxValue)
                .ToArray();

            for (var index = 0; index < scenePaths.Length; index++)
            {
                var scenePath = scenePaths[index];
                var sceneAlias = $"catalog_{index:D4}";
                Console.WriteLine($"[CATALOG] ({index + 1}/{scenePaths.Length}) {scenePath}");

                try
                {
                    handler.ResetLoadSceneTask();
                    handler.ResetObjectInfosTask();
                    var scene = engine.LoadScene(scenePath, sceneAlias);
                    if (scene is null)
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, "LoadScene returned null."));
                        continue;
                    }

                    handler.ConfigureQueryObjectInfosOnLoad(scene);

                    if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, "OnLoadScene timed out."));
                        continue;
                    }

                    if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                        TryUnloadScene(handler, scene, options.Connection.Timeout);
                        continue;
                    }

                    if (!WaitForTaskWithMessagePump(handler.ObjectInfosTask, options.Connection.Timeout))
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, "OnQueryObjectInfos timed out."));
                        TryUnloadScene(handler, scene, options.Connection.Timeout);
                        continue;
                    }

                    var objectInfosResult = handler.ObjectInfosTask.Result;
                    if (objectInfosResult.Result != eKResult.RESULT_SUCCESS)
                    {
                        var reason = string.IsNullOrWhiteSpace(objectInfosResult.Detail)
                            ? $"OnQueryObjectInfos result={objectInfosResult.Result}"
                            : $"OnQueryObjectInfos result={objectInfosResult.Result} detail={objectInfosResult.Detail}";
                        failures.Add(new SceneCatalogFailure(scenePath, reason));
                        TryUnloadScene(handler, scene, options.Connection.Timeout);
                        continue;
                    }

                    entries.Add(new SceneCatalogEntry(
                        scenePath,
                        Path.GetRelativePath(options.RootPath, scenePath),
                        objectInfosResult.Objects));

                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch (Exception ex)
                {
                    failures.Add(new SceneCatalogFailure(scenePath, ex.Message));
                }
            }

            WriteCatalogMarkdown(options, entries, failures);
            completion.TrySetResult(new SceneCatalogProbeResult(true, scenePaths.Length, entries.Count, options.OutputPath, failures));

            try
            {
                engine.Disconnect();
                handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
            }
            catch
            {
            }
        }
        catch (Exception ex)
        {
            failures.Add(new SceneCatalogFailure("(exception)", ex.ToString()));
            completion.TrySetResult(new SceneCatalogProbeResult(false, entries.Count + failures.Count, entries.Count, options.OutputPath, failures));
        }
    })
    {
        IsBackground = true,
        Name = "KarismaSceneCatalog"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<SceneObjectInspectionProbeResult> InspectSceneObjectsAsync(SceneObjectInspectionOptions options)
{
    var completion = new TaskCompletionSource<SceneObjectInspectionProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        IKAScene? scene = null;
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;

        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[INSPECT-OBJECTS] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[INSPECT-OBJECTS] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(false, "NOT_RUN", "NOT_RUN", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(true, "NOT_RUN", "NOT_RUN", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(true, "NOT_RUN", "NOT_RUN", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[INSPECT-OBJECTS] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(true, "FAILED", "NOT_RUN", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(true, "TIMEOUT", "NOT_RUN", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), "OnLoadScene timed out."));
                return;
            }

            if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(true, handler.LoadSceneTask.Result.ToString(), "NOT_RUN", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                return;
            }

            Console.WriteLine("[INSPECT-OBJECTS] Querying object infos...");
            handler.ResetObjectInfosTask();
            scene.QueryObjectInfos();
            if (!WaitForTaskWithMessagePump(handler.ObjectInfosTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new SceneObjectInspectionProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), "OnQueryObjectInfos timed out."));
                return;
            }

            var objectInfosResult = handler.ObjectInfosTask.Result;
            var objects = objectInfosResult.Objects
                .Where(item => string.IsNullOrWhiteSpace(options.NameFilter) ||
                               item.Name.Contains(options.NameFilter, StringComparison.OrdinalIgnoreCase))
                .OrderBy(item => item.Name, StringComparer.OrdinalIgnoreCase)
                .ToArray();
            WriteSceneObjectInspectionReport(options, objectInfosResult, objects);
            completion.TrySetResult(
                new SceneObjectInspectionProbeResult(
                    true,
                    "SUCCESS",
                    objectInfosResult.Result.ToString(),
                    options.OutputPath,
                    objects,
                    objectInfosResult.Detail));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(
                new SceneObjectInspectionProbeResult(false, "EXCEPTION", "EXCEPTION", options.OutputPath, Array.Empty<SceneObjectCatalogItem>(), ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaSceneObjectInspection"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<ObjectTypeQueryProbeResult> QueryObjectTypesAsync(ObjectTypeQueryOptions options)
{
    var completion = new TaskCompletionSource<ObjectTypeQueryProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        IKAScene? scene = null;
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;

        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[QUERY-OBJECT-TYPES] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[QUERY-OBJECT-TYPES] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(
                    new ObjectTypeQueryProbeResult(false, "NOT_RUN", options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ObjectTypeQueryProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new ObjectTypeQueryProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[QUERY-OBJECT-TYPES] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(
                    new ObjectTypeQueryProbeResult(true, "FAILED", options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ObjectTypeQueryProbeResult(true, "TIMEOUT", options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), "OnLoadScene timed out."));
                return;
            }

            if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new ObjectTypeQueryProbeResult(true, handler.LoadSceneTask.Result.ToString(), options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                return;
            }

            var items = new List<ObjectTypeQueryItem>(options.ObjectNames.Count);
            foreach (var objectName in options.ObjectNames)
            {
                Console.WriteLine($"[QUERY-OBJECT-TYPES] Querying object={objectName}...");
                var sceneObject = scene.GetObject(objectName);
                if (sceneObject is null)
                {
                    items.Add(new ObjectTypeQueryItem(objectName, "OBJECT_NOT_FOUND", eKObjectType.OBJECT_TYPE_UNKNOWN, "GetObject returned null."));
                    continue;
                }

                handler.ResetObjectTypeTask();
                sceneObject.QueryObjectType();
                if (!WaitForTaskWithMessagePump(handler.ObjectTypeTask, options.Connection.Timeout))
                {
                    items.Add(new ObjectTypeQueryItem(objectName, "TIMEOUT", eKObjectType.OBJECT_TYPE_UNKNOWN, "OnQueryObjectType timed out."));
                    continue;
                }

                var objectTypeResult = handler.ObjectTypeTask.Result;
                items.Add(new ObjectTypeQueryItem(
                    objectName,
                    objectTypeResult.Result.ToString(),
                    objectTypeResult.ObjectType,
                    objectTypeResult.Detail));
            }

            WriteObjectTypeQueryReport(options, items);
            completion.TrySetResult(new ObjectTypeQueryProbeResult(true, "SUCCESS", options.OutputPath, items, string.Empty));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new ObjectTypeQueryProbeResult(false, "EXCEPTION", options.OutputPath, Array.Empty<ObjectTypeQueryItem>(), ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaObjectTypeQuery"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<ObjectRuntimeInspectionProbeResult> InspectObjectRuntimeAsync(ObjectTypeQueryOptions options)
{
    var completion = new TaskCompletionSource<ObjectRuntimeInspectionProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        IKAScene? scene = null;
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;

        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[INSPECT-OBJECT-RUNTIME] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[INSPECT-OBJECT-RUNTIME] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(
                    new ObjectRuntimeInspectionProbeResult(false, "NOT_RUN", options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ObjectRuntimeInspectionProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new ObjectRuntimeInspectionProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[INSPECT-OBJECT-RUNTIME] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(
                    new ObjectRuntimeInspectionProbeResult(true, "FAILED", options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ObjectRuntimeInspectionProbeResult(true, "TIMEOUT", options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), "OnLoadScene timed out."));
                return;
            }

            if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new ObjectRuntimeInspectionProbeResult(true, handler.LoadSceneTask.Result.ToString(), options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                return;
            }

            var items = new List<ObjectRuntimeInspectionItem>(options.ObjectNames.Count);
            foreach (var objectName in options.ObjectNames)
            {
                Console.WriteLine($"[INSPECT-OBJECT-RUNTIME] Inspecting object={objectName}...");
                var sceneObject = scene.GetObject(objectName);
                if (sceneObject is null)
                {
                    items.Add(new ObjectRuntimeInspectionItem(
                        objectName,
                        "OBJECT_NOT_FOUND",
                        eKObjectType.OBJECT_TYPE_UNKNOWN,
                        string.Empty,
                        "OBJECT_NOT_FOUND",
                        null,
                        null,
                        null,
                        "GetObject returned null."));
                    continue;
                }

                var detailParts = new List<string>();
                string animationName;
                try
                {
                    animationName = sceneObject.GetAnimationName() ?? string.Empty;
                }
                catch (Exception ex)
                {
                    animationName = string.Empty;
                    detailParts.Add($"GetAnimationName: {ex.Message}");
                }

                handler.ResetObjectTypeTask();
                sceneObject.QueryObjectType();
                string typeResultText;
                var objectType = eKObjectType.OBJECT_TYPE_UNKNOWN;
                if (!WaitForTaskWithMessagePump(handler.ObjectTypeTask, options.Connection.Timeout))
                {
                    typeResultText = "TIMEOUT";
                    detailParts.Add("OnQueryObjectType timed out.");
                }
                else
                {
                    var objectTypeResult = handler.ObjectTypeTask.Result;
                    typeResultText = objectTypeResult.Result.ToString();
                    objectType = objectTypeResult.ObjectType;
                    if (!string.IsNullOrWhiteSpace(objectTypeResult.Detail))
                    {
                        detailParts.Add($"QueryObjectType: {objectTypeResult.Detail}");
                    }
                }

                handler.ResetQueryPositionTask();
                sceneObject.QueryPosition();
                string positionResultText;
                float? x = null;
                float? y = null;
                float? z = null;
                if (!WaitForTaskWithMessagePump(handler.QueryPositionTask, options.Connection.Timeout))
                {
                    positionResultText = "TIMEOUT";
                    detailParts.Add("OnQueryPosition timed out.");
                }
                else
                {
                    var positionResult = handler.QueryPositionTask.Result;
                    positionResultText = positionResult.Result.ToString();
                    if (positionResult.Result == eKResult.RESULT_SUCCESS)
                    {
                        x = positionResult.X;
                        y = positionResult.Y;
                        z = positionResult.Z;
                    }

                    if (!string.IsNullOrWhiteSpace(positionResult.Detail))
                    {
                        detailParts.Add($"QueryPosition: {positionResult.Detail}");
                    }
                }

                items.Add(new ObjectRuntimeInspectionItem(
                    objectName,
                    typeResultText,
                    objectType,
                    animationName,
                    positionResultText,
                    x,
                    y,
                    z,
                    string.Join("; ", detailParts.Where(part => !string.IsNullOrWhiteSpace(part)))));
            }

            WriteObjectRuntimeInspectionReport(options, items);
            completion.TrySetResult(new ObjectRuntimeInspectionProbeResult(true, "SUCCESS", options.OutputPath, items, string.Empty));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new ObjectRuntimeInspectionProbeResult(false, "EXCEPTION", options.OutputPath, Array.Empty<ObjectRuntimeInspectionItem>(), ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaObjectRuntimeInspection"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<ScreenPointInspectionProbeResult> InspectScreenPointsAsync(ScreenPointInspectionOptions options)
{
    var completion = new TaskCompletionSource<ScreenPointInspectionProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        IKAScene? scene = null;
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;

        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[QUERY-SCREEN-POINT] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[QUERY-SCREEN-POINT] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(
                    new ScreenPointInspectionProbeResult(false, "NOT_RUN", options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ScreenPointInspectionProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new ScreenPointInspectionProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[QUERY-SCREEN-POINT] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(
                    new ScreenPointInspectionProbeResult(true, "FAILED", options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new ScreenPointInspectionProbeResult(true, "TIMEOUT", options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), "OnLoadScene timed out."));
                return;
            }

            if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new ScreenPointInspectionProbeResult(true, handler.LoadSceneTask.Result.ToString(), options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                return;
            }

            var items = new List<ScreenPointInspectionItem>(options.Points.Count);
            foreach (var point in options.Points)
            {
                Console.WriteLine($"[QUERY-SCREEN-POINT] Querying point=({point.X},{point.Y})...");
                handler.ResetObjectInfosTask();
                scene.QueryObjectInfosByScreenPoint(point.X, point.Y);
                if (!WaitForTaskWithMessagePump(handler.ObjectInfosTask, options.Connection.Timeout))
                {
                    items.Add(new ScreenPointInspectionItem(point.X, point.Y, "TIMEOUT", Array.Empty<SceneObjectCatalogItem>(), "OnQueryObjectInfosByScreenPoint timed out."));
                    continue;
                }

                var objectInfosResult = handler.ObjectInfosTask.Result;
                items.Add(new ScreenPointInspectionItem(
                    point.X,
                    point.Y,
                    objectInfosResult.Result.ToString(),
                    objectInfosResult.Objects,
                    objectInfosResult.Detail));
            }

            WriteScreenPointInspectionReport(options, items);
            completion.TrySetResult(new ScreenPointInspectionProbeResult(true, "SUCCESS", options.OutputPath, items, string.Empty));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new ScreenPointInspectionProbeResult(false, "EXCEPTION", options.OutputPath, Array.Empty<ScreenPointInspectionItem>(), ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaScreenPointInspection"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<SceneCapabilityInspectionProbeResult> InspectSceneCapabilitiesAsync(SceneCapabilityInspectionOptions options)
{
    var completion = new TaskCompletionSource<SceneCapabilityInspectionProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        IKAScene? scene = null;
        ProbeEventHandler? handler = null;
        IKAEngine? engine = null;

        try
        {
            handler = new ProbeEventHandler();
            engine = (IKAEngine)new KAEngineClass();

            var samplePngPath = FindFirstFile(Environment.CurrentDirectory, "*.png");
            Console.WriteLine($"[INSPECT-CAPABILITIES] Sample PNG={(samplePngPath ?? "(none)")}");

            Console.WriteLine("[INSPECT-CAPABILITIES] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[INSPECT-CAPABILITIES] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(
                    new SceneCapabilityInspectionProbeResult(false, "NOT_RUN", options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new SceneCapabilityInspectionProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(
                    new SceneCapabilityInspectionProbeResult(true, "NOT_RUN", options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            Console.WriteLine("[INSPECT-CAPABILITIES] Loading scene...");
            handler.ResetLoadSceneTask();
            scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(
                    new SceneCapabilityInspectionProbeResult(true, "FAILED", options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(
                    new SceneCapabilityInspectionProbeResult(true, "TIMEOUT", options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), "OnLoadScene timed out."));
                return;
            }

            if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new SceneCapabilityInspectionProbeResult(true, handler.LoadSceneTask.Result.ToString(), options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                return;
            }

            var candidates = ExtractCandidateNames(options.ScenePath)
                .Where(candidate => options.Keywords.Count == 0 ||
                                    options.Keywords.Any(keyword => candidate.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
                .OrderBy(candidate => candidate, StringComparer.OrdinalIgnoreCase)
                .Take(options.MaxCandidates ?? int.MaxValue)
                .ToArray();

            var items = new List<SceneCapabilityInspectionItem>(candidates.Length);
            foreach (var candidate in candidates)
            {
                Console.WriteLine($"[INSPECT-CAPABILITIES] Inspecting candidate={candidate}...");
                KAObject? sceneObject;
                try
                {
                    sceneObject = scene.GetObject(candidate);
                }
                catch (Exception ex)
                {
                    items.Add(new SceneCapabilityInspectionItem(
                        candidate,
                        false,
                        string.Empty,
                        false,
                        false,
                        false,
                        "GET_OBJECT_EXCEPTION",
                        eKObjectType.OBJECT_TYPE_UNKNOWN,
                        string.Empty,
                        null,
                        null,
                        null,
                        string.Empty,
                        string.Empty,
                        string.Empty,
                        string.Empty,
                        ex.Message));
                    continue;
                }

                if (sceneObject is null)
                {
                    items.Add(new SceneCapabilityInspectionItem(
                        candidate,
                        false,
                        string.Empty,
                        false,
                        false,
                        false,
                        "OBJECT_NOT_FOUND",
                        eKObjectType.OBJECT_TYPE_UNKNOWN,
                        string.Empty,
                        null,
                        null,
                        null,
                        string.Empty,
                        string.Empty,
                        string.Empty,
                        string.Empty,
                        "GetObject returned null."));
                    continue;
                }

                var detailParts = new List<string>();
                string animationName;
                try
                {
                    animationName = sceneObject.GetAnimationName() ?? string.Empty;
                }
                catch (Exception ex)
                {
                    animationName = string.Empty;
                    detailParts.Add($"GetAnimationName: {ex.Message}");
                }

                handler.ResetObjectTypeTask();
                sceneObject.QueryObjectType();
                string queryTypeResult;
                var objectType = eKObjectType.OBJECT_TYPE_UNKNOWN;
                if (!WaitForTaskWithMessagePump(handler.ObjectTypeTask, options.Connection.Timeout))
                {
                    queryTypeResult = "TIMEOUT";
                    detailParts.Add("OnQueryObjectType timed out.");
                }
                else
                {
                    var callbackResult = handler.ObjectTypeTask.Result;
                    queryTypeResult = callbackResult.Result.ToString();
                    objectType = callbackResult.ObjectType;
                }

                var isChart = objectType == eKObjectType.OBJECT_TYPE_CHART;
                var isCounter = objectType == eKObjectType.OBJECT_TYPE_COUNTER;
                var isPath = objectType is eKObjectType.OBJECT_TYPE_PATH or eKObjectType.OBJECT_TYPE_PATHSHAPE;

                handler.ResetQueryPositionTask();
                sceneObject.QueryPosition();
                string queryPositionResult;
                float? x = null;
                float? y = null;
                float? z = null;
                if (!WaitForTaskWithMessagePump(handler.QueryPositionTask, options.Connection.Timeout))
                {
                    queryPositionResult = "TIMEOUT";
                    detailParts.Add("OnQueryPosition timed out.");
                }
                else
                {
                    var callbackResult = handler.QueryPositionTask.Result;
                    queryPositionResult = callbackResult.Result.ToString();
                    if (callbackResult.Result == eKResult.RESULT_SUCCESS)
                    {
                        x = callbackResult.X;
                        y = callbackResult.Y;
                        z = callbackResult.Z;
                    }
                }

                var setPositionResult = string.Empty;
                if (x.HasValue && y.HasValue && z.HasValue)
                {
                    handler.ResetPositionTask();
                    sceneObject.SetPosition(x.Value, y.Value, z.Value, eKVectorType.VECTOR_TYPE_XYZ);
                    if (!WaitForTaskWithMessagePump(handler.PositionTask, options.Connection.Timeout))
                    {
                        setPositionResult = "TIMEOUT";
                        detailParts.Add("OnSetPosition timed out.");
                    }
                    else
                    {
                        setPositionResult = handler.PositionTask.Result.ToString();
                    }
                }

                var setValueTextResult = TrySetValueOnObject(handler, sceneObject, "__TCP_VALIDATE__", options.Connection.Timeout).ToString();
                var setValueImageResult = string.Empty;
                if (!string.IsNullOrWhiteSpace(samplePngPath))
                {
                    setValueImageResult = TrySetValueOnObject(handler, sceneObject, samplePngPath, options.Connection.Timeout).ToString();
                }

                var counterKeyResult = string.Empty;
                if (isCounter && sceneObject is IKACounter counter)
                {
                    handler.ResetCounterNumberKeyTask();
                    counter.SetCounterNumberKey(1, 1d);
                    if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, options.Connection.Timeout))
                    {
                        counterKeyResult = "TIMEOUT";
                        detailParts.Add("OnSetCounterNumberKey timed out.");
                    }
                    else
                    {
                        counterKeyResult = handler.CounterNumberKeyTask.Result.ToString();
                    }
                }

                items.Add(new SceneCapabilityInspectionItem(
                    candidate,
                    true,
                    animationName,
                    isChart,
                    isCounter,
                    isPath,
                    queryTypeResult,
                    objectType,
                    queryPositionResult,
                    x,
                    y,
                    z,
                    setPositionResult,
                    setValueTextResult,
                    setValueImageResult,
                    counterKeyResult,
                    string.Join("; ", detailParts.Where(part => !string.IsNullOrWhiteSpace(part)))));
            }

            WriteSceneCapabilityInspectionReport(options, items);
            completion.TrySetResult(new SceneCapabilityInspectionProbeResult(true, "SUCCESS", options.OutputPath, items, string.Empty));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new SceneCapabilityInspectionProbeResult(false, "EXCEPTION", options.OutputPath, Array.Empty<SceneCapabilityInspectionItem>(), ex.ToString()));
        }
        finally
        {
            if (scene is not null && handler is not null)
            {
                try
                {
                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch
                {
                }
            }

            if (engine is not null && handler is not null)
            {
                try
                {
                    engine.Disconnect();
                    handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
                }
                catch
                {
                }
            }
        }
    })
    {
        IsBackground = true,
        Name = "KarismaSceneCapabilityInspection"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<SceneValidationProbeResult> ValidateSceneOperationsAsync(SceneValidationOptions options)
{
    var completion = new TaskCompletionSource<SceneValidationProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);
    var operations = LoadValidationOperations(options);

    var thread = new Thread(() =>
    {
        try
        {
            var handler = new ProbeEventHandler();
            var engine = (IKAEngine)new KAEngineClass();
            var results = new List<SceneOperationValidationResult>();

            Console.WriteLine("[VALIDATE] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[VALIDATE] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(new SceneValidationProbeResult(false, "NOT_RUN", 0, operations.Count, options.OutputPath, "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new SceneValidationProbeResult(true, "NOT_RUN", 0, operations.Count, options.OutputPath, "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(new SceneValidationProbeResult(true, "NOT_RUN", 0, operations.Count, options.OutputPath, $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            handler.ResetLoadSceneTask();
            var scene = engine.LoadScene(options.ScenePath, options.SceneAlias);
            if (scene is null)
            {
                completion.TrySetResult(new SceneValidationProbeResult(true, "FAILED", 0, operations.Count, options.OutputPath, "LoadScene returned null."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new SceneValidationProbeResult(true, "TIMEOUT", 0, operations.Count, options.OutputPath, "OnLoadScene timed out."));
                return;
            }

            var loadSceneResult = handler.LoadSceneTask.Result;
            if (loadSceneResult != eKResult.RESULT_SUCCESS)
            {
                completion.TrySetResult(
                    new SceneValidationProbeResult(true, loadSceneResult.ToString(), 0, operations.Count, options.OutputPath, $"OnLoadScene result={loadSceneResult}"));
                return;
            }

            foreach (var operation in operations)
            {
                results.Add(ApplySceneOperation(handler, scene, operation, options.Connection.Timeout));
            }

            WriteSceneValidationMarkdown(options, results);

            try
            {
                scene.UnloadScene();
            }
            catch
            {
            }

            try
            {
                engine.Disconnect();
                handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
            }
            catch
            {
            }

            completion.TrySetResult(new SceneValidationProbeResult(
                true,
                "SUCCESS",
                results.Count(result => string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal)),
                results.Count(result => !string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal)),
                options.OutputPath,
                string.Empty));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new SceneValidationProbeResult(false, "EXCEPTION", 0, operations.Count, options.OutputPath, ex.ToString()));
        }
    })
    {
        IsBackground = true,
        Name = "KarismaSceneValidator"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static Task<FolderInspectionProbeResult> InspectTscnFolderAsync(FolderInspectionOptions options)
{
    var completion = new TaskCompletionSource<FolderInspectionProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        try
        {
            var handler = new ProbeEventHandler();
            var engine = (IKAEngine)new KAEngineClass();
            var sceneResults = new List<SceneInspectionResult>();
            var failures = new List<SceneCatalogFailure>();

            var imagePngPath = FindFirstFile(options.RootPath, "*.png");
            var imageVrvPath = FindFirstFile(options.RootPath, "*.vrv");
            Console.WriteLine($"[INSPECT] Sample PNG={(imagePngPath ?? "(none)")}");
            Console.WriteLine($"[INSPECT] Sample VRV={(imageVrvPath ?? "(none)")}");

            Console.WriteLine("[INSPECT] Calling Connect()...");
            var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler);
            Console.WriteLine($"[INSPECT] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");
            if (connectRequested == 0)
            {
                completion.TrySetResult(new FolderInspectionProbeResult(false, 0, 0, 0, 1, options.OutputPath, "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout))
            {
                completion.TrySetResult(new FolderInspectionProbeResult(true, 0, 0, 0, 1, options.OutputPath, "OnConnect timed out."));
                return;
            }

            if (handler.ConnectTask.Result != 0)
            {
                completion.TrySetResult(new FolderInspectionProbeResult(true, 0, 0, 0, 1, options.OutputPath, $"OnConnect errorCode={handler.ConnectTask.Result}"));
                return;
            }

            var scenePaths = Directory
                .EnumerateFiles(options.RootPath, "*.tscn", SearchOption.AllDirectories)
                .Where(path => string.IsNullOrWhiteSpace(options.SceneFilter) ||
                               path.Contains(options.SceneFilter, StringComparison.OrdinalIgnoreCase))
                .Select(path => new
                {
                    Path = path,
                    RelativePath = Path.GetRelativePath(options.RootPath, path)
                })
                .OrderBy(item => item.RelativePath.Count(character =>
                    character == Path.DirectorySeparatorChar ||
                    character == Path.AltDirectorySeparatorChar))
                .ThenBy(item => item.RelativePath, StringComparer.OrdinalIgnoreCase)
                .Select(item => item.Path)
                .Take(options.MaxScenes ?? int.MaxValue)
                .ToArray();

            for (var sceneIndex = 0; sceneIndex < scenePaths.Length; sceneIndex++)
            {
                var scenePath = scenePaths[sceneIndex];
                var sceneAlias = $"inspect_{sceneIndex:D4}";
                Console.WriteLine($"[INSPECT] ({sceneIndex + 1}/{scenePaths.Length}) {scenePath}");

                try
                {
                    handler.ResetLoadSceneTask();
                    var scene = engine.LoadScene(scenePath, sceneAlias);
                    if (scene is null)
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, "LoadScene returned null."));
                        continue;
                    }

                    if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout))
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, "OnLoadScene timed out."));
                        TryUnloadScene(handler, scene, options.Connection.Timeout);
                        continue;
                    }

                    if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS)
                    {
                        failures.Add(new SceneCatalogFailure(scenePath, $"OnLoadScene result={handler.LoadSceneTask.Result}"));
                        TryUnloadScene(handler, scene, options.Connection.Timeout);
                        continue;
                    }

                    var candidates = ExtractCandidateNames(scenePath);
                    var discoveries = new List<SceneVariableDiscovery>();

                    foreach (var candidate in candidates)
                    {
                        KAObject? sceneObject;
                        try
                        {
                            sceneObject = scene.GetObject(candidate);
                        }
                        catch
                        {
                            continue;
                        }

                        if (sceneObject is null)
                        {
                            continue;
                        }

                        var textAttempt = TrySetValueOnObject(handler, sceneObject, "__TCP_VALIDATE__", options.Connection.Timeout);
                        if (textAttempt == eKResult.RESULT_SUCCESS)
                        {
                            discoveries.Add(new SceneVariableDiscovery(candidate, "SetValue", "__TCP_VALIDATE__", textAttempt.ToString()));
                            continue;
                        }

                        if (textAttempt == eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT)
                        {
                            continue;
                        }

                        if (!string.IsNullOrWhiteSpace(imagePngPath))
                        {
                            var pngAttempt = TrySetValueOnObject(handler, sceneObject, imagePngPath, options.Connection.Timeout);
                            if (pngAttempt == eKResult.RESULT_SUCCESS)
                            {
                                discoveries.Add(new SceneVariableDiscovery(candidate, "SetValue", imagePngPath, pngAttempt.ToString()));
                                continue;
                            }
                        }

                        if (!string.IsNullOrWhiteSpace(imageVrvPath))
                        {
                            var vrvAttempt = TrySetValueOnObject(handler, sceneObject, imageVrvPath, options.Connection.Timeout);
                            if (vrvAttempt == eKResult.RESULT_SUCCESS)
                            {
                                discoveries.Add(new SceneVariableDiscovery(candidate, "SetValue", imageVrvPath, vrvAttempt.ToString()));
                                continue;
                            }
                        }

                        if (sceneObject is IKACounter counter)
                        {
                            handler.ResetCounterNumberKeyTask();
                            counter.SetCounterNumberKey(1, 1d);
                            if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, options.Connection.Timeout))
                            {
                                continue;
                            }

                            var counterResult = handler.CounterNumberKeyTask.Result;
                            if (counterResult == eKResult.RESULT_SUCCESS)
                            {
                                discoveries.Add(new SceneVariableDiscovery(
                                    candidate,
                                    "SetCounterNumberKey",
                                    "keyIndex=1, number=1",
                                    counterResult.ToString()));
                            }
                        }
                    }

                    sceneResults.Add(new SceneInspectionResult(
                        scenePath,
                        Path.GetRelativePath(options.RootPath, scenePath),
                        candidates.Count,
                        discoveries.OrderBy(discovery => discovery.VariableName, StringComparer.Ordinal).ToArray()));

                    TryUnloadScene(handler, scene, options.Connection.Timeout);
                }
                catch (Exception ex)
                {
                    failures.Add(new SceneCatalogFailure(scenePath, ex.Message));
                }
            }

            WriteFolderInspectionMarkdown(options, sceneResults, failures);

            try
            {
                engine.Disconnect();
                handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
            }
            catch
            {
            }

            completion.TrySetResult(new FolderInspectionProbeResult(
                true,
                scenePaths.Length,
                sceneResults.Count,
                sceneResults.Sum(result => result.Discoveries.Count),
                failures.Count,
                options.OutputPath,
                string.Empty));
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new FolderInspectionProbeResult(false, 0, 0, 0, 1, options.OutputPath, ex.ToString()));
        }
    })
    {
        IsBackground = true,
        Name = "KarismaTscnInspector"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static eKResult TrySetValueOnObject(ProbeEventHandler handler, KAObject sceneObject, string payload, TimeSpan timeout)
{
    handler.ResetSetValueTask();
    sceneObject.SetValue(payload);
    return WaitForTaskWithMessagePump(handler.SetValueTask, timeout)
        ? handler.SetValueTask.Result
        : eKResult.RESULT_FAILURE;
}

static string? FindFirstFile(string rootPath, string pattern)
{
    try
    {
        return Directory.EnumerateFiles(rootPath, pattern, SearchOption.AllDirectories)
            .OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
            .FirstOrDefault();
    }
    catch
    {
        return null;
    }
}

static IReadOnlyList<string> ExtractCandidateNames(string scenePath)
{
    var data = File.ReadAllBytes(scenePath);
    var candidates = new HashSet<string>(StringComparer.Ordinal);
    for (var index = 0; index + 8 <= data.Length; index++)
    {
        var length = BitConverter.ToInt32(data, index);
        if (length < 2 || length > 48)
        {
            continue;
        }

        var byteLength = length * 2;
        var start = index + 4;
        var end = start + byteLength;
        if (end > data.Length)
        {
            continue;
        }

        string value;
        try
        {
            value = System.Text.Encoding.Unicode.GetString(data, start, byteLength);
        }
        catch
        {
            continue;
        }

        if (IsCandidateName(value))
        {
            candidates.Add(value);
        }
    }

    return candidates.OrderBy(value => value, StringComparer.Ordinal).ToArray();
}

static bool IsCandidateName(string value)
{
    if (string.IsNullOrWhiteSpace(value))
    {
        return false;
    }

    var trimmed = value.Trim();
    if (trimmed.Length < 2 || trimmed.Length > 48)
    {
        return false;
    }

    if (trimmed.Contains('\\') ||
        trimmed.Contains('/') ||
        trimmed.Contains(':') ||
        trimmed.Contains('.') ||
        trimmed.Contains(' '))
    {
        return false;
    }

    if (trimmed.All(char.IsDigit))
    {
        return false;
    }

    if (trimmed.StartsWith("OBJECT_TYPE_", StringComparison.Ordinal) ||
        trimmed.StartsWith("RESULT_", StringComparison.Ordinal) ||
        trimmed.StartsWith("EFFECT_", StringComparison.Ordinal))
    {
        return false;
    }

    if (trimmed is "Tornado3" or "Out" or "Default" or "Image" or "Text" or "Counter" or
        "Rect" or "Paragraph" or "Char" or "Arial" or "Path" or "Group" or "Change")
    {
        return false;
    }

    return trimmed.All(ch =>
        ch is >= '0' and <= '9' ||
        ch is >= 'A' and <= 'Z' ||
        ch is >= 'a' and <= 'z' ||
        ch is >= '\uAC00' and <= '\uD7A3' ||
        ch is '_' or '-');
}

static void WriteFolderInspectionMarkdown(
    FolderInspectionOptions options,
    IReadOnlyList<SceneInspectionResult> sceneResults,
    IReadOnlyList<SceneCatalogFailure> failures)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# TSCN Variable Discovery");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Root: `{options.RootPath}`");
    writer.WriteLine($"- Scene Count: {sceneResults.Count}");
    writer.WriteLine($"- Discovered Variable Count: {sceneResults.Sum(result => result.Discoveries.Count)}");
    writer.WriteLine($"- Failure Count: {failures.Count}");
    writer.WriteLine();
    writer.WriteLine("## Method");
    writer.WriteLine();
    writer.WriteLine("- Candidate names are extracted from each `.tscn` as UTF-16LE strings.");
    writer.WriteLine("- Each candidate is verified through Karisma TCP callbacks.");
    writer.WriteLine("- `SetValue(__TCP_VALIDATE__)`, valid `.png`, valid `.vrv`, and `SetCounterNumberKey(1, 1)` are tried as applicable.");
    writer.WriteLine("- Only callbacks that returned `RESULT_SUCCESS` are listed as discovered variables.");
    writer.WriteLine();

    if (failures.Count > 0)
    {
        writer.WriteLine("## Failures");
        writer.WriteLine();
        foreach (var failure in failures)
        {
            writer.WriteLine($"- `{failure.ScenePath}`: {EscapeInline(failure.Reason)}");
        }

        writer.WriteLine();
    }

    writer.WriteLine("## Scenes");
    writer.WriteLine();
    foreach (var sceneResult in sceneResults)
    {
        writer.WriteLine($"### `{sceneResult.RelativePath}`");
        writer.WriteLine();
        writer.WriteLine($"- Candidate Count: {sceneResult.CandidateCount}");
        writer.WriteLine($"- Discovered Variables: {sceneResult.Discoveries.Count}");
        writer.WriteLine();

        if (sceneResult.Discoveries.Count == 0)
        {
            writer.WriteLine("- No variables discovered with the current TCP validation heuristics.");
            writer.WriteLine();
            continue;
        }

        writer.WriteLine("| Variable | Method | Payload | Result |");
        writer.WriteLine("| --- | --- | --- | --- |");
        foreach (var discovery in sceneResult.Discoveries)
        {
            writer.WriteLine(
                $"| {EscapeCell(discovery.VariableName)} | {EscapeCell(discovery.Method)} | {EscapeCell(discovery.Payload)} | {EscapeCell(discovery.Result)} |");
        }

        writer.WriteLine();
    }
}

static List<SceneValidationOperation> LoadValidationOperations(SceneValidationOptions options)
{
    return LoadSceneOperations(options.ScenePath, options.OperationsPath);
}

static List<SceneValidationOperation> LoadSceneOperations(string scenePath, string operationsPath)
{
    var json = File.ReadAllText(operationsPath);
    var operations = JsonSerializer.Deserialize<List<SceneValidationOperation>>(json, new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true
    }) ?? new List<SceneValidationOperation>();

    foreach (var operation in operations)
    {
        if (!string.IsNullOrWhiteSpace(operation.Value))
        {
            operation.Value = operation.Value.Replace("${SCENE_DIR}", Path.GetDirectoryName(scenePath) ?? string.Empty, StringComparison.Ordinal);
        }
    }

    return operations;
}

static SceneOperationValidationResult ApplySceneOperation(
    ProbeEventHandler handler,
    IKAScene scene,
    SceneValidationOperation operation,
    TimeSpan timeout)
{
    Console.WriteLine($"[VALIDATE] {operation.Method} object={operation.ObjectName}");
    var sceneObject = scene.GetObject(operation.ObjectName);
    if (sceneObject is null)
    {
        return new SceneOperationValidationResult(
            operation.ObjectName,
            operation.Method,
            DescribeOperationPayload(operation),
            "OBJECT_NOT_FOUND",
            "scene.GetObject returned null.");
    }

    if (string.Equals(operation.Method, "SetCounterNumberKey", StringComparison.OrdinalIgnoreCase))
    {
        if (sceneObject is not IKACounter counter)
        {
            return new SceneOperationValidationResult(
                operation.ObjectName,
                operation.Method,
                DescribeOperationPayload(operation),
                "NOT_A_COUNTER",
                "Object does not implement IKACounter.");
        }

        handler.ResetCounterNumberKeyTask();
        counter.SetCounterNumberKey(operation.KeyIndex, operation.Number);
        if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, timeout))
        {
            return new SceneOperationValidationResult(
                operation.ObjectName,
                operation.Method,
                DescribeOperationPayload(operation),
                "TIMEOUT",
                "OnSetCounterNumberKey timed out.");
        }

        return new SceneOperationValidationResult(
            operation.ObjectName,
            operation.Method,
            DescribeOperationPayload(operation),
            handler.CounterNumberKeyTask.Result.ToString(),
            string.Empty);
    }

    if (string.Equals(operation.Method, "SetVisible", StringComparison.OrdinalIgnoreCase))
    {
        handler.ResetVisibleTask();
        sceneObject.SetVisible(operation.Visible ? 1 : 0);
        if (!WaitForTaskWithMessagePump(handler.VisibleTask, timeout))
        {
            return new SceneOperationValidationResult(
                operation.ObjectName,
                operation.Method,
                DescribeOperationPayload(operation),
                "TIMEOUT",
                "OnSetVisible timed out.");
        }

        return new SceneOperationValidationResult(
            operation.ObjectName,
            operation.Method,
            DescribeOperationPayload(operation),
            handler.VisibleTask.Result.ToString(),
            string.Empty);
    }

    if (string.Equals(operation.Method, "SetStyleColor", StringComparison.OrdinalIgnoreCase))
    {
        if (sceneObject is not IKAStyle style)
        {
            return new SceneOperationValidationResult(
                operation.ObjectName,
                operation.Method,
                DescribeOperationPayload(operation),
                "NOT_A_STYLE_OBJECT",
                "Object does not implement IKAStyle.");
        }

        handler.ResetStyleColorTask();
        style.SetStyleColor(
            ParseStyleType(operation.StyleType),
            operation.Order,
            operation.R,
            operation.G,
            operation.B,
            operation.A);
        if (!WaitForTaskWithMessagePump(handler.StyleColorTask, timeout))
        {
            return new SceneOperationValidationResult(
                operation.ObjectName,
                operation.Method,
                DescribeOperationPayload(operation),
                "TIMEOUT",
                "OnSetStyleColor timed out.");
        }

        return new SceneOperationValidationResult(
            operation.ObjectName,
            operation.Method,
            DescribeOperationPayload(operation),
            handler.StyleColorTask.Result.ToString(),
            string.Empty);
    }

    handler.ResetSetValueTask();
    sceneObject.SetValue(operation.Value ?? string.Empty);
    if (!WaitForTaskWithMessagePump(handler.SetValueTask, timeout))
    {
        return new SceneOperationValidationResult(
            operation.ObjectName,
            operation.Method,
            DescribeOperationPayload(operation),
            "TIMEOUT",
            "OnSetValue timed out.");
    }

    return new SceneOperationValidationResult(
        operation.ObjectName,
        operation.Method,
        DescribeOperationPayload(operation),
        handler.SetValueTask.Result.ToString(),
        string.Empty);
}

static string DescribeOperationPayload(SceneValidationOperation operation)
{
    if (string.Equals(operation.Method, "SetVisible", StringComparison.OrdinalIgnoreCase))
    {
        return $"visible={operation.Visible}";
    }

    if (string.Equals(operation.Method, "SetStyleColor", StringComparison.OrdinalIgnoreCase))
    {
        return $"styleType={operation.StyleType}, order={operation.Order}, rgba=({operation.R}, {operation.G}, {operation.B}, {operation.A})";
    }

    return string.Equals(operation.Method, "SetCounterNumberKey", StringComparison.OrdinalIgnoreCase)
        ? $"keyIndex={operation.KeyIndex}, number={operation.Number:0.###}"
        : operation.Value ?? string.Empty;
}

static eKStyleType ParseStyleType(string? value)
{
    return (value ?? string.Empty).Trim().ToLowerInvariant() switch
    {
        "face" => eKStyleType.STYLE_TYPE_FACE,
        "edge" => eKStyleType.STYLE_TYPE_EDGE,
        "shadow" => eKStyleType.STYLE_TYPE_SHADOW,
        "underline" => eKStyleType.STYLE_TYPE_UNDERLINE,
        "frame" => eKStyleType.STYLE_TYPE_FRAME,
        _ => throw new ArgumentException($"Unsupported style type: {value}")
    };
}

static void WriteSceneValidationMarkdown(SceneValidationOptions options, IReadOnlyList<SceneOperationValidationResult> results)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Scene Variable Validation");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Scene: `{options.ScenePath}`");
    writer.WriteLine($"- Operations: `{options.OperationsPath}`");
    writer.WriteLine($"- Success Count: {results.Count(result => string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))}");
    writer.WriteLine($"- Failure Count: {results.Count(result => !string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))}");
    writer.WriteLine();
    writer.WriteLine("| Object | Method | Payload | Result | Detail |");
    writer.WriteLine("| --- | --- | --- | --- | --- |");
    foreach (var result in results)
    {
        writer.WriteLine(
            $"| {EscapeCell(result.ObjectName)} | {EscapeCell(result.Method)} | {EscapeCell(result.Payload)} | {EscapeCell(result.Result)} | {EscapeCell(result.Detail)} |");
    }
}

static void TryUnloadScene(ProbeEventHandler handler, IKAScene scene, TimeSpan timeout)
{
    try
    {
        handler.ResetUnloadSceneTask();
        scene.UnloadScene();
        WaitForTaskWithMessagePump(handler.UnloadSceneTask, timeout);
    }
    catch
    {
    }
}

static void WriteCatalogMarkdown(
    SceneCatalogOptions options,
    IReadOnlyList<SceneCatalogEntry> entries,
    IReadOnlyList<SceneCatalogFailure> failures)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Scene Object Catalog");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Root: `{options.RootPath}`");
    writer.WriteLine($"- Scene Count: {entries.Count}");
    writer.WriteLine($"- Failure Count: {failures.Count}");
    writer.WriteLine();

    if (failures.Count > 0)
    {
        writer.WriteLine("## Failures");
        writer.WriteLine();
        foreach (var failure in failures)
        {
            writer.WriteLine($"- `{failure.ScenePath}`: {EscapeInline(failure.Reason)}");
        }

        writer.WriteLine();
    }

    writer.WriteLine("## Scenes");
    writer.WriteLine();
    foreach (var entry in entries)
    {
        writer.WriteLine($"### `{entry.RelativePath}`");
        writer.WriteLine();
        writer.WriteLine($"- Object Count: {entry.Objects.Count}");
        writer.WriteLine();
        writer.WriteLine("| Name | Type | Value | Visible |");
        writer.WriteLine("| --- | --- | --- | --- |");
        foreach (var obj in entry.Objects)
        {
            writer.WriteLine(
                $"| {EscapeCell(obj.Name)} | {obj.ObjectType} | {EscapeCell(obj.Value)} | {(obj.Visible ? "true" : "false")} |");
        }

        writer.WriteLine();
    }
}

static void WriteSceneObjectInspectionReport(
    SceneObjectInspectionOptions options,
    ObjectInfosProbeResult probeResult,
    IReadOnlyList<SceneObjectCatalogItem> objects)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Scene Object Inspection");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Scene: `{options.ScenePath}`");
    writer.WriteLine($"- Query Result: `{probeResult.Result}`");
    writer.WriteLine($"- Object Count: {objects.Count}");
    if (!string.IsNullOrWhiteSpace(options.NameFilter))
    {
        writer.WriteLine($"- Name Filter: `{options.NameFilter}`");
    }

    if (!string.IsNullOrWhiteSpace(probeResult.Detail))
    {
        writer.WriteLine($"- Detail: {EscapeInline(probeResult.Detail)}");
    }

    writer.WriteLine();
    writer.WriteLine("## Type Counts");
    writer.WriteLine();
    foreach (var typeGroup in objects.GroupBy(item => item.ObjectType).OrderBy(group => group.Key.ToString(), StringComparer.OrdinalIgnoreCase))
    {
        writer.WriteLine($"- `{typeGroup.Key}`: {typeGroup.Count()}");
    }

    writer.WriteLine();
    writer.WriteLine("## Objects");
    writer.WriteLine();
    writer.WriteLine("| Name | Type | Visible | Value |");
    writer.WriteLine("| --- | --- | --- | --- |");
    foreach (var item in objects)
    {
        writer.WriteLine(
            $"| {EscapeCell(item.Name)} | {EscapeCell(item.ObjectType.ToString())} | {(item.Visible ? "yes" : "no")} | {EscapeCell(item.Value)} |");
    }
}

static void WriteObjectTypeQueryReport(
    ObjectTypeQueryOptions options,
    IReadOnlyList<ObjectTypeQueryItem> items)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Object Type Query");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Scene: `{options.ScenePath}`");
    writer.WriteLine($"- Objects Queried: {items.Count}");
    writer.WriteLine();
    writer.WriteLine("| Object | Result | Type | Detail |");
    writer.WriteLine("| --- | --- | --- | --- |");
    foreach (var item in items)
    {
        writer.WriteLine(
            $"| {EscapeCell(item.ObjectName)} | {EscapeCell(item.Result)} | {EscapeCell(item.ObjectType.ToString())} | {EscapeCell(item.Detail)} |");
    }
}

static void WriteObjectRuntimeInspectionReport(
    ObjectTypeQueryOptions options,
    IReadOnlyList<ObjectRuntimeInspectionItem> items)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Object Runtime Inspection");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Scene: `{options.ScenePath}`");
    writer.WriteLine($"- Objects Inspected: {items.Count}");
    writer.WriteLine();
    writer.WriteLine("| Object | Type Result | Type | Animation | Position Result | X | Y | Z | Detail |");
    writer.WriteLine("| --- | --- | --- | --- | --- | --- | --- | --- | --- |");
    foreach (var item in items)
    {
        writer.WriteLine(
            $"| {EscapeCell(item.ObjectName)} | {EscapeCell(item.TypeResult)} | {EscapeCell(item.ObjectType.ToString())} | {EscapeCell(item.AnimationName)} | {EscapeCell(item.PositionResult)} | {EscapeCell(FormatCoordinate(item.X))} | {EscapeCell(FormatCoordinate(item.Y))} | {EscapeCell(FormatCoordinate(item.Z))} | {EscapeCell(item.Detail)} |");
    }
}

static void WriteScreenPointInspectionReport(
    ScreenPointInspectionOptions options,
    IReadOnlyList<ScreenPointInspectionItem> items)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Screen Point Inspection");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Scene: `{options.ScenePath}`");
    writer.WriteLine($"- Points Inspected: {items.Count}");
    writer.WriteLine();

    foreach (var item in items)
    {
        writer.WriteLine($"## ({item.X}, {item.Y})");
        writer.WriteLine();
        writer.WriteLine($"- Result: `{item.Result}`");
        if (!string.IsNullOrWhiteSpace(item.Detail))
        {
            writer.WriteLine($"- Detail: {EscapeInline(item.Detail)}");
        }

        writer.WriteLine();
        writer.WriteLine("| Name | Type | Visible | Value |");
        writer.WriteLine("| --- | --- | --- | --- |");
        foreach (var sceneObject in item.Objects)
        {
            writer.WriteLine(
                $"| {EscapeCell(sceneObject.Name)} | {EscapeCell(sceneObject.ObjectType.ToString())} | {(sceneObject.Visible ? "yes" : "no")} | {EscapeCell(sceneObject.Value)} |");
        }

        if (item.Objects.Count == 0)
        {
            writer.WriteLine("_No objects returned._");
        }

        writer.WriteLine();
    }
}

static void WriteSceneCapabilityInspectionReport(
    SceneCapabilityInspectionOptions options,
    IReadOnlyList<SceneCapabilityInspectionItem> items)
{
    Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!);

    using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false));
    writer.WriteLine("# Scene Capability Inspection");
    writer.WriteLine();
    writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    writer.WriteLine($"- Scene: `{options.ScenePath}`");
    writer.WriteLine($"- Candidate Count: {items.Count}");
    if (options.Keywords.Count > 0)
    {
        writer.WriteLine($"- Keywords: `{string.Join("; ", options.Keywords)}`");
    }

    writer.WriteLine();
    writer.WriteLine("| Candidate | Found | Anim | Chart | Counter | Path | QueryType | Type | QueryPos | X | Y | Z | SetPos | SetValueText | SetValueImage | CounterKey | Detail |");
    writer.WriteLine("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |");
    foreach (var item in items)
    {
        writer.WriteLine(
            $"| {EscapeCell(item.Candidate)} | {(item.ObjectFound ? "yes" : "no")} | {EscapeCell(item.AnimationName)} | {(item.IsChart ? "yes" : "no")} | {(item.IsCounter ? "yes" : "no")} | {(item.IsPath ? "yes" : "no")} | {EscapeCell(item.QueryTypeResult)} | {EscapeCell(item.ObjectType.ToString())} | {EscapeCell(item.QueryPositionResult)} | {EscapeCell(FormatCoordinate(item.X))} | {EscapeCell(FormatCoordinate(item.Y))} | {EscapeCell(FormatCoordinate(item.Z))} | {EscapeCell(item.SetPositionResult)} | {EscapeCell(item.SetValueTextResult)} | {EscapeCell(item.SetValueImageResult)} | {EscapeCell(item.CounterKeyResult)} | {EscapeCell(item.Detail)} |");
    }
}

static string EscapeCell(string? value)
{
    if (string.IsNullOrEmpty(value))
    {
        return string.Empty;
    }

    return value.Replace("\\", "\\\\", StringComparison.Ordinal)
        .Replace("|", "\\|", StringComparison.Ordinal)
        .Replace("\r", " ", StringComparison.Ordinal)
        .Replace("\n", "<br/>", StringComparison.Ordinal);
}

static string EscapeInline(string? value) => EscapeCell(value);

static string FormatCoordinate(float? value) => value.HasValue ? value.Value.ToString("0.###") : string.Empty;

static Task<SdkProbeResult> ProbeSdkAsync(ProbeOptions options)
{
    var completion = new TaskCompletionSource<SdkProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    var thread = new Thread(() =>
    {
        try
        {
            var handler = new ProbeEventHandler();
            var engine = (IKAEngine)new KAEngineClass();

            Console.WriteLine("[SDK] Calling Connect()...");
            var connectRequested = engine.Connect(options.Host, options.Port, handler);
            Console.WriteLine($"[SDK] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}");

            if (connectRequested == 0)
            {
                completion.TrySetResult(new SdkProbeResult(false, "FAILED", "Connect() returned 0."));
                return;
            }

            if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Timeout))
            {
                completion.TrySetResult(new SdkProbeResult(true, "TIMEOUT", $"OnConnect was not received within {options.Timeout.TotalSeconds:0} seconds."));
                return;
            }

            var errorCode = handler.ConnectTask.Result;
            var outcome = errorCode == 0 ? "SUCCESS" : "FAILED";
            completion.TrySetResult(new SdkProbeResult(true, outcome, $"OnConnect errorCode={errorCode}"));

            try
            {
                engine.Disconnect();
                handler.CloseTask.Wait(TimeSpan.FromSeconds(2));
            }
            catch
            {
            }
        }
        catch (Exception ex)
        {
            completion.TrySetResult(new SdkProbeResult(false, "EXCEPTION", ex.ToString()));
        }
    })
    {
        IsBackground = true,
        Name = "KarismaTcpProbe"
    };

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return completion.Task;
}

static bool WaitForTaskWithMessagePump(Task task, TimeSpan timeout)
{
    User32.PeekMessage(out _, IntPtr.Zero, 0, 0, 0);

    var deadline = DateTime.UtcNow + timeout;
    while (!task.IsCompleted)
    {
        while (User32.PeekMessage(out var message, IntPtr.Zero, 0, 0, 1))
        {
            User32.TranslateMessage(ref message);
            User32.DispatchMessage(ref message);
        }

        if (DateTime.UtcNow >= deadline)
        {
            return false;
        }

        Thread.Sleep(10);
    }

    return true;
}

internal sealed record ProbeOptions(string Host, int Port, TimeSpan Timeout)
{
    public static ProbeOptions Parse(string[] args)
    {
        var host = "127.0.0.1";
        var port = 30001;
        var timeout = TimeSpan.FromSeconds(5);

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--host" when index + 1 < args.Length:
                    host = args[++index];
                    break;
                case "--port" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedPort):
                    port = parsedPort;
                    index++;
                    break;
                case "--timeout" when index + 1 < args.Length && double.TryParse(args[index + 1], out var parsedTimeoutSeconds):
                    timeout = TimeSpan.FromSeconds(parsedTimeoutSeconds);
                    index++;
                    break;
            }
        }

        return new ProbeOptions(host, port, timeout);
    }
}

internal sealed record CounterProbeOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string ObjectName,
    int KeyIndex,
    double Number)
{
    public static CounterProbeOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? objectName = null;
        var keyIndex = 1;
        var number = 0d;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--object" when index + 1 < args.Length:
                    objectName = args[++index];
                    break;
                case "--key-index" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedKeyIndex):
                    keyIndex = parsedKeyIndex;
                    index++;
                    break;
                case "--number" when index + 1 < args.Length && double.TryParse(args[index + 1], out var parsedNumber):
                    number = parsedNumber;
                    index++;
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        if (string.IsNullOrWhiteSpace(objectName))
        {
            throw new ArgumentException("--object is required.");
        }

        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        return new CounterProbeOptions(connection, scenePath, sceneAlias, objectName, keyIndex, number);
    }
}

internal sealed record ChartInspectionOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string ObjectName)
{
    public static ChartInspectionOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? objectName = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--object" when index + 1 < args.Length:
                    objectName = args[++index];
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        if (string.IsNullOrWhiteSpace(objectName))
        {
            throw new ArgumentException("--object is required.");
        }

        scenePath = Path.GetFullPath(scenePath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        return new ChartInspectionOptions(connection, scenePath, sceneAlias, objectName);
    }
}

internal sealed record SaveSceneImageOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string OutputPath,
    int Width,
    int Height,
    int Frame,
    IReadOnlyList<int> Frames,
    string? OutputDirectory,
    string OutputPattern,
    string? SetObjectName,
    string? SetObjectValue,
    string? VisibleObjectName,
    bool? VisibleObjectValue,
    VariableNameUpdate? VariableName,
    CloneObjectUpdate? CloneObject,
    string? OperationsPath,
    MaterialOpacityUpdate? MaterialOpacity,
    SizeUpdate? Size,
    PositionUpdate? Position,
    IReadOnlyList<PositionUpdate> Positions,
    PositionKeyUpdate? PositionKey,
    IReadOnlyList<PositionKeyUpdate> PositionKeys,
    IReadOnlyList<PositionUpdate> PostPositions,
    IReadOnlyList<PositionKeyUpdate> PostPositionKeys,
    string? ChartObjectName,
    string? ChartCsvPath,
    IReadOnlyList<ChartCellUpdate> ChartCells,
    string? PathObjectName,
    IReadOnlyList<PathPoint3> PathPoints,
    IReadOnlyList<PathPointModification> PathModifications)
{
    public static SaveSceneImageOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? outputPath = null;
        string? outputDirectory = null;
        string outputPattern = "frame_{0:D4}.png";
        IReadOnlyList<int> frames = Array.Empty<int>();
        string? setObjectName = null;
        string? setObjectValue = null;
        string? visibleObjectName = null;
        bool? visibleObjectValue = null;
        string? variableNameObjectName = null;
        string? variableNameValue = null;
        string? cloneSourceObjectName = null;
        string? cloneVariableName = null;
        string? operationsPath = null;
        string? materialOpacityObjectName = null;
        float? materialOpacityValue = null;
        string? sizeObjectName = null;
        string? sizeRaw = null;
        string? positionObjectName = null;
        string? positionRaw = null;
        string? positionsRaw = null;
        string? positionKeyObjectName = null;
        int positionKeyIndex = 1;
        string? positionKeyRaw = null;
        string? positionKeysRaw = null;
        string? postPositionsRaw = null;
        string? postPositionKeysRaw = null;
        string? chartObjectName = null;
        string? chartCsvPath = null;
        string? chartCellsRaw = null;
        string? pathObjectName = null;
        string? pathPointsRaw = null;
        string? modifyPathRaw = null;
        var width = 320;
        var height = 180;
        var frame = -1;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--output-dir" when index + 1 < args.Length:
                    outputDirectory = args[++index];
                    break;
                case "--output-pattern" when index + 1 < args.Length:
                    outputPattern = args[++index];
                    break;
                case "--set-object" when index + 1 < args.Length:
                    setObjectName = args[++index];
                    break;
                case "--set-value" when index + 1 < args.Length:
                    setObjectValue = args[++index];
                    break;
                case "--visible-object" when index + 1 < args.Length:
                    visibleObjectName = args[++index];
                    break;
                case "--visible" when index + 1 < args.Length:
                    visibleObjectValue = args[++index] switch
                    {
                        "1" or "true" or "True" => true,
                        "0" or "false" or "False" => false,
                        _ => throw new ArgumentException("--visible must be true/false/1/0.")
                    };
                    break;
                case "--variable-name-object" when index + 1 < args.Length:
                    variableNameObjectName = args[++index];
                    break;
                case "--variable-name" when index + 1 < args.Length:
                    variableNameValue = args[++index];
                    break;
                case "--clone-source" when index + 1 < args.Length:
                    cloneSourceObjectName = args[++index];
                    break;
                case "--clone-name" when index + 1 < args.Length:
                    cloneVariableName = args[++index];
                    break;
                case "--operations" when index + 1 < args.Length:
                    operationsPath = args[++index];
                    break;
                case "--material-opacity-object" when index + 1 < args.Length:
                    materialOpacityObjectName = args[++index];
                    break;
                case "--material-opacity" when index + 1 < args.Length && float.TryParse(args[index + 1], out var parsedMaterialOpacity):
                    materialOpacityValue = parsedMaterialOpacity;
                    index++;
                    break;
                case "--size-object" when index + 1 < args.Length:
                    sizeObjectName = args[++index];
                    break;
                case "--size" when index + 1 < args.Length:
                    sizeRaw = args[++index];
                    break;
                case "--position-object" when index + 1 < args.Length:
                    positionObjectName = args[++index];
                    break;
                case "--position" when index + 1 < args.Length:
                    positionRaw = args[++index];
                    break;
                case "--positions" when index + 1 < args.Length:
                    positionsRaw = args[++index];
                    break;
                case "--position-key-object" when index + 1 < args.Length:
                    positionKeyObjectName = args[++index];
                    break;
                case "--position-key-index" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedPositionKeyIndex):
                    positionKeyIndex = parsedPositionKeyIndex;
                    index++;
                    break;
                case "--position-key" when index + 1 < args.Length:
                    positionKeyRaw = args[++index];
                    break;
                case "--position-keys" when index + 1 < args.Length:
                    positionKeysRaw = args[++index];
                    break;
                case "--post-positions" when index + 1 < args.Length:
                    postPositionsRaw = args[++index];
                    break;
                case "--post-position-keys" when index + 1 < args.Length:
                    postPositionKeysRaw = args[++index];
                    break;
                case "--chart-object" when index + 1 < args.Length:
                    chartObjectName = args[++index];
                    break;
                case "--chart-csv" when index + 1 < args.Length:
                    chartCsvPath = args[++index];
                    break;
                case "--chart-cells" when index + 1 < args.Length:
                    chartCellsRaw = args[++index];
                    break;
                case "--path-object" when index + 1 < args.Length:
                    pathObjectName = args[++index];
                    break;
                case "--path-points" when index + 1 < args.Length:
                    pathPointsRaw = args[++index];
                    break;
                case "--modify-path" when index + 1 < args.Length:
                    modifyPathRaw = args[++index];
                    break;
                case "--width" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedWidth):
                    width = parsedWidth;
                    index++;
                    break;
                case "--height" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedHeight):
                    height = parsedHeight;
                    index++;
                    break;
                case "--frame" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedFrame):
                    frame = parsedFrame;
                    index++;
                    break;
                case "--frames" when index + 1 < args.Length:
                    frames = ParseFrameSequence(args[++index]);
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        if (frames.Count > 0)
        {
            outputDirectory ??= outputPath;
            if (string.IsNullOrWhiteSpace(outputDirectory))
            {
                throw new ArgumentException("--output-dir is required when --frames is provided.");
            }

            outputDirectory = Path.GetFullPath(outputDirectory);
            outputPath ??= outputDirectory;
        }
        else if (string.IsNullOrWhiteSpace(outputPath))
        {
            throw new ArgumentException("--output is required.");
        }

        scenePath = Path.GetFullPath(scenePath);
        outputPath = Path.GetFullPath(outputPath);
        operationsPath = string.IsNullOrWhiteSpace(operationsPath)
            ? null
            : Path.GetFullPath(operationsPath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        return new SaveSceneImageOptions(
            connection,
            scenePath,
            sceneAlias,
            outputPath,
            width,
            height,
            frame,
            frames,
            outputDirectory,
            outputPattern,
            setObjectName,
            setObjectValue,
            visibleObjectName,
            visibleObjectValue,
            ParseVariableName(variableNameObjectName, variableNameValue),
            ParseCloneObject(cloneSourceObjectName, cloneVariableName),
            operationsPath,
            ParseMaterialOpacity(materialOpacityObjectName, materialOpacityValue),
            ParseSize(sizeObjectName, sizeRaw),
            ParsePosition(positionObjectName, positionRaw),
            ParsePositions(positionsRaw),
            ParsePositionKey(positionKeyObjectName, positionKeyIndex, positionKeyRaw),
            ParsePositionKeys(positionKeysRaw),
            ParsePositions(postPositionsRaw),
            ParsePositionKeys(postPositionKeysRaw),
            chartObjectName,
            chartCsvPath,
            ParseChartCells(chartCellsRaw),
            pathObjectName,
            ParsePathPoints(pathPointsRaw),
            ParsePathModifications(modifyPathRaw));
    }

    private static IReadOnlyList<int> ParseFrameSequence(string value)
    {
        var frames = new List<int>();
        foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var rangeMatch = Regex.Match(token, @"^(?<start>-?\d+)-(?<end>-?\d+)(?::(?<step>\d+))?$", RegexOptions.CultureInvariant);
            if (rangeMatch.Success)
            {
                var start = int.Parse(rangeMatch.Groups["start"].Value, CultureInfo.InvariantCulture);
                var end = int.Parse(rangeMatch.Groups["end"].Value, CultureInfo.InvariantCulture);
                var step = rangeMatch.Groups["step"].Success
                    ? int.Parse(rangeMatch.Groups["step"].Value, CultureInfo.InvariantCulture)
                    : 1;
                if (step <= 0)
                {
                    throw new ArgumentException("--frames range step must be greater than zero.");
                }

                if (start <= end)
                {
                    for (var frame = start; frame <= end; frame += step)
                    {
                        frames.Add(frame);
                    }
                }
                else
                {
                    for (var frame = start; frame >= end; frame -= step)
                    {
                        frames.Add(frame);
                    }
                }

                continue;
            }

            if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var singleFrame))
            {
                throw new ArgumentException($"Invalid frame token: {token}");
            }

            frames.Add(singleFrame);
        }

        return frames.Distinct().ToArray();
    }

    private static CloneObjectUpdate? ParseCloneObject(string? sourceObjectName, string? variableName)
    {
        if (string.IsNullOrWhiteSpace(sourceObjectName) || string.IsNullOrWhiteSpace(variableName))
        {
            return null;
        }

        return new CloneObjectUpdate(sourceObjectName, variableName);
    }

    private static MaterialOpacityUpdate? ParseMaterialOpacity(string? objectName, float? opacity)
    {
        if (string.IsNullOrWhiteSpace(objectName) || !opacity.HasValue)
        {
            return null;
        }

        return new MaterialOpacityUpdate(objectName, opacity.Value);
    }

    private static VariableNameUpdate? ParseVariableName(string? objectName, string? variableName)
    {
        if (string.IsNullOrWhiteSpace(objectName) || string.IsNullOrWhiteSpace(variableName))
        {
            return null;
        }

        return new VariableNameUpdate(objectName, variableName);
    }

    private static SizeUpdate? ParseSize(string? objectName, string? raw)
    {
        if (string.IsNullOrWhiteSpace(objectName) || string.IsNullOrWhiteSpace(raw))
        {
            return null;
        }

        var parts = raw.Split(',', StringSplitOptions.TrimEntries);
        if (parts.Length != 2 ||
            !float.TryParse(parts[0], out var width) ||
            !float.TryParse(parts[1], out var height))
        {
            throw new ArgumentException("--size must be 'width,height'.");
        }

        return new SizeUpdate(objectName, width, height);
    }

    private static PositionUpdate? ParsePosition(string? objectName, string? raw)
    {
        if (string.IsNullOrWhiteSpace(objectName) || string.IsNullOrWhiteSpace(raw))
        {
            return null;
        }

        var vectorType = eKVectorType.VECTOR_TYPE_XYZ;
        var rawParts = raw.Split(':', StringSplitOptions.TrimEntries);
        var coordinatesRaw = rawParts[0];
        if (rawParts.Length > 1 && !Enum.TryParse(rawParts[1], true, out vectorType))
        {
            throw new ArgumentException($"Unknown vector type '{rawParts[1]}'.");
        }

        var coordinates = coordinatesRaw.Split(',', StringSplitOptions.TrimEntries);
        if (coordinates.Length < 2 ||
            !float.TryParse(coordinates[0], out var x) ||
            !float.TryParse(coordinates[1], out var y))
        {
            throw new ArgumentException("--position must be 'x,y[,z][:VectorType]'.");
        }

        var z = 0f;
        if (coordinates.Length > 2 && !float.TryParse(coordinates[2], out z))
        {
            throw new ArgumentException("--position z must be numeric.");
        }

        return new PositionUpdate(objectName, x, y, z, vectorType);
    }

    private static IReadOnlyList<PositionUpdate> ParsePositions(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return Array.Empty<PositionUpdate>();
        }

        var updates = new List<PositionUpdate>();
        foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var nameParts = token.Split('=', 2, StringSplitOptions.TrimEntries);
            if (nameParts.Length != 2)
            {
                throw new ArgumentException($"Invalid position update: {token}");
            }

            var update = ParsePosition(nameParts[0], nameParts[1]);
            if (update is not null)
            {
                updates.Add(update);
            }
        }

        return updates;
    }

    private static PositionKeyUpdate? ParsePositionKey(string? objectName, int keyIndex, string? raw)
    {
        if (string.IsNullOrWhiteSpace(objectName) || string.IsNullOrWhiteSpace(raw))
        {
            return null;
        }

        var vectorType = eKVectorType.VECTOR_TYPE_XYZ;
        var rawParts = raw.Split(':', StringSplitOptions.TrimEntries);
        var coordinatesRaw = rawParts[0];
        if (rawParts.Length > 1 && !Enum.TryParse(rawParts[1], true, out vectorType))
        {
            throw new ArgumentException($"Unknown vector type '{rawParts[1]}'.");
        }

        var coordinates = coordinatesRaw.Split(',', StringSplitOptions.TrimEntries);
        if (coordinates.Length < 2 ||
            !float.TryParse(coordinates[0], out var x) ||
            !float.TryParse(coordinates[1], out var y))
        {
            throw new ArgumentException("--position-key must be 'x,y[,z][:VectorType]'.");
        }

        var z = 0f;
        if (coordinates.Length > 2 && !float.TryParse(coordinates[2], out z))
        {
            throw new ArgumentException("--position-key z must be numeric.");
        }

        return new PositionKeyUpdate(objectName, keyIndex, x, y, z, vectorType);
    }

    private static IReadOnlyList<PositionKeyUpdate> ParsePositionKeys(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return Array.Empty<PositionKeyUpdate>();
        }

        var updates = new List<PositionKeyUpdate>();
        foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var nameParts = token.Split('=', 2, StringSplitOptions.TrimEntries);
            if (nameParts.Length != 2)
            {
                throw new ArgumentException($"Invalid position key update: {token}");
            }

            var objectAndKey = nameParts[0].Split('#', 2, StringSplitOptions.TrimEntries);
            if (objectAndKey.Length != 2 || !int.TryParse(objectAndKey[1], out var keyIndex))
            {
                throw new ArgumentException($"Invalid position key object/index: {nameParts[0]}");
            }

            var update = ParsePositionKey(objectAndKey[0], keyIndex, nameParts[1]);
            if (update is not null)
            {
                updates.Add(update);
            }
        }

        return updates;
    }

    private static IReadOnlyList<ChartCellUpdate> ParseChartCells(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return Array.Empty<ChartCellUpdate>();
        }

        var updates = new List<ChartCellUpdate>();
        foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var parts = token.Split(',', StringSplitOptions.TrimEntries);
            if (parts.Length < 3 ||
                !int.TryParse(parts[0], out var row) ||
                !int.TryParse(parts[1], out var column) ||
                !float.TryParse(parts[2], out var value))
            {
                throw new ArgumentException($"Invalid chart cell update: {token}");
            }

            updates.Add(new ChartCellUpdate(row, column, value));
        }

        return updates;
    }

    private static IReadOnlyList<PathPoint3> ParsePathPoints(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return Array.Empty<PathPoint3>();
        }

        var points = new List<PathPoint3>();
        foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var parts = token.Split(',', StringSplitOptions.TrimEntries);
            if (parts.Length < 2 ||
                !float.TryParse(parts[0], out var x) ||
                !float.TryParse(parts[1], out var y))
            {
                throw new ArgumentException($"Invalid path point: {token}");
            }

            var z = 0f;
            if (parts.Length >= 3 && !float.TryParse(parts[2], out z))
            {
                throw new ArgumentException($"Invalid path point z value: {token}");
            }

            points.Add(new PathPoint3(x, y, z));
        }

        return points;
    }

    private static IReadOnlyList<PathPointModification> ParsePathModifications(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return Array.Empty<PathPointModification>();
        }

        var modifications = new List<PathPointModification>();
        foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var sections = token.Split(':', StringSplitOptions.TrimEntries);
            if (sections.Length < 2)
            {
                throw new ArgumentException($"Invalid path modification: {token}");
            }

            if (!int.TryParse(sections[0], out var index))
            {
                throw new ArgumentException($"Invalid path modification index: {token}");
            }

            var coords = sections[1].Split(',', StringSplitOptions.TrimEntries);
            if (coords.Length < 2 ||
                !float.TryParse(coords[0], out var x) ||
                !float.TryParse(coords[1], out var y))
            {
                throw new ArgumentException($"Invalid path modification coordinates: {token}");
            }

            var z = 0f;
            if (coords.Length >= 3 && !float.TryParse(coords[2], out z))
            {
                throw new ArgumentException($"Invalid path modification z value: {token}");
            }

            var vectorType = eKVectorType.VECTOR_TYPE_XYZ;
            if (sections.Length >= 3)
            {
                vectorType = sections[2].ToUpperInvariant() switch
                {
                    "X" => eKVectorType.VECTOR_TYPE_X,
                    "Y" => eKVectorType.VECTOR_TYPE_Y,
                    "Z" => eKVectorType.VECTOR_TYPE_Z,
                    "XY" => eKVectorType.VECTOR_TYPE_XY,
                    "YZ" => eKVectorType.VECTOR_TYPE_YZ,
                    "ZX" => eKVectorType.VECTOR_TYPE_ZX,
                    "XYZ" => eKVectorType.VECTOR_TYPE_XYZ,
                    _ => throw new ArgumentException($"Invalid path modification vector type: {token}")
                };
            }

            modifications.Add(new PathPointModification(index, x, y, z, vectorType));
        }

        return modifications;
    }
}

internal sealed record SceneCatalogOptions(
    ProbeOptions Connection,
    string RootPath,
    string OutputPath,
    string? SceneFilter,
    int? MaxScenes)
{
    public static SceneCatalogOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        var rootPath = TornadoPathResolver.GetDefaultT3CutPath();
        string? outputPath = null;
        string? sceneFilter = null;
        int? maxScenes = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--root" when index + 1 < args.Length:
                    rootPath = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--filter" when index + 1 < args.Length:
                    sceneFilter = args[++index];
                    break;
                case "--max-scenes" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedMaxScenes):
                    maxScenes = parsedMaxScenes;
                    index++;
                    break;
            }
        }

        if (!Directory.Exists(rootPath))
        {
            throw new DirectoryNotFoundException($"Catalog root path does not exist: {rootPath}");
        }

        outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCENE_OBJECT_CATALOG.md");
        outputPath = Path.GetFullPath(outputPath);
        return new SceneCatalogOptions(connection, Path.GetFullPath(rootPath), outputPath, sceneFilter, maxScenes);
    }
}

internal sealed record SceneObjectInspectionOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string OutputPath,
    string? NameFilter)
{
    public static SceneObjectInspectionOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? outputPath = null;
        string? nameFilter = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--filter" when index + 1 < args.Length:
                    nameFilter = args[++index];
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        scenePath = Path.GetFullPath(scenePath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCENE_OBJECT_INSPECTION.md");
        outputPath = Path.GetFullPath(outputPath);
        return new SceneObjectInspectionOptions(connection, scenePath, sceneAlias, outputPath, nameFilter);
    }
}

internal sealed record ObjectTypeQueryOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string OutputPath,
    IReadOnlyList<string> ObjectNames)
{
    public static ObjectTypeQueryOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? outputPath = null;
        string? objectsRaw = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--objects" when index + 1 < args.Length:
                    objectsRaw = args[++index];
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        if (string.IsNullOrWhiteSpace(objectsRaw))
        {
            throw new ArgumentException("--objects is required.");
        }

        var objectNames = objectsRaw
            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Where(value => !string.IsNullOrWhiteSpace(value))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();
        if (objectNames.Length == 0)
        {
            throw new ArgumentException("--objects did not contain any names.");
        }

        scenePath = Path.GetFullPath(scenePath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        outputPath ??= Path.Combine(Environment.CurrentDirectory, "OBJECT_TYPE_QUERY.md");
        outputPath = Path.GetFullPath(outputPath);
        return new ObjectTypeQueryOptions(connection, scenePath, sceneAlias, outputPath, objectNames);
    }
}

internal sealed record ScreenPointInspectionOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string OutputPath,
    IReadOnlyList<ScreenPoint> Points)
{
    public static ScreenPointInspectionOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? outputPath = null;
        string? pointsRaw = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--points" when index + 1 < args.Length:
                    pointsRaw = args[++index];
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        if (string.IsNullOrWhiteSpace(pointsRaw))
        {
            throw new ArgumentException("--points is required.");
        }

        var points = pointsRaw
            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Select(rawPoint =>
            {
                var coordinates = rawPoint.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                if (coordinates.Length != 2 ||
                    !int.TryParse(coordinates[0], out var x) ||
                    !int.TryParse(coordinates[1], out var y))
                {
                    throw new ArgumentException($"Invalid screen point '{rawPoint}'. Use 'x,y'.");
                }

                return new ScreenPoint(x, y);
            })
            .Distinct()
            .ToArray();
        if (points.Length == 0)
        {
            throw new ArgumentException("--points did not contain any coordinates.");
        }

        scenePath = Path.GetFullPath(scenePath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCREEN_POINT_INSPECTION.md");
        outputPath = Path.GetFullPath(outputPath);
        return new ScreenPointInspectionOptions(connection, scenePath, sceneAlias, outputPath, points);
    }
}

internal sealed record SceneCapabilityInspectionOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string OutputPath,
    IReadOnlyList<string> Keywords,
    int? MaxCandidates)
{
    public static SceneCapabilityInspectionOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? outputPath = null;
        string? keywordsRaw = null;
        int? maxCandidates = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--keywords" when index + 1 < args.Length:
                    keywordsRaw = args[++index];
                    break;
                case "--max-candidates" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedMaxCandidates):
                    maxCandidates = parsedMaxCandidates;
                    index++;
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        var keywords = string.IsNullOrWhiteSpace(keywordsRaw)
            ? Array.Empty<string>()
            : keywordsRaw
                .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                .Where(value => !string.IsNullOrWhiteSpace(value))
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();

        scenePath = Path.GetFullPath(scenePath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCENE_CAPABILITY_INSPECTION.md");
        outputPath = Path.GetFullPath(outputPath);
        return new SceneCapabilityInspectionOptions(connection, scenePath, sceneAlias, outputPath, keywords, maxCandidates);
    }
}

internal sealed record SceneValidationOptions(
    ProbeOptions Connection,
    string ScenePath,
    string SceneAlias,
    string OperationsPath,
    string OutputPath)
{
    public static SceneValidationOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        string? scenePath = null;
        string? sceneAlias = null;
        string? operationsPath = null;
        string? outputPath = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--scene" when index + 1 < args.Length:
                    scenePath = args[++index];
                    break;
                case "--alias" when index + 1 < args.Length:
                    sceneAlias = args[++index];
                    break;
                case "--operations" when index + 1 < args.Length:
                    operationsPath = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(scenePath))
        {
            throw new ArgumentException("--scene is required.");
        }

        if (string.IsNullOrWhiteSpace(operationsPath))
        {
            throw new ArgumentException("--operations is required.");
        }

        scenePath = Path.GetFullPath(scenePath);
        operationsPath = Path.GetFullPath(operationsPath);
        sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath);
        outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCENE_VARIABLE_VALIDATION.md");
        outputPath = Path.GetFullPath(outputPath);

        return new SceneValidationOptions(connection, scenePath, sceneAlias, operationsPath, outputPath);
    }
}

internal sealed record FolderInspectionOptions(ProbeOptions Connection, string RootPath, string OutputPath, string? SceneFilter, int? MaxScenes)
{
    public static FolderInspectionOptions Parse(string[] args)
    {
        var connection = ProbeOptions.Parse(args);
        var rootPath = TornadoPathResolver.GetDefaultT3CutPath();
        string? outputPath = null;
        string? sceneFilter = null;
        int? maxScenes = null;

        for (var index = 0; index < args.Length; index++)
        {
            switch (args[index])
            {
                case "--root" when index + 1 < args.Length:
                    rootPath = args[++index];
                    break;
                case "--output" when index + 1 < args.Length:
                    outputPath = args[++index];
                    break;
                case "--filter" when index + 1 < args.Length:
                    sceneFilter = args[++index];
                    break;
                case "--max-scenes" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedMaxScenes):
                    maxScenes = parsedMaxScenes;
                    index++;
                    break;
            }
        }

        rootPath = Path.GetFullPath(rootPath);
        outputPath ??= Path.Combine(Environment.CurrentDirectory, "TSCN_VARIABLE_DISCOVERY.md");
        outputPath = Path.GetFullPath(outputPath);
        return new FolderInspectionOptions(connection, rootPath, outputPath, sceneFilter, maxScenes);
    }
}

internal sealed record SdkProbeResult(bool ConnectRequestAccepted, string ConnectOutcome, string Detail);
internal sealed record CounterProbeResult(bool ConnectRequestAccepted, string SceneLoadOutcome, string CounterOutcome, string Detail);
internal sealed record ChartCellSnapshot(int Row, int Column, string Value);
internal sealed record ChartCellUpdate(int Row, int Column, float Value);
internal sealed record PathPoint3(float X, float Y, float Z);
internal sealed record SizeUpdate(string ObjectName, float Width, float Height);
internal sealed record VariableNameUpdate(string ObjectName, string VariableName);
internal sealed record CloneObjectUpdate(string SourceObjectName, string VariableName);
internal sealed record MaterialOpacityUpdate(string ObjectName, float Opacity);
internal sealed record PositionUpdate(string ObjectName, float X, float Y, float Z, eKVectorType VectorType);
internal sealed record PositionKeyUpdate(string ObjectName, int KeyIndex, float X, float Y, float Z, eKVectorType VectorType);
internal sealed record PathPointModification(int Index, float X, float Y, float Z, eKVectorType VectorType);
internal sealed record ChartDataTableProbeResult(
    eKResult Result,
    int RowCount,
    int ColumnCount,
    IReadOnlyList<ChartCellSnapshot> Cells,
    string Detail);
internal sealed record ChartInspectionProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    string InspectionOutcome,
    int RowCount,
    int ColumnCount,
    IReadOnlyList<ChartCellSnapshot> Cells,
    string Detail);
internal sealed record SaveSceneImageProbeResult(bool ConnectRequestAccepted, string SceneLoadOutcome, string SaveOutcome, string OutputPath, string Detail);
internal sealed record SceneCatalogProbeResult(
    bool ConnectRequestAccepted,
    int SceneCount,
    int CatalogedCount,
    string OutputPath,
    IReadOnlyList<SceneCatalogFailure> Failures);
internal sealed record SceneCatalogFailure(string ScenePath, string Reason);
internal sealed record SceneCatalogEntry(string ScenePath, string RelativePath, IReadOnlyList<SceneObjectCatalogItem> Objects);
internal sealed record SceneObjectCatalogItem(string Name, eKObjectType ObjectType, string Value, bool Visible);
internal sealed record ObjectInfosProbeResult(eKResult Result, string SceneName, IReadOnlyList<SceneObjectCatalogItem> Objects, string Detail);
internal sealed record SceneObjectInspectionProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    string ObjectInfoOutcome,
    string OutputPath,
    IReadOnlyList<SceneObjectCatalogItem> Objects,
    string Detail);
internal sealed record ObjectTypeCallbackResult(eKResult Result, string ObjectName, eKObjectType ObjectType, string Detail);
internal sealed record ObjectTypeQueryItem(string ObjectName, string Result, eKObjectType ObjectType, string Detail);
internal sealed record ObjectTypeQueryProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    string OutputPath,
    IReadOnlyList<ObjectTypeQueryItem> Items,
    string Detail);
internal sealed record PositionCallbackResult(eKResult Result, string ObjectName, float X, float Y, float Z, string Detail);
internal sealed record ScreenPoint(int X, int Y);
internal sealed record ScreenPointInspectionItem(int X, int Y, string Result, IReadOnlyList<SceneObjectCatalogItem> Objects, string Detail);
internal sealed record ScreenPointInspectionProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    string OutputPath,
    IReadOnlyList<ScreenPointInspectionItem> Items,
    string Detail);
internal sealed record SceneCapabilityInspectionItem(
    string Candidate,
    bool ObjectFound,
    string AnimationName,
    bool IsChart,
    bool IsCounter,
    bool IsPath,
    string QueryTypeResult,
    eKObjectType ObjectType,
    string QueryPositionResult,
    float? X,
    float? Y,
    float? Z,
    string SetPositionResult,
    string SetValueTextResult,
    string SetValueImageResult,
    string CounterKeyResult,
    string Detail);
internal sealed record SceneCapabilityInspectionProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    string OutputPath,
    IReadOnlyList<SceneCapabilityInspectionItem> Items,
    string Detail);
internal sealed record ObjectRuntimeInspectionItem(
    string ObjectName,
    string TypeResult,
    eKObjectType ObjectType,
    string AnimationName,
    string PositionResult,
    float? X,
    float? Y,
    float? Z,
    string Detail);
internal sealed record ObjectRuntimeInspectionProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    string OutputPath,
    IReadOnlyList<ObjectRuntimeInspectionItem> Items,
    string Detail);
internal sealed record SceneValidationProbeResult(
    bool ConnectRequestAccepted,
    string SceneLoadOutcome,
    int SuccessCount,
    int FailureCount,
    string OutputPath,
    string Detail);
internal sealed record FolderInspectionProbeResult(
    bool ConnectRequestAccepted,
    int SceneCount,
    int ProcessedSceneCount,
    int DiscoveredVariableCount,
    int FailureCount,
    string OutputPath,
    string Detail);
internal sealed class SceneValidationOperation
{
    public string ObjectName { get; set; } = string.Empty;

    public string Method { get; set; } = "SetValue";

    public string? Value { get; set; }

    public int KeyIndex { get; set; }

    public double Number { get; set; }

    public string? StyleType { get; set; }

    public int Order { get; set; }

    public int R { get; set; }

    public int G { get; set; }

    public int B { get; set; }

    public int A { get; set; } = 255;

    public bool Visible { get; set; }

    public bool ContinueOnFailure { get; set; }
}

internal sealed record SceneOperationValidationResult(string ObjectName, string Method, string Payload, string Result, string Detail);
internal sealed record SceneInspectionResult(string ScenePath, string RelativePath, int CandidateCount, IReadOnlyList<SceneVariableDiscovery> Discoveries);
internal sealed record SceneVariableDiscovery(string VariableName, string Method, string Payload, string Result);

internal sealed class ProbeEventHandler : KAEventHandler
{
    private readonly TaskCompletionSource<int> _connectTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private readonly TaskCompletionSource<int> _closeTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _loadSceneTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _unloadSceneTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _counterNumberKeyTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _styleColorTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _visibleTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _variableNameTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _addCloneObjectTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _materialOpacityTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _setValueTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _sizeTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _saveSceneImageTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<ObjectInfosProbeResult> _objectInfosTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<ChartDataTableProbeResult> _chartDataTableTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _chartMutationTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<ObjectTypeCallbackResult> _objectTypeTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<PositionCallbackResult> _queryPositionTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _positionTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _positionKeyTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private TaskCompletionSource<eKResult> _pathPointTask = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private IKAScene? _sceneToQueryOnLoad;

    public Task<int> ConnectTask => _connectTask.Task;

    public Task<int> CloseTask => _closeTask.Task;

    public Task<eKResult> LoadSceneTask => _loadSceneTask.Task;

    public Task<eKResult> UnloadSceneTask => _unloadSceneTask.Task;

    public Task<eKResult> CounterNumberKeyTask => _counterNumberKeyTask.Task;

    public Task<eKResult> StyleColorTask => _styleColorTask.Task;

    public Task<eKResult> VisibleTask => _visibleTask.Task;

    public Task<eKResult> VariableNameTask => _variableNameTask.Task;

    public Task<eKResult> AddCloneObjectTask => _addCloneObjectTask.Task;

    public Task<eKResult> MaterialOpacityTask => _materialOpacityTask.Task;

    public Task<eKResult> SetValueTask => _setValueTask.Task;

    public Task<eKResult> SizeTask => _sizeTask.Task;

    public Task<eKResult> SaveSceneImageTask => _saveSceneImageTask.Task;

    public Task<ObjectInfosProbeResult> ObjectInfosTask => _objectInfosTask.Task;

    public Task<ChartDataTableProbeResult> ChartDataTableTask => _chartDataTableTask.Task;

    public Task<eKResult> ChartMutationTask => _chartMutationTask.Task;

    public Task<ObjectTypeCallbackResult> ObjectTypeTask => _objectTypeTask.Task;

    public Task<PositionCallbackResult> QueryPositionTask => _queryPositionTask.Task;

    public Task<eKResult> PositionTask => _positionTask.Task;

    public Task<eKResult> PositionKeyTask => _positionKeyTask.Task;

    public Task<eKResult> PathPointTask => _pathPointTask.Task;

    public void ResetLoadSceneTask() => _loadSceneTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetUnloadSceneTask() => _unloadSceneTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetCounterNumberKeyTask() => _counterNumberKeyTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetStyleColorTask() => _styleColorTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetVisibleTask() => _visibleTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetVariableNameTask() => _variableNameTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetAddCloneObjectTask() => _addCloneObjectTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetMaterialOpacityTask() => _materialOpacityTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetSetValueTask() => _setValueTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetSizeTask() => _sizeTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetSaveSceneImageTask() => _saveSceneImageTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetObjectInfosTask() => _objectInfosTask = new TaskCompletionSource<ObjectInfosProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetChartDataTableTask() => _chartDataTableTask = new TaskCompletionSource<ChartDataTableProbeResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetChartMutationTask() => _chartMutationTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetObjectTypeTask() => _objectTypeTask = new TaskCompletionSource<ObjectTypeCallbackResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetQueryPositionTask() => _queryPositionTask = new TaskCompletionSource<PositionCallbackResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetPositionTask() => _positionTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetPositionKeyTask() => _positionKeyTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ResetPathPointTask() => _pathPointTask = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);

    public void ConfigureQueryObjectInfosOnLoad(IKAScene scene) => _sceneToQueryOnLoad = scene;

    public void OnConnect(int ErrorCode)
    {
        Console.WriteLine($"[SDK] OnConnect errorCode={ErrorCode}");
        _connectTask.TrySetResult(ErrorCode);
    }

    public void OnClose(int ErrorCode)
    {
        Console.WriteLine($"[SDK] OnClose errorCode={ErrorCode}");
        _closeTask.TrySetResult(ErrorCode);
    }

    public void OnLoadScene(eKResult Result, string SceneName)
    {
        Console.WriteLine($"[SDK] OnLoadScene result={Result} scene={SceneName}");
        _loadSceneTask.TrySetResult(Result);

        if (Result == eKResult.RESULT_SUCCESS && _sceneToQueryOnLoad is not null)
        {
            try
            {
                _sceneToQueryOnLoad.QueryObjectInfos();
            }
            catch (Exception ex)
            {
                _objectInfosTask.TrySetResult(new ObjectInfosProbeResult(
                    eKResult.RESULT_FAILURE,
                    SceneName,
                    Array.Empty<SceneObjectCatalogItem>(),
                    ex.Message));
            }
            finally
            {
                _sceneToQueryOnLoad = null;
            }
        }
    }
    public void OnLoadSceneForce(eKResult Result, string SceneName) { }
    public void OnLogMessage(string LogMessage) { }
    public void OnMessageNo(uint MessageNo) { }
    public void OnBeginTransaction(eKResult Result) { }
    public void OnEndTransaction(eKResult Result) { }
    public void OnHeartBeat(eKResult Result) { }
    public void OnUnloadAll(eKResult Result) { }
    public void OnSetTrialPlayoutMode(eKResult Result) { }
    public void OnCheckVersion(eKResult Result, string ServerVersion, string SDKVersion) { }
    public void OnSetAudioOutput(eKResult Result) { }
    public void OnScenePrepare(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnScenePrepareEx(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnPlay(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnPlayOut(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnStop(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnStopAll(eKResult Result) { }
    public void OnPause(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnResume(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnQueryIsOnAir(eKResult Result, int OutputChannelIndex, int LayerNo, int bOnAir) { }
    public void OnTrigger(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnScenePlayingStarted(string SceneName, int OutputChannelIndex, int LayerNo) { }
    public void OnScenePlayed(string SceneName, int OutputChannelIndex, int LayerNo) { }
    public void OnSceneAnimationPlayed(string SceneName, int OutputChannelIndex, int LayerNo, string AnimationName) { }
    public void OnScenePaused(string SceneName, int OutputChannelIndex, int LayerNo, int bLastPause) { }
    public void OnSceneSaved(eKResult Result, string FileName) { }
    public void OnTriggerObject(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnResumeBackground(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnSaveMixedPreviewImage(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnPlayDirect(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnCutIn(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnCutOut(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnClearNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnPlayRange(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnQueryPlaybackRangeCount(eKResult Result, string SceneName, int PlaybackRangeCount) { }
    public void OnQueryPlaybackRange(eKResult Result, string SceneName, int PlaybackRangeNo, int Start, int End) { }
    public void OnQueryOutputChannelIndex(eKResult Result, string SceneName, int OutputChannelIndex) { }
    public void OnPlayInNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnPlayOutNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnSetBackgroundFill(eKResult Result, string SceneName) { }
    public void OnSetBackgroundTexture(eKResult Result, string SceneName) { }
    public void OnSetBackgroundVideo(eKResult Result, string SceneName) { }
    public void OnSetBackgroundLiveIn(eKResult Result, string SceneName) { }
    public void OnUseBackground(eKResult Result, string SceneName) { }
    public void OnSetBackgroundVideoPlayInfo(eKResult Result, string SceneName) { }
    public void OnQueryBackgroundVideoPlayInfo(eKResult Result, string SceneName, ref sKVideoPlayInfo pVideoPlayInfo) { }
    public void OnSetSceneEffectType(eKResult Result, string SceneName) { }
    public void OnSaveSceneImage(eKResult Result, string SceneName)
    {
        Console.WriteLine($"[SDK] OnSaveSceneImage result={Result} scene={SceneName}");
        _saveSceneImageTask.TrySetResult(Result);
    }
    public void OnSaveScene(eKResult Result, string SceneName) { }
    public void OnUnloadScene(eKResult Result, string SceneName)
    {
        _unloadSceneTask.TrySetResult(Result);
    }
    public void OnReloadScene(eKResult Result, string SceneName) { }
    public void OnUpdateTextures(eKResult Result, string SceneName) { }
    public void OnSetSceneAudioFile(eKResult Result, string SceneName) { }
    public void OnEnableSceneAudio(eKResult Result, string SceneName) { }
    public void OnSetSceneDuration(eKResult Result, string SceneName) { }
    public void OnSetBackgroundPauseType(eKResult Result, string SceneName) { }
    public void OnSetBackgroundChangeType(eKResult Result, string SceneName) { }
    public void OnSetBackgroundPauseAtZeroFrameAsStandBy(eKResult Result, string SceneName) { }
    public void OnResetDuration(eKResult Result, string SceneName) { }
    public void OnSetDuration(eKResult Result, string SceneName) { }
    public void OnAddObject(eKResult Result, string SceneName) { }
    public void OnUpdateThumbnail(eKResult Result, string SceneName) { }
    public void OnExportVideo(eKResult Result, string SceneName) { }
    public void OnStopVideoExporting(eKResult Result) { }
    public void OnQueryVideoExportingProgress(eKResult Result, string TargetName, int CurrentFrame, int TotalFrame) { }
    public void OnFinishedVideoExporting(eKResult Result, string FileName) { }
    public void OnAddPause(eKResult Result, string SceneName) { }
    public void OnDeletePause(eKResult Result, string SceneName) { }
    public void OnSetPause(eKResult Result, string SceneName) { }
    public void OnSetPauseWithIndex(eKResult Result, string SceneName) { }
    public void OnDeletePauseWithIndex(eKResult Result, string SceneName) { }
    public void OnQueryPauseCount(eKResult Result, string SceneName, int PauseCount) { }
    public void OnQueryObjectInfos(eKResult Result, string SceneName, KAObjectInfos pObjectInfos)
    {
        try
        {
            var objects = new List<SceneObjectCatalogItem>();
            if (Result == eKResult.RESULT_SUCCESS)
            {
                var count = pObjectInfos.GetCount();
                for (var index = 0; index < count; index++)
                {
                    var objectInfo = pObjectInfos.GetObjectInfo(index);
                    objects.Add(new SceneObjectCatalogItem(
                        objectInfo.Name ?? string.Empty,
                        objectInfo.ObjectType,
                        objectInfo.Value ?? string.Empty,
                        objectInfo.bVisible != 0));
                }
            }

            _objectInfosTask.TrySetResult(new ObjectInfosProbeResult(Result, SceneName, objects, string.Empty));
        }
        catch (Exception ex)
        {
            _objectInfosTask.TrySetResult(new ObjectInfosProbeResult(
                eKResult.RESULT_FAILURE,
                SceneName,
                Array.Empty<SceneObjectCatalogItem>(),
                ex.Message));
        }
    }
    public void OnQueryAnimationNames(eKResult Result, string SceneName, KAStrings pAnimationNames) { }
    public void OnQueryAnimationCount(eKResult Result, string SceneName, int AnimationCount) { }
    public void OnQueryObjectInfosByScreenPoint(eKResult Result, KAObjectInfos pObjectInfos)
    {
        try
        {
            var objects = new List<SceneObjectCatalogItem>();
            if (Result == eKResult.RESULT_SUCCESS)
            {
                var count = pObjectInfos.GetCount();
                for (var index = 0; index < count; index++)
                {
                    var objectInfo = pObjectInfos.GetObjectInfo(index);
                    objects.Add(new SceneObjectCatalogItem(
                        objectInfo.Name ?? string.Empty,
                        objectInfo.ObjectType,
                        objectInfo.Value ?? string.Empty,
                        objectInfo.bVisible != 0));
                }
            }

            _objectInfosTask.TrySetResult(new ObjectInfosProbeResult(Result, string.Empty, objects, string.Empty));
        }
        catch (Exception ex)
        {
            _objectInfosTask.TrySetResult(new ObjectInfosProbeResult(
                eKResult.RESULT_FAILURE,
                string.Empty,
                Array.Empty<SceneObjectCatalogItem>(),
                ex.Message));
        }
    }
    public void OnQuerySceneEffectType(eKResult Result, string SceneName, int bInEffect, eKEffectType EffectType, int Duration) { }
    public void OnQueryDuration(eKResult Result, string SceneName, string AnimationName, int Duration) { }
    public void OnQueryContentsOfTextObjects(eKResult Result, string SceneName, KAStrings pTexts) { }
    public void OnSetStyleColor(eKResult Result, string SceneName, string ObjectName)
    {
        if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT)
        {
            Console.WriteLine($"[SDK] OnSetStyleColor result={Result} scene={SceneName} object={ObjectName}");
        }

        _styleColorTask.TrySetResult(Result);
    }
    public void OnSetStyleTexture(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetFaceTextColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetEdgeTextColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetShadowTextColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetVisible(eKResult Result, string SceneName, string ObjectName)
    {
        if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT)
        {
            Console.WriteLine($"[SDK] OnSetVisible result={Result} scene={SceneName} object={ObjectName}");
        }

        _visibleTask.TrySetResult(Result);
    }
    public void OnSetVariableName(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetVariableName result={Result} scene={SceneName} object={ObjectName}");
        _variableNameTask.TrySetResult(Result);
    }
    public void OnAddCloneObject(eKResult Result, string SceneName)
    {
        Console.WriteLine($"[SDK] OnAddCloneObject result={Result} scene={SceneName}");
        _addCloneObjectTask.TrySetResult(Result);
    }
    public void OnSetTransparencyOpacity(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetTransparencyOpacity result={Result} scene={SceneName} object={ObjectName}");
        _materialOpacityTask.TrySetResult(Result);
    }
    public void OnSetValue(eKResult Result, string SceneName, string ObjectName)
    {
        if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT)
        {
            Console.WriteLine($"[SDK] OnSetValue result={Result} scene={SceneName} object={ObjectName}");
        }

        _setValueTask.TrySetResult(Result);
    }
    public void OnAddText(eKResult Result, string SceneName, string ObjectName) { }
    public void OnStoreTextStyle(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetTextStyle(eKResult Result, string SceneName, string ObjectName) { }
    public void OnEditText(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetFont(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetTextRange(eKResult Result, string SceneName, string ObjectName) { }
    public void OnResetTextRange(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryObjectType(eKResult Result, string SceneName, string ObjectName, eKObjectType ObjectType)
    {
        Console.WriteLine($"[SDK] OnQueryObjectType result={Result} scene={SceneName} object={ObjectName} type={ObjectType}");
        _objectTypeTask.TrySetResult(new ObjectTypeCallbackResult(Result, ObjectName, ObjectType, string.Empty));
    }
    public void OnSetChartCSVFile(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetChartCSVFile result={Result} scene={SceneName} object={ObjectName}");
        _chartMutationTask.TrySetResult(Result);
    }
    public void OnSetChartCellData(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetChartCellData result={Result} scene={SceneName} object={ObjectName}");
        _chartMutationTask.TrySetResult(Result);
    }
    public void OnQueryChartDataTable(eKResult Result, string SceneName, string ObjectName, KAChartDataTable Table)
    {
        var rowCount = 0;
        var columnCount = 0;
        var cells = new List<ChartCellSnapshot>();
        var detail = string.Empty;

        if (Result == eKResult.RESULT_SUCCESS)
        {
            try
            {
                rowCount = Table.GetRowCount();
                columnCount = Table.GetColumnCount();

                for (var row = 0; row < rowCount; row++)
                {
                    for (var column = 0; column < columnCount; column++)
                    {
                        var cell = Table.GetChartDataCell(row, column);
                        cells.Add(new ChartCellSnapshot(row, column, cell.Data ?? string.Empty));
                    }
                }
            }
            catch (Exception ex)
            {
                detail = ex.Message;
            }
        }

        _chartDataTableTask.TrySetResult(new ChartDataTableProbeResult(Result, rowCount, columnCount, cells, detail));
    }
    public void OnQuerySize(eKResult Result, string SceneName, string ObjectName, float Width, float Height) { }
    public void OnSetSize(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetSize result={Result} scene={SceneName} object={ObjectName}");
        _sizeTask.TrySetResult(Result);
    }
    public void OnSetCounterNumberKey(eKResult Result, string SceneName, string ObjectName)
    {
        if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT)
        {
            Console.WriteLine($"[SDK] OnSetCounterNumberKey result={Result} scene={SceneName} object={ObjectName}");
        }

        _counterNumberKeyTask.TrySetResult(Result);
    }
    public void OnSetPositionKey(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetPositionKey result={Result} scene={SceneName} object={ObjectName}");
        _positionKeyTask.TrySetResult(Result);
    }
    public void OnSetRotationKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetScaleKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCylinderAngleKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetSphereAngleKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCircleAngleKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCropKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCountDown(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetPosition(eKResult Result, string SceneName, string ObjectName)
    {
        Console.WriteLine($"[SDK] OnSetPosition result={Result} scene={SceneName} object={ObjectName}");
        _positionTask.TrySetResult(Result);
    }
    public void OnSetRotation(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetScale(eKResult Result, string SceneName, string ObjectName) { }
    public void OnAddPathPoint(eKResult Result, string SceneName, string ObjectName)
    {
        _pathPointTask.TrySetResult(Result);
    }
    public void OnClearPathPoints(eKResult Result, string SceneName, string ObjectName)
    {
        _pathPointTask.TrySetResult(Result);
    }
    public void OnAddPathShapePoint(eKResult Result, string SceneName, string ObjectName) { }
    public void OnClearPathShapePoints(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryScrollRemainingDistance(eKResult Result, string SceneName, string ObjectName, int ScrollRemainingDistance) { }
    public void OnQueryScrollChildRemainingDistance(eKResult Result, string SceneName, string ObjectName, string ChildName, int ScrollRemainingDistance) { }
    public void OnAddScrollObject(eKResult Result, string SceneName, string ObjectName) { }
    public void OnAdjustScrollSpeed(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetScrollSpeed(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetLoftPositionKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetChangeOut(eKResult Result, string SceneName) { }
    public void OnModifyPathPoint(eKResult Result, string SceneName, string ObjectName)
    {
        _pathPointTask.TrySetResult(Result);
    }
    public void OnInitScrollObject(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCounterInfo(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCounterNumber(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCounterRange(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCounterRemainingTime(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetCounterElapsedTime(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSaveObjectImage(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetPositionOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetPositionKeyOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetStartFrame(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetObjectEffectType(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetObjectOutEffectDelay(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetColorKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetEmissiveColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetEmissiveColorKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetTransparencyOpacityKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetExposure(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetExposureKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureType(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureFile(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureOffset(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureOffsetKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureTiling(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureTilingKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureRotation(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureRotationKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureOpacity(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureOpacityKey(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryGroupType(eKResult Result, string SceneName, string ObjectName, eKGroupType GroupType) { }
    public void OnQueryImageType(eKResult Result, string SceneName, string ObjectName, eKImageType ImageType) { }
    public void OnSetVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo pVideoPlayInfo) { }
    public void OnQueryIs3D(eKResult Result, string SceneName, string ObjectName, int b3D) { }
    public void OnQueryPosition(eKResult Result, string SceneName, string ObjectName, float X, float Y, float Z)
    {
        Console.WriteLine($"[SDK] OnQueryPosition result={Result} scene={SceneName} object={ObjectName} pos=({X},{Y},{Z})");
        _queryPositionTask.TrySetResult(new PositionCallbackResult(Result, ObjectName, X, Y, Z, string.Empty));
    }
    public void OnSetImageType(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMemo(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryMemo(eKResult Result, string SceneName, string ObjectName, string Memo) { }
    public void OnQueryFont(eKResult Result, string SceneName, string ObjectName, ref sKFont Param) { }
    public void OnSetImageOriginalSize(eKResult Result, string SceneName, string ObjectName) { }
    public void OnApplyChangeEffectLibrary(eKResult Result, string SceneName) { }
    public void OnApplyObjectLibrary(eKResult Result, string SceneName, string ObjectName) { }
    public void OnApplyTextureEffectLibrary(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetTableValue(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetTableColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryTableValue(eKResult Result, string SceneName, string ObjectName, int Row, int Column, string Value) { }
    public void OnQueryTableValues(eKResult Result, string SceneName, string ObjectName, KATableValues pValues) { }
    public void OnSetPathShapeOutlineThickness(eKResult Result, string SceneName, string ObjectName) { }
    public void OnEnablePathShapeOutline(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetPlaybackCamera(eKResult Result, string SceneName) { }
    public void OnSetMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { }
    public void OnQueryMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo VideoPlayInfo) { }
    public void OnQueryVideoFormat(eKResult Result, ref sKVideoFormat VideoFormat) { }
    public void OnQueryLiveStreamingStatus(eKResult Result, string StreamingURI, eKLiveStreamingStatus Status) { }
    public void OnPreloadLiveStreaming(eKResult Result, string StreamingURI) { }
    public void OnReleaseLiveStreaming(eKResult Result, string StreamingURI) { }
    public void OnUpdateImageResource(eKResult Result) { }
    public void OnQueryLayerCount(eKResult Result, int LayerCount) { }
    public void OnSetLayerViewportRate(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnSetLayerViewportRateEx(eKResult Result, int OutputChannelIndex, int LayerNo) { }
    public void OnSetFitting(eKResult Result, string SceneName) { }
    public void OnSetFittingOffset(eKResult Result, string SceneName) { }
    public void OnSetFittingScale(eKResult Result, string SceneName) { }
    public void OnSetLightColor(eKResult Result, string SceneName, string ObjectName) { }
    public void OnEnableLight(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetDirectionalLight(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetPointLight(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetSpotLight(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetInfinitePointLight(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetMaterialTextureLiveStreamingURI(eKResult Result, string SceneName, string ObjectName) { }
    public void OnSetBackgroundLiveStreamingURI(eKResult Result, string SceneName) { }
    public void OnLoadProject(eKResult Result, string FilePath, string AliasName) { }
    public void OnNewProject(eKResult Result, string AliasName) { }
    public void OnUnloadAllProject(eKResult Result) { }
    public void OnSaveProject(eKResult Result, string AliasName) { }
    public void OnQuerySceneItemCount(eKResult Result, string AliasName, int SceneItemCount) { }
    public void OnQuerySceneItemInfos(eKResult Result, string AliasName, KASceneItemInfos SceneItemInfos) { }
    public void OnAddSceneItem(eKResult Result, string AliasName, int Index) { }
    public void OnInsertSceneItem(eKResult Result, string AliasName) { }
    public void OnDeleteSceneItem(eKResult Result, string AliasNAme) { }
    public void OnQueryProjectFormat(eKResult Result, ref sKVideoFormat ProjectFormat) { }
    public void OnSetTimecode(eKResult Result, string AliasName) { }
    public void OnSetTimecodeInOut(eKResult Result, string AliasName) { }
    public void OnSetTimecodeTrack(eKResult Result, string AliasName) { }
    public void OnSetTimecodeInOutType(eKResult Result, string AliasName) { }
    public void OnDeleteTimecode(eKResult Result, string AliasName) { }
    public void OnQueryTimecode(eKResult Result, int TrackNo, int In, int Out, int bOnTrack) { }
    public void OnUnloadProject(eKResult Result, string AliasName) { }
    public void OnEnableSyncWithSceneEffect(eKResult Result, string AliasName) { }
    public void OnExportProjectVideo(eKResult Result, string AliasName) { }
    public void OnExportSceneImage(eKResult Result, string SceneName) { }
    public void OnStartVideoCapture(eKResult Result) { }
    public void OnStopVideoCapture(eKResult Result) { }
    public void OnCaptureImage(eKResult Result) { }
}

[StructLayout(LayoutKind.Sequential)]
internal struct NativeMessage
{
    public IntPtr hwnd;
    public uint message;
    public UIntPtr wParam;
    public IntPtr lParam;
    public uint time;
    public NativePoint pt;
    public uint lPrivate;
}

[StructLayout(LayoutKind.Sequential)]
internal struct NativePoint
{
    public int x;
    public int y;
}

internal static class User32
{
    [DllImport("user32.dll")]
    internal static extern bool PeekMessage(out NativeMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);

    [DllImport("user32.dll")]
    internal static extern bool TranslateMessage([In] ref NativeMessage lpMsg);

    [DllImport("user32.dll")]
    internal static extern IntPtr DispatchMessage([In] ref NativeMessage lpMsg);
}
