using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using KAsyncEngineLib;

namespace Tornado3_2026Election.Services;

public class KarismaEventHandler : KAEventHandler
{
    private readonly LogService _logService;
    private readonly Action<int>? _onConnect;
    private readonly Action<int>? _onClose;
    private readonly object _connectSync = new();
    private readonly object _loadSceneSync = new();
    private readonly object _endTransactionSync = new();
    private readonly object _updateTexturesSync = new();
    private readonly object _scenePrepareSync = new();
    private readonly object _saveSceneImageSync = new();
    private readonly object _saveMixedPreviewImageSync = new();
    private TaskCompletionSource<int>? _pendingConnect;
    private readonly Dictionary<string, TaskCompletionSource<eKResult>> _pendingLoadScenes = new(StringComparer.OrdinalIgnoreCase);
    private TaskCompletionSource<eKResult>? _pendingEndTransaction;
    private readonly Dictionary<string, TaskCompletionSource<eKResult>> _pendingUpdateTextures = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<(int OutputChannelIndex, int LayerNo), TaskCompletionSource<eKResult>> _pendingScenePrepares = new();
    private TaskCompletionSource<(eKResult Result, string SceneName)>? _pendingSaveSceneImage;
    private TaskCompletionSource<(eKResult Result, int OutputChannelIndex, int LayerNo)>? _pendingSaveMixedPreviewImage;

    public KarismaEventHandler(LogService logService, Action<int>? onConnect = null, Action<int>? onClose = null)
    {
        _logService = logService;
        _onConnect = onConnect;
        _onClose = onClose;
    }

    private void LogResult(string callbackName, eKResult result, string? details = null)
    {
        var message = string.IsNullOrWhiteSpace(details)
            ? $"CG callback {callbackName}: {result} ({(int)result})"
            : $"CG callback {callbackName}: {result} ({(int)result}) / {details}";

        if (result == eKResult.RESULT_SUCCESS)
        {
            _logService.Info(message);
        }
        else
        {
            _logService.Warning(message);
        }
    }

    private void LogEvent(string eventName, string? details = null)
    {
        var message = string.IsNullOrWhiteSpace(details)
            ? $"CG event {eventName}"
            : $"CG event {eventName}: {details}";
        _logService.Info(message);
    }

    public Task<int> BeginConnectWait()
    {
        lock (_connectSync)
        {
            _pendingConnect ??= new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
            return _pendingConnect.Task;
        }
    }

    public void CancelPendingConnect(Exception? error = null)
    {
        TaskCompletionSource<int>? completion;
        lock (_connectSync)
        {
            completion = _pendingConnect;
            _pendingConnect = null;
        }

        if (completion is null)
        {
            return;
        }

        if (error is null)
        {
            completion.TrySetCanceled();
            return;
        }

        completion.TrySetException(error);
    }

    public Task<eKResult> BeginLoadSceneWait(string sceneName)
    {
        if (string.IsNullOrWhiteSpace(sceneName))
        {
            throw new ArgumentException("Scene name is required.", nameof(sceneName));
        }

        lock (_loadSceneSync)
        {
            if (_pendingLoadScenes.ContainsKey(sceneName))
            {
                throw new InvalidOperationException($"Another LoadScene request is already pending for '{sceneName}'.");
            }

            var completion = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);
            _pendingLoadScenes[sceneName] = completion;
            return completion.Task;
        }
    }

    public void CancelPendingLoadScene(string sceneName, Exception? error = null)
    {
        if (string.IsNullOrWhiteSpace(sceneName))
        {
            return;
        }

        TaskCompletionSource<eKResult>? completion;
        lock (_loadSceneSync)
        {
            if (!_pendingLoadScenes.TryGetValue(sceneName, out completion))
            {
                return;
            }

            _pendingLoadScenes.Remove(sceneName);
        }

        if (error is null)
        {
            completion.TrySetCanceled();
            return;
        }

        completion.TrySetException(error);
    }

    public void CancelAllPendingLoadScenes(Exception? error = null)
    {
        TaskCompletionSource<eKResult>[] completions;
        lock (_loadSceneSync)
        {
            completions = [.. _pendingLoadScenes.Values];
            _pendingLoadScenes.Clear();
        }

        foreach (var completion in completions)
        {
            if (error is null)
            {
                completion.TrySetCanceled();
                continue;
            }

            completion.TrySetException(error);
        }
    }

    public Task<eKResult> BeginEndTransactionWait()
    {
        lock (_endTransactionSync)
        {
            if (_pendingEndTransaction is not null)
            {
                throw new InvalidOperationException("Another EndTransaction request is already pending.");
            }

            _pendingEndTransaction = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);
            return _pendingEndTransaction.Task;
        }
    }

    public void CancelPendingEndTransaction(Exception? error = null)
    {
        TaskCompletionSource<eKResult>? completion;
        lock (_endTransactionSync)
        {
            completion = _pendingEndTransaction;
            _pendingEndTransaction = null;
        }

        CompleteOrCancel(completion, error);
    }

    public Task<eKResult> BeginUpdateTexturesWait(string sceneName)
    {
        if (string.IsNullOrWhiteSpace(sceneName))
        {
            throw new ArgumentException("Scene name is required.", nameof(sceneName));
        }

        lock (_updateTexturesSync)
        {
            if (_pendingUpdateTextures.ContainsKey(sceneName))
            {
                throw new InvalidOperationException($"Another UpdateTextures request is already pending for '{sceneName}'.");
            }

            var completion = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);
            _pendingUpdateTextures[sceneName] = completion;
            return completion.Task;
        }
    }

    public void CancelPendingUpdateTextures(string sceneName, Exception? error = null)
    {
        if (string.IsNullOrWhiteSpace(sceneName))
        {
            return;
        }

        TaskCompletionSource<eKResult>? completion;
        lock (_updateTexturesSync)
        {
            if (!_pendingUpdateTextures.TryGetValue(sceneName, out completion))
            {
                return;
            }

            _pendingUpdateTextures.Remove(sceneName);
        }

        CompleteOrCancel(completion, error);
    }

    public Task<eKResult> BeginScenePrepareWait(int outputChannelIndex, int layerNo)
    {
        lock (_scenePrepareSync)
        {
            var key = (outputChannelIndex, layerNo);
            if (_pendingScenePrepares.ContainsKey(key))
            {
                throw new InvalidOperationException($"Another scene Prepare request is already pending for output={outputChannelIndex} layer={layerNo}.");
            }

            var completion = new TaskCompletionSource<eKResult>(TaskCreationOptions.RunContinuationsAsynchronously);
            _pendingScenePrepares[key] = completion;
            return completion.Task;
        }
    }

    public void CancelPendingScenePrepare(int outputChannelIndex, int layerNo, Exception? error = null)
    {
        TaskCompletionSource<eKResult>? completion;
        lock (_scenePrepareSync)
        {
            var key = (outputChannelIndex, layerNo);
            if (!_pendingScenePrepares.TryGetValue(key, out completion))
            {
                return;
            }

            _pendingScenePrepares.Remove(key);
        }

        CompleteOrCancel(completion, error);
    }

    public Task<(eKResult Result, string SceneName)> BeginSaveSceneImageWait()
    {
        lock (_saveSceneImageSync)
        {
            if (_pendingSaveSceneImage is not null)
            {
                throw new InvalidOperationException("Another SaveSceneImage request is already pending.");
            }

            _pendingSaveSceneImage = new TaskCompletionSource<(eKResult Result, string SceneName)>(TaskCreationOptions.RunContinuationsAsynchronously);
            return _pendingSaveSceneImage.Task;
        }
    }

    public void CancelPendingSaveSceneImage(Exception? error = null)
    {
        TaskCompletionSource<(eKResult Result, string SceneName)>? completion = null;

        lock (_saveSceneImageSync)
        {
            completion = _pendingSaveSceneImage;
            _pendingSaveSceneImage = null;
        }

        if (completion is null)
        {
            return;
        }

        if (error is null)
        {
            completion.TrySetCanceled();
            return;
        }

        completion.TrySetException(error);
    }

    public Task<(eKResult Result, int OutputChannelIndex, int LayerNo)> BeginSaveMixedPreviewImageWait()
    {
        lock (_saveMixedPreviewImageSync)
        {
            if (_pendingSaveMixedPreviewImage is not null)
            {
                throw new InvalidOperationException("Another SaveMixedPreviewImage request is already pending.");
            }

            _pendingSaveMixedPreviewImage = new TaskCompletionSource<(eKResult Result, int OutputChannelIndex, int LayerNo)>(TaskCreationOptions.RunContinuationsAsynchronously);
            return _pendingSaveMixedPreviewImage.Task;
        }
    }

    public void CancelPendingSaveMixedPreviewImage(Exception? error = null)
    {
        TaskCompletionSource<(eKResult Result, int OutputChannelIndex, int LayerNo)>? completion = null;

        lock (_saveMixedPreviewImageSync)
        {
            completion = _pendingSaveMixedPreviewImage;
            _pendingSaveMixedPreviewImage = null;
        }

        if (completion is null)
        {
            return;
        }

        if (error is null)
        {
            completion.TrySetCanceled();
            return;
        }

        completion.TrySetException(error);
    }

	public void OnLoadScene(eKResult Result, string SceneName)
    {
        LogResult(nameof(OnLoadScene), Result, $"scene={SceneName}");
        CompletePendingLoadScene(SceneName, Result);
    }

	public void OnLoadSceneForce(eKResult Result, string SceneName)
    {
        LogResult(nameof(OnLoadSceneForce), Result, $"scene={SceneName}");
        CompletePendingLoadScene(SceneName, Result);
    }
	public void OnLogMessage(string LogMessage) => LogEvent(nameof(OnLogMessage), LogMessage);
	public void OnMessageNo(uint MessageNo) => LogEvent(nameof(OnMessageNo), $"messageNo={MessageNo}");
	public void OnConnect(int ErrorCode) { if (ErrorCode == 0) { _logService.Info("CG callback OnConnect: success (errorCode=0)"); } else { _logService.Error($"CG callback OnConnect: failed (errorCode={ErrorCode})"); } CompletePendingConnect(ErrorCode); _onConnect?.Invoke(ErrorCode); }
	public void OnClose(int ErrorCode) { if (ErrorCode == 0) { _logService.Info("CG callback OnClose: closed cleanly (errorCode=0)"); } else { _logService.Warning($"CG callback OnClose: closed with errorCode={ErrorCode}"); } _onClose?.Invoke(ErrorCode); }
	public void OnBeginTransaction(eKResult Result) => LogResult(nameof(OnBeginTransaction), Result);
	public void OnEndTransaction(eKResult Result)
    {
        LogResult(nameof(OnEndTransaction), Result);

        TaskCompletionSource<eKResult>? completion;
        lock (_endTransactionSync)
        {
            completion = _pendingEndTransaction;
            _pendingEndTransaction = null;
        }

        completion?.TrySetResult(Result);
    }
	public void OnHeartBeat(eKResult Result) => LogResult(nameof(OnHeartBeat), Result);
	virtual public void OnUnloadAll(eKResult Result) { }
	virtual public void OnSetTrialPlayoutMode(eKResult Result) { }
	virtual public void OnCheckVersion(eKResult Result, string ServerVersion, string SDKVersion) { }
	virtual public void OnSetAudioOutput(eKResult Result) { }
	public void OnScenePrepare(eKResult Result, int OutputChannelIndex, int LayerNo)
    {
        LogResult(nameof(OnScenePrepare), Result, $"output={OutputChannelIndex} layer={LayerNo}");
        CompletePendingScenePrepare(OutputChannelIndex, LayerNo, Result);
    }
	public void OnScenePrepareEx(eKResult Result, int OutputChannelIndex, int LayerNo)
    {
        LogResult(nameof(OnScenePrepareEx), Result, $"output={OutputChannelIndex} layer={LayerNo}");
        CompletePendingScenePrepare(OutputChannelIndex, LayerNo, Result);
    }
	public void OnPlay(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPlay), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnPlayOut(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPlayOut), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnStop(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnStop), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnStopAll(eKResult Result) => LogResult(nameof(OnStopAll), Result);
	public void OnPause(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPause), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnResume(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnResume), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnQueryIsOnAir(eKResult Result, int OutputChannelIndex, int LayerNo, int bOnAir) => LogResult(nameof(OnQueryIsOnAir), Result, $"output={OutputChannelIndex} layer={LayerNo} onAir={(bOnAir != 0)}");
	public void OnTrigger(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnTrigger), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnScenePlayingStarted(string SceneName, int OutputChannelIndex, int LayerNo) => LogEvent(nameof(OnScenePlayingStarted), $"scene={SceneName} output={OutputChannelIndex} layer={LayerNo}");
	public void OnScenePlayed(string SceneName, int OutputChannelIndex, int LayerNo) => LogEvent(nameof(OnScenePlayed), $"scene={SceneName} output={OutputChannelIndex} layer={LayerNo}");
	public void OnSceneAnimationPlayed(string SceneName, int OutputChannelIndex, int LayerNo, string AnimationName) => LogEvent(nameof(OnSceneAnimationPlayed), $"scene={SceneName} output={OutputChannelIndex} layer={LayerNo} animation={AnimationName}");
	public void OnScenePaused(string SceneName, int OutputChannelIndex, int LayerNo, int bLastPause) => LogEvent(nameof(OnScenePaused), $"scene={SceneName} output={OutputChannelIndex} layer={LayerNo} lastPause={(bLastPause != 0)}");
	virtual public void OnSceneSaved(eKResult Result, string FileName) { }
	public void OnTriggerObject(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnTriggerObject), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	virtual public void OnResumeBackground(eKResult Result, int OutputChannelIndex, int LayerNo) { }
	public void OnSaveMixedPreviewImage(eKResult Result, int OutputChannelIndex, int LayerNo)
    {
        LogResult(nameof(OnSaveMixedPreviewImage), Result, $"output={OutputChannelIndex} layer={LayerNo}");

        TaskCompletionSource<(eKResult Result, int OutputChannelIndex, int LayerNo)>? completion = null;
        lock (_saveMixedPreviewImageSync)
        {
            completion = _pendingSaveMixedPreviewImage;
            _pendingSaveMixedPreviewImage = null;
        }

        completion?.TrySetResult((Result, OutputChannelIndex, LayerNo));
    }
	public void OnPlayDirect(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPlayDirect), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnCutIn(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnCutIn), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnCutOut(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnCutOut), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnClearNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnClearNextPreview), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnPlayRange(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPlayRange), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	virtual public void OnQueryPlaybackRangeCount(eKResult Result, string SceneName, int PlaybackRangeCount) { }
	virtual public void OnQueryPlaybackRange(eKResult Result, string SceneName, int PlaybackRangeNo, int Start, int End) { }
	virtual public void OnQueryOutputChannelIndex(eKResult Result, string SceneName, int OutputChannelIndex) { }
	public void OnPlayInNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPlayInNextPreview), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	public void OnPlayOutNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) => LogResult(nameof(OnPlayOutNextPreview), Result, $"output={OutputChannelIndex} layer={LayerNo}");
	virtual public void OnSetBackgroundFill(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundTexture(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundVideo(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundLiveIn(eKResult Result, string SceneName) { }
	virtual public void OnUseBackground(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundVideoPlayInfo(eKResult Result, string SceneName) { }
	virtual public void OnQueryBackgroundVideoPlayInfo(eKResult Result, string SceneName, ref sKVideoPlayInfo pVideoPlayInfo) { }
	virtual public void OnSetSceneEffectType(eKResult Result, string SceneName) { }
	public void OnSaveSceneImage(eKResult Result, string SceneName)
    {
        LogResult(nameof(OnSaveSceneImage), Result, $"scene={SceneName}");

        TaskCompletionSource<(eKResult Result, string SceneName)>? completion = null;
        lock (_saveSceneImageSync)
        {
            completion = _pendingSaveSceneImage;
            _pendingSaveSceneImage = null;
        }

        completion?.TrySetResult((Result, SceneName));
    }
	virtual public void OnSaveScene(eKResult Result, string SceneName) { }
	virtual public void OnUnloadScene(eKResult Result, string SceneName) { }
	virtual public void OnReloadScene(eKResult Result, string SceneName) { }
	public void OnUpdateTextures(eKResult Result, string SceneName)
    {
        LogResult(nameof(OnUpdateTextures), Result, $"scene={SceneName}");

        TaskCompletionSource<eKResult>? completion;
        lock (_updateTexturesSync)
        {
            if (!_pendingUpdateTextures.TryGetValue(SceneName, out completion))
            {
                return;
            }

            _pendingUpdateTextures.Remove(SceneName);
        }

        completion.TrySetResult(Result);
    }
	virtual public void OnSetSceneAudioFile(eKResult Result, string SceneName) { }
	virtual public void OnEnableSceneAudio(eKResult Result, string SceneName) { }
	virtual public void OnSetSceneDuration(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundPauseType(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundChangeType(eKResult Result, string SceneName) { }
	virtual public void OnSetBackgroundPauseAtZeroFrameAsStandBy(eKResult Result, string SceneName) { }
	virtual public void OnResetDuration(eKResult Result, string SceneName) { }
	virtual public void OnSetDuration(eKResult Result, string SceneName) { }
	virtual public void OnAddObject(eKResult Result, string SceneName) { }
	virtual public void OnAddCloneObject(eKResult Result, string SceneName) { }
	virtual public void OnUpdateThumbnail(eKResult Result, string SceneName) { }
	virtual public void OnExportVideo(eKResult Result, string SceneName) { }
	virtual public void OnStopVideoExporting(eKResult Result) { }
	virtual public void OnQueryVideoExportingProgress(eKResult Result, string TargetName, int CurrentFrame, int TotalFrame) { }
	virtual public void OnFinishedVideoExporting(eKResult Result, string FileName) { }
	virtual public void OnAddPause(eKResult Result, string SceneName) { }
	virtual public void OnDeletePause(eKResult Result, string SceneName) { }
	virtual public void OnSetPause(eKResult Result, string SceneName) { }
	virtual public void OnSetPauseWithIndex(eKResult Result, string SceneName) { }
	virtual public void OnDeletePauseWithIndex(eKResult Result, string SceneName) { }
	virtual public void OnQueryPauseCount(eKResult Result, string SceneName, int PauseCount) { }
	virtual public void OnQueryObjectInfos(eKResult Result, string SceneName, KAObjectInfos pObjectInfos) { }	
	virtual public void OnQueryAnimationNames(eKResult Result, string SceneName, KAStrings pAnimationNames) { }
	virtual public void OnQueryAnimationCount(eKResult Result, string SceneName, int AnimationCount) { }
	virtual public void OnQueryObjectInfosByScreenPoint(eKResult Result, KAObjectInfos pObjectInfos) { }
	virtual public void OnQuerySceneEffectType(eKResult Result, string SceneName, int bInEffect, eKEffectType EffectType, int Duration) { }
	virtual public void OnQueryDuration(eKResult Result, string SceneName, string AnimationName, int Duration) { }
	virtual public void OnQueryContentsOfTextObjects(eKResult Result, string SceneName, KAStrings pTexts) { }
	public void OnSetStyleColor(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetStyleColor), Result, $"scene={SceneName} object={ObjectName}");
	virtual public void OnSetStyleTexture(eKResult Result, string SceneName, string ObjectName) { }
	public void OnSetFaceTextColor(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetFaceTextColor), Result, $"scene={SceneName} object={ObjectName}");
	public void OnSetEdgeTextColor(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetEdgeTextColor), Result, $"scene={SceneName} object={ObjectName}");
	public void OnSetShadowTextColor(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetShadowTextColor), Result, $"scene={SceneName} object={ObjectName}");
	public void OnSetVisible(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetVisible), Result, $"scene={SceneName} object={ObjectName}");
	public void OnSetValue(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetValue), Result, $"scene={SceneName} object={ObjectName}");
	virtual public void OnAddText(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnStoreTextStyle(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetTextStyle(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnEditText(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetFont(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetTextRange(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnResetTextRange(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryObjectType(eKResult Result, string SceneName, string ObjectName, eKObjectType ObjectType) { }
	virtual public void OnSetChartCSVFile(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetChartCellData(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryChartDataTable(eKResult Result, string SceneName, string ObjectName, KAChartDataTable Table) { }
	virtual public void OnQuerySize(eKResult Result, string SceneName, string ObjectName, float Width, float Height) { }
	virtual public void OnSetSize(eKResult Result, string SceneName, string ObjectName) { }
	public void OnSetCounterNumberKey(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetCounterNumberKey), Result, $"scene={SceneName} object={ObjectName}");
	virtual public void OnSetPositionKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetRotationKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetScaleKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetCylinderAngleKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetSphereAngleKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetCircleAngleKey(eKResult Result, string SceneName, string ObjectName) { }
	public void OnSetCropKey(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetCropKey), Result, $"scene={SceneName} object={ObjectName}");
	virtual public void OnSetCountDown(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetPosition(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetRotation(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetScale(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnAddPathPoint(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnClearPathPoints(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnAddPathShapePoint(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnClearPathShapePoints(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryScrollRemainingDistance(eKResult Result, string SceneName, string ObjectName, int ScrollRemainingDistance) { }
	virtual public void OnQueryScrollChildRemainingDistance(eKResult Result, string SceneName, string ObjectName, string ChildName, int ScrollRemainingDistance) { }
	virtual public void OnAddScrollObject(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnAdjustScrollSpeed(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetScrollSpeed(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetVariableName(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetLoftPositionKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetChangeOut(eKResult Result, string SceneName) { }
	virtual public void OnModifyPathPoint(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnInitScrollObject(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetCounterInfo(eKResult Result, string SceneName, string ObjectName) { }
	public void OnSetCounterNumber(eKResult Result, string SceneName, string ObjectName) => LogResult(nameof(OnSetCounterNumber), Result, $"scene={SceneName} object={ObjectName}");
	virtual public void OnSetCounterRange(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetCounterRemainingTime(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetCounterElapsedTime(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSaveObjectImage(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetPositionOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetPositionKeyOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetStartFrame(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetObjectEffectType(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetObjectOutEffectDelay(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetColor(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetColorKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetEmissiveColor(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetEmissiveColorKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetTransparencyOpacity(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetTransparencyOpacityKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetExposure(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetExposureKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureType(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureFile(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureOffset(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureOffsetKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureTiling(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureTilingKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureRotation(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureRotationKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureOpacity(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureOpacityKey(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryGroupType(eKResult Result, string SceneName, string ObjectName, eKGroupType GroupType) { }
	virtual public void OnQueryImageType(eKResult Result, string SceneName, string ObjectName, eKImageType ImageType) { }
	virtual public void OnSetVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo pVideoPlayInfo) { }
	virtual public void OnQueryIs3D(eKResult Result, string SceneName, string ObjectName, int b3D) { }
	virtual public void OnQueryPosition(eKResult Result, string SceneName, string ObjectName, float X, float Y, float Z) { }
	virtual public void OnSetImageType(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMemo(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryMemo(eKResult Result, string SceneName, string ObjectName, string Memo) { }
	virtual public void OnQueryFont(eKResult Result, string SceneName, string ObjectName, ref sKFont Param) { }
	virtual public void OnSetImageOriginalSize(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnApplyChangeEffectLibrary(eKResult Result, string SceneName) { }
	virtual public void OnApplyObjectLibrary(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnApplyTextureEffectLibrary(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetTableValue(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetTableColor(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryTableValue(eKResult Result, string SceneName, string ObjectName, int Row, int Column, string Value) { }
	virtual public void OnQueryTableValues(eKResult Result, string SceneName, string ObjectName, KATableValues pValues) { }
	virtual public void OnSetPathShapeOutlineThickness(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnEnablePathShapeOutline(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetPlaybackCamera(eKResult Result, string SceneName) { }
	virtual public void OnSetMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnQueryMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo VideoPlayInfo) { }
	virtual public void OnQueryVideoFormat(eKResult Result, ref sKVideoFormat VideoFormat) { }
	virtual public void OnQueryLiveStreamingStatus(eKResult Result, string StreamingURI, eKLiveStreamingStatus Status) { }
	virtual public void OnPreloadLiveStreaming(eKResult Result, string StreamingURI) { }
	virtual public void OnReleaseLiveStreaming(eKResult Result, string StreamingURI) { }
	public void OnUpdateImageResource(eKResult Result) => LogResult(nameof(OnUpdateImageResource), Result);
	public void OnQueryLayerCount(eKResult Result, int LayerCount) => LogResult(nameof(OnQueryLayerCount), Result, $"layerCount={LayerCount}");
	virtual public void OnSetLayerViewportRate(eKResult Result, int OutputChannelIndex, int LayerNo) { }
	virtual public void OnSetLayerViewportRateEx(eKResult Result, int OutputChannelIndex, int LayerNo) { }
	virtual public void OnSetFitting(eKResult Result, string SceneName) { }
	virtual public void OnSetFittingOffset(eKResult Result, string SceneName) { }
	virtual public void OnSetFittingScale(eKResult Result, string SceneName) { }
	virtual public void OnSetLightColor(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnEnableLight(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetDirectionalLight(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetPointLight(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetSpotLight(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetInfinitePointLight(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetMaterialTextureLiveStreamingURI(eKResult Result, string SceneName, string ObjectName) { }
	virtual public void OnSetBackgroundLiveStreamingURI(eKResult Result, string SceneName) { }
	virtual public void OnLoadProject(eKResult Result, string FilePath, string AliasName) { }
	virtual public void OnNewProject(eKResult Result, string AliasName) { }
	virtual public void OnUnloadAllProject(eKResult Result) { }
	virtual public void OnSaveProject(eKResult Result, string AliasName) { }
	virtual public void OnQuerySceneItemCount(eKResult Result, string AliasName, int SceneItemCount) { }
	virtual public void OnQuerySceneItemInfos(eKResult Result, string AliasName, KASceneItemInfos SceneItemInfos) { }
	virtual public void OnAddSceneItem(eKResult Result, string AliasName, int Index) { }
	virtual public void OnInsertSceneItem(eKResult Result, string AliasName) { }
	virtual public void OnDeleteSceneItem(eKResult Result, string AliasNAme) { }
	virtual public void OnQueryProjectFormat(eKResult Result, ref sKVideoFormat ProjectFormat) { }
	virtual public void OnSetTimecode(eKResult Result, string AliasName) { }
	virtual public void OnSetTimecodeInOut(eKResult Result, string AliasName) { }
	virtual public void OnSetTimecodeTrack(eKResult Result, string AliasName) { }
	virtual public void OnSetTimecodeInOutType(eKResult Result, string AliasName) { }
	virtual public void OnDeleteTimecode(eKResult Result, string AliasName) { }
	virtual public void OnQueryTimecode(eKResult Result, int TrackNo, int In, int Out, int bOnTrack) { }
	virtual public void OnUnloadProject(eKResult Result, string AliasName) { }
	virtual public void OnEnableSyncWithSceneEffect(eKResult Result, string AliasName) { }
	virtual public void OnExportProjectVideo(eKResult Result, string AliasName) { }
	virtual public void OnExportSceneImage(eKResult Result, string SceneName) { }
	virtual public void OnStartVideoCapture(eKResult Result) { }
	virtual public void OnStopVideoCapture(eKResult Result) { }
	virtual public void OnCaptureImage(eKResult Result) { }

    private void CompletePendingLoadScene(string sceneName, eKResult result)
    {
        if (string.IsNullOrWhiteSpace(sceneName))
        {
            return;
        }

        TaskCompletionSource<eKResult>? completion;
        lock (_loadSceneSync)
        {
            if (!_pendingLoadScenes.TryGetValue(sceneName, out completion))
            {
                return;
            }

            _pendingLoadScenes.Remove(sceneName);
        }

        completion.TrySetResult(result);
    }

    private void CompletePendingScenePrepare(int outputChannelIndex, int layerNo, eKResult result)
    {
        TaskCompletionSource<eKResult>? completion;
        lock (_scenePrepareSync)
        {
            var key = (outputChannelIndex, layerNo);
            if (!_pendingScenePrepares.TryGetValue(key, out completion))
            {
                return;
            }

            _pendingScenePrepares.Remove(key);
        }

        completion.TrySetResult(result);
    }

    private void CompletePendingConnect(int errorCode)
    {
        TaskCompletionSource<int>? completion;
        lock (_connectSync)
        {
            completion = _pendingConnect;
            _pendingConnect = null;
        }

        completion?.TrySetResult(errorCode);
    }

    private static void CancelPendingSceneOperation(
        object syncRoot,
        Dictionary<string, TaskCompletionSource<eKResult>> pendingOperations,
        string sceneName,
        Exception? error)
    {
        if (string.IsNullOrWhiteSpace(sceneName))
        {
            return;
        }

        TaskCompletionSource<eKResult>? completion;
        lock (syncRoot)
        {
            if (!pendingOperations.TryGetValue(sceneName, out completion))
            {
                return;
            }

            pendingOperations.Remove(sceneName);
        }

        CompleteOrCancel(completion, error);
    }

    private static void CompletePendingSceneOperation(
        object syncRoot,
        Dictionary<string, TaskCompletionSource<eKResult>> pendingOperations,
        string sceneName,
        eKResult result)
    {
        TaskCompletionSource<eKResult>? completion = null;
        lock (syncRoot)
        {
            if (!string.IsNullOrWhiteSpace(sceneName) &&
                pendingOperations.TryGetValue(sceneName, out completion))
            {
                pendingOperations.Remove(sceneName);
            }
            else if (pendingOperations.Count == 1)
            {
                string? keyToRemove = null;
                foreach (var pair in pendingOperations)
                {
                    keyToRemove = pair.Key;
                    completion = pair.Value;
                    break;
                }

                if (!string.IsNullOrWhiteSpace(keyToRemove))
                {
                    pendingOperations.Remove(keyToRemove);
                }
            }
        }

        completion?.TrySetResult(result);
    }

    private static void CompleteOrCancel<TResult>(TaskCompletionSource<TResult>? completion, Exception? error)
    {
        if (completion is null)
        {
            return;
        }

        if (error is null)
        {
            completion.TrySetCanceled();
            return;
        }

        completion.TrySetException(error);
    }
}
