#import "screen_capture_kit.h"
#import "logging.h"
#import "sync_timeline.h"
#import "mic_filter.h"
#import "recording_writer_safety.h"
#include <atomic>
#import <AVFoundation/AVFoundation.h>
#import <CoreVideo/CoreVideo.h>
#import <CoreMedia/CoreMedia.h>
#import <AudioToolbox/AudioToolbox.h>

// MULTI-SESSION RECORDING: Session-based state management
@interface RecordingSession : NSObject
@property (nonatomic, strong) NSString *sessionId;
@property (nonatomic, strong) SCStream *stream API_AVAILABLE(macos(12.3));
@property (nonatomic, strong) id<SCStreamDelegate> streamDelegate API_AVAILABLE(macos(12.3));
@property (nonatomic, assign) BOOL isRecording;
@property (nonatomic, assign) BOOL isCleaningUp;
@property (nonatomic, assign) BOOL isScheduling;
@property (nonatomic, strong) NSString *outputPath;

// Frame tracking
@property (nonatomic, assign) BOOL firstFrameReceived;
@property (nonatomic, assign) NSInteger frameCountSinceStart;

// Queues and outputs
@property (nonatomic, strong) dispatch_queue_t videoQueue;
@property (nonatomic, strong) dispatch_queue_t audioQueue;
@property (nonatomic, strong) id videoStreamOutput;
@property (nonatomic, strong) id audioStreamOutput;

// Video writer state
@property (nonatomic, strong) AVAssetWriter *videoWriter;
@property (nonatomic, strong) AVAssetWriterInput *videoInput;
@property (nonatomic, assign) CFTypeRef pixelBufferAdaptorRef;
@property (nonatomic, assign) CMTime videoStartTime;
@property (nonatomic, assign) BOOL videoWriterStarted;

// Audio state
@property (nonatomic, assign) BOOL shouldCaptureAudio;
@property (nonatomic, strong) NSString *audioOutputPath;
@property (nonatomic, strong) AVAssetWriter *audioWriter;
@property (nonatomic, strong) AVAssetWriterInput *systemAudioInput;
@property (nonatomic, strong) AVAssetWriterInput *microphoneAudioInput;
@property (nonatomic, assign) CMTime audioStartTime;
@property (nonatomic, assign) BOOL audioWriterStarted;
@property (nonatomic, assign) BOOL captureMicrophoneEnabled;
@property (nonatomic, assign) BOOL captureSystemAudioEnabled;
@property (nonatomic, assign) BOOL mixAudioEnabled;
@property (nonatomic, assign) float mixMicGain;
@property (nonatomic, assign) float mixSystemGain;

// Configuration
@property (nonatomic, assign) NSInteger configuredSampleRate;
@property (nonatomic, assign) NSInteger configuredChannelCount;
@property (nonatomic, assign) NSInteger targetFPS;

// Frame rate debugging
@property (nonatomic, assign) NSInteger frameCount;
@property (nonatomic, assign) CFAbsoluteTime firstFrameTime;

- (instancetype)initWithSessionId:(NSString *)sessionId;
- (void)cleanup;
@end

@implementation RecordingSession

- (instancetype)initWithSessionId:(NSString *)sessionId {
    self = [super init];
    if (self) {
        _sessionId = sessionId;
        _isRecording = NO;
        _isCleaningUp = NO;
        _isScheduling = NO;
        _firstFrameReceived = NO;
        _frameCountSinceStart = 0;
        _videoStartTime = kCMTimeInvalid;
        _videoWriterStarted = NO;
        _audioStartTime = kCMTimeInvalid;
        _audioWriterStarted = NO;
        _shouldCaptureAudio = NO;
        _captureMicrophoneEnabled = NO;
        _captureSystemAudioEnabled = NO;
        _mixAudioEnabled = YES;
        _mixMicGain = 0.8f;
        _mixSystemGain = 0.4f;
        _configuredSampleRate = 48000;
        _configuredChannelCount = 2;
        _targetFPS = 60;
        _frameCount = 0;
        _firstFrameTime = 0;
        _pixelBufferAdaptorRef = NULL;
    }
    return self;
}

- (void)cleanup {
    MRLog(@"🧹 Cleaning up session: %@", _sessionId);

    if (_pixelBufferAdaptorRef) {
        CFRelease(_pixelBufferAdaptorRef);
        _pixelBufferAdaptorRef = NULL;
    }

    _stream = nil;
    _streamDelegate = nil;
    _videoWriter = nil;
    _videoInput = nil;
    _audioWriter = nil;
    _systemAudioInput = nil;
    _microphoneAudioInput = nil;
    _videoStreamOutput = nil;
    _audioStreamOutput = nil;
    _videoQueue = nil;
    _audioQueue = nil;
    _outputPath = nil;
    _audioOutputPath = nil;
    _isRecording = NO;
    _isCleaningUp = NO;
    _isScheduling = NO;
    _firstFrameReceived = NO;
    _frameCountSinceStart = 0;
}

- (void)dealloc {
    [self cleanup];
}

@end

// Session registry - thread-safe access
static NSMutableDictionary<NSString *, RecordingSession *> *g_sessions = nil;
static dispatch_queue_t g_sessionsQueue = nil;

// Legacy global state for backward compatibility (points to first/default session)
static SCStream * API_AVAILABLE(macos(12.3)) g_stream = nil;
static id<SCStreamDelegate> API_AVAILABLE(macos(12.3)) g_streamDelegate = nil;
static std::atomic<bool> g_isRecording{false};
static std::atomic<bool> g_isCleaningUp{false};
static std::atomic<bool> g_isScheduling{false};
static uint64_t g_schedulingGeneration = 0;
static NSString *g_outputPath = nil;
static BOOL g_firstFrameReceived = NO;
static NSInteger g_frameCountSinceStart = 0;
static dispatch_queue_t g_videoQueue = nil;
static dispatch_queue_t g_audioQueue = nil;
static id g_videoStreamOutput = nil;
static id g_audioStreamOutput = nil;
static AVAssetWriter *g_videoWriter = nil;
static AVAssetWriterInput *g_videoInput = nil;
static CFTypeRef g_pixelBufferAdaptorRef = NULL;
static CMTime g_videoStartTime = kCMTimeInvalid;
static BOOL g_videoWriterStarted = NO;
// Late-start padding: a source whose first sample comes after the shared t=0
// fills the gap (first frame repeated / silence) so every file stays aligned.
static BOOL g_videoPrefixWritten = NO;
static BOOL g_micPrefixWritten = NO;
static BOOL g_systemPrefixWritten = NO;
static BOOL g_shouldCaptureAudio = NO;
static NSString *g_audioOutputPath = nil;
static AVAssetWriter *g_audioWriter = nil;
static AVAssetWriterInput *g_systemAudioInput = nil;
static AVAssetWriterInput *g_microphoneAudioInput = nil;
static CMTime g_audioStartTime = kCMTimeInvalid;
static BOOL g_audioWriterStarted = NO;
static BOOL g_captureMicrophoneEnabled = NO;
static BOOL g_captureSystemAudioEnabled = NO;
static BOOL g_captureCameraEnabled = NO;
static BOOL g_mixAudioEnabled = YES;
static float g_mixMicGain = 0.8f;
static float g_mixSystemGain = 0.4f;
static NSInteger g_configuredSampleRate = 48000;
static NSInteger g_configuredChannelCount = 2;
static NSInteger g_targetFPS = 60;
static NSString *g_qualityPreset = @"high";
static NSInteger g_frameCount = 0;
static CFAbsoluteTime g_firstFrameTime = 0;

// Encoder gercek zamanli yetisemezse kare SESSIZCE dusuyordu (readyForMoreMediaData
// NO -> return). Fansiz makinelerde (MacBook Air) 60 FPS Retina yakalamada bu
// gorunmez kalite kaybinin ana kaynagi; sayilmadan teshis edilemiyor.
static NSInteger g_videoFramesAppended = 0;
static NSInteger g_videoFramesDroppedNotReady = 0;
// CleanupWriters sayaclari sifirladigi icin JS tarafi kayit bittikten SONRA
// okuyabilsin diye son oturumun degerleri ayrica saklanir.
static NSInteger g_lastVideoFramesAppended = 0;
static NSInteger g_lastVideoFramesDropped = 0;
static NSInteger g_lastVideoTargetFPS = 0;

// This target uses manual reference counting. Async configuration copies no
// longer leak, so globals must explicitly own paths used after setup returns.
static void SCKSetOwnedString(NSString **slot, NSString *value) {
    @synchronized([ScreenCaptureKitRecorder class]) {
        NSString *owned = [value copy];
        [*slot release];
        *slot = owned;
    }
}

// ---- Prewarm edilmis SCShareableContent onbellegi ----
// Kayit baslatilirken envanter cagrisini tamamen atlayabilmek icin saklanir.
// ARC KAPALI: retain/release elle yonetiliyor.
static id g_cachedShareableContent = nil;
static CFAbsoluteTime g_cachedShareableContentTime = 0;
// Bayat envanter yanlis pencere/ekran secimine yol acabilir; kisa tutuluyor.
static const CFAbsoluteTime kShareableContentCacheTTLSeconds = 30.0;

static void SCKStoreShareableContent(id content) {
    if (!content) return;
    @synchronized([ScreenCaptureKitRecorder class]) {
        if (g_cachedShareableContent != content) {
            [g_cachedShareableContent release];
            g_cachedShareableContent = [content retain];
        }
        g_cachedShareableContentTime = CFAbsoluteTimeGetCurrent();
    }
}

// Taze onbellek varsa doner ve onbellegi bosaltir (her kayit taze envanterle
// baslasin diye tek kullanimlik). Yoksa nil.
static id SCKTakeCachedShareableContent(void) {
    @synchronized([ScreenCaptureKitRecorder class]) {
        if (!g_cachedShareableContent) {
            return nil;
        }

        CFAbsoluteTime age = CFAbsoluteTimeGetCurrent() - g_cachedShareableContentTime;
        id content = [[g_cachedShareableContent retain] autorelease];
        [g_cachedShareableContent release];
        g_cachedShareableContent = nil;

        if (age > kShareableContentCacheTTLSeconds) {
            return nil;
        }
        return content;
    }
}

// Quality helpers
static NSString *SCKNormalizeQualityPreset(id preset) {
    if (![preset isKindOfClass:[NSString class]]) {
        return @"high";
    }
    NSString *trimmed = [[(NSString *)preset stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString];
    if ([trimmed length] == 0) {
        return @"high";
    }
    if ([trimmed isEqualToString:@"low"] || [trimmed isEqualToString:@"medium"] || [trimmed isEqualToString:@"high"]) {
        return trimmed;
    }
    return @"high";
}

static CGFloat SCKQualityScaleForPreset(NSString *preset) {
    NSString *normalized = SCKNormalizeQualityPreset(preset);
    if ([normalized isEqualToString:@"low"]) {
        return 0.5;
    }
    if ([normalized isEqualToString:@"medium"]) {
        return 0.75;
    }
    return 1.0; // High = full resolution
}

// SCDisplay.width/height ve CGDisplayPixelsWide/High ölçekli Retina modlarında
// mantıksal (Quartz) çözünürlüğü döndürebiliyor. Zoom sırasında gerçek piksel
// detayını korumak için aktif display mode'un backing pixel ölçüsünü, global
// point-space frame'e böl. ScreenCaptureKit filter scale'i bazı bağımsız pencere
// filtrelerinde 1.0 raporladığından bu değer güvenilir alt sınırdır.
static CGFloat SCKBackingScaleForDisplay(SCDisplay *display) API_AVAILABLE(macos(12.3)) {
    if (!display) return 1.0;

    CGFloat logicalWidth = display.frame.size.width;
    CGFloat logicalHeight = display.frame.size.height;
    NSInteger backingWidth = 0;
    NSInteger backingHeight = 0;

    CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display.displayID);
    if (mode) {
        backingWidth = (NSInteger)CGDisplayModeGetPixelWidth(mode);
        backingHeight = (NSInteger)CGDisplayModeGetPixelHeight(mode);
        CGDisplayModeRelease(mode);
    }

    // Eski/alışılmadık display mode'larda pixel ölçüsü yoksa Quartz değerine
    // düş; bu en azından non-Retina ekranlarda doğru 1x sonucu verir.
    if (backingWidth <= 0) {
        backingWidth = (NSInteger)CGDisplayPixelsWide(display.displayID);
    }
    if (backingHeight <= 0) {
        backingHeight = (NSInteger)CGDisplayPixelsHigh(display.displayID);
    }

    CGFloat scaleX = (logicalWidth > 0 && backingWidth > 0)
        ? (CGFloat)backingWidth / logicalWidth
        : 1.0;
    CGFloat scaleY = (logicalHeight > 0 && backingHeight > 0)
        ? (CGFloat)backingHeight / logicalHeight
        : 1.0;
    CGFloat scale = MAX(scaleX, scaleY);
    if (!isfinite(scale)) scale = 1.0;
    return MIN(4.0, MAX(1.0, scale));
}

// Bitrate hesabi FPS'e DUYARLI olmali. Eski formul `w*h*multiplier` idi ve fps'i
// hic gormuyordu: kayit 30 -> 60 FPS'e cikarildiginda toplam bitrate ayni kaldigi
// icin kare basina bit yariya dustu, ustelik 180+ Mbps CABAC akisi fansiz bir
// makinede gercek zamanli encoder'i doyurup kare dusurmeye basladi. Artik hedef
// "kare basina piksel basina bit" (bpp) olarak ifade edilir; fps degisince toplam
// bitrate onunla birlikte olceklenir ve kare basina kalite SABIT kalir.
static void SCKQualityBitrateForDimensions(NSString *preset,
                                           NSInteger width,
                                           NSInteger height,
                                           NSInteger fps,
                                           NSInteger *bitrateOut,
                                           double *bppOut,
                                           NSInteger *minOut,
                                           NSInteger *maxOut) {
    NSString *normalized = SCKNormalizeQualityPreset(preset);

    // H.264 High/CABAC 4:2:0 ekran icerigi icin 0.28 bpp zaten gorsel olarak
    // doygunluk bolgesidir; asil kalite tavani bitrate degil 4:2:0 kroma alt
    // ornekleme. Eski 0.53 bpp'nin ikinci yarisi kalite getirmiyor, sadece
    // entropy coding + disk yazma yuku olarak realtime encoder'i zorluyordu.
    double bpp = 0.28;
    NSInteger minBitrate = 40 * 1000 * 1000;
    NSInteger maxBitrate = 200 * 1000 * 1000;

    if ([normalized isEqualToString:@"low"]) {
        bpp = 0.09;
        minBitrate = 10 * 1000 * 1000;
        maxBitrate = 45 * 1000 * 1000;
    } else if ([normalized isEqualToString:@"medium"]) {
        bpp = 0.16;
        minBitrate = 20 * 1000 * 1000;
        maxBitrate = 90 * 1000 * 1000;
    }

    double base = ((double)MAX(1, width)) * ((double)MAX(1, height)) *
                  ((double)MAX(1, fps)) * bpp;
    NSInteger bitrate = (NSInteger)base;
    if (bitrate < minBitrate) bitrate = minBitrate;
    if (bitrate > maxBitrate) bitrate = maxBitrate;

    if (bitrateOut) *bitrateOut = bitrate;
    if (bppOut) *bppOut = bpp;
    if (minOut) *minOut = minBitrate;
    if (maxOut) *maxOut = maxBitrate;
}

static dispatch_queue_t ScreenCaptureControlQueue(void);
static void SCKMarkSchedulingComplete(void);
static void SCKFailScheduling(void);
static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *content, uint64_t generation) API_AVAILABLE(macos(12.3));
static void SCKCompleteStop(void);
static void SCKRequestStop(SCStream *expectedStream) API_AVAILABLE(macos(12.3));

static void CleanupWriters(void);

// SESSION MANAGEMENT FUNCTIONS
static void InitializeSessionRegistry(void) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g_sessions = [[NSMutableDictionary alloc] init];
        g_sessionsQueue = dispatch_queue_create("com.macrecorder.sessions", DISPATCH_QUEUE_CONCURRENT);
        MRLog(@"📦 Session registry initialized");
    });
}

static NSString *GenerateSessionId(void) {
    return [NSString stringWithFormat:@"rec_%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)];
}

static RecordingSession * _Nullable GetSession(NSString *sessionId) {
    if (!sessionId) return nil;
    InitializeSessionRegistry();

    __block RecordingSession *session = nil;
    dispatch_sync(g_sessionsQueue, ^{
        session = g_sessions[sessionId];
    });
    return session;
}

static NSString *CreateSession(void) {
    InitializeSessionRegistry();

    NSString *sessionId = GenerateSessionId();
    RecordingSession *session = [[RecordingSession alloc] initWithSessionId:sessionId];

    dispatch_barrier_async(g_sessionsQueue, ^{
        g_sessions[sessionId] = session;
        MRLog(@"➕ Session created: %@", sessionId);
    });

    return sessionId;
}

static void RemoveSession(NSString *sessionId) {
    if (!sessionId) return;
    InitializeSessionRegistry();

    dispatch_barrier_async(g_sessionsQueue, ^{
        RecordingSession *session = g_sessions[sessionId];
        if (session) {
            [session cleanup];
            [g_sessions removeObjectForKey:sessionId];
            MRLog(@"➖ Session removed: %@", sessionId);
        }
    });
}

static NSArray<NSString *> *GetAllSessionIds(void) {
    InitializeSessionRegistry();

    __block NSArray<NSString *> *sessionIds = nil;
    dispatch_sync(g_sessionsQueue, ^{
        sessionIds = [g_sessions allKeys];
    });
    return sessionIds ?: @[];
}

static NSInteger GetActiveSessionCount(void) {
    InitializeSessionRegistry();

    __block NSInteger count = 0;
    dispatch_sync(g_sessionsQueue, ^{
        count = g_sessions.count;
    });
    return count;
}

static AVAssetWriterInputPixelBufferAdaptor * _Nullable CurrentPixelBufferAdaptor(void) {
    if (!g_pixelBufferAdaptorRef) {
        return nil;
    }
    return (__bridge AVAssetWriterInputPixelBufferAdaptor *)g_pixelBufferAdaptorRef;
}

static NSString *MRNormalizePath(id value) {
    if (!value || value == (id)kCFNull) {
        return nil;
    }
    if ([value isKindOfClass:[NSString class]]) {
        return (NSString *)value;
    }
    if ([value isKindOfClass:[NSURL class]]) {
        return [(NSURL *)value path];
    }
    if ([value isKindOfClass:[NSArray class]]) {
        for (id entry in (NSArray *)value) {
            NSString *candidate = MRNormalizePath(entry);
            if (candidate.length > 0) {
                return candidate;
            }
        }
    }
    return nil;
}

static void FinishWriter(AVAssetWriter *writer, AVAssetWriterInput *input) {
    MRFinishAssetWriterSafely(writer, 5.0);
}

static void CleanupWriters(void) {
    if (g_videoWriter) {
        FinishWriter(g_videoWriter, g_videoInput);
        [g_videoWriter release];
        g_videoWriter = nil;
        g_videoInput = nil;
        if (g_pixelBufferAdaptorRef) {
            CFRelease(g_pixelBufferAdaptorRef);
            g_pixelBufferAdaptorRef = NULL;
        }
        g_videoWriterStarted = NO;
        g_videoStartTime = kCMTimeInvalid;
        g_videoPrefixWritten = NO;

        // Kayit sonu ozeti: kare dusme orani gorunur kalsin, aksi halde "video
        // kalitesiz" sikayeti olcusuz kaliyor.
        NSInteger totalSeen = g_videoFramesAppended + g_videoFramesDroppedNotReady;
        if (totalSeen > 0) {
            g_lastVideoFramesAppended = g_videoFramesAppended;
            g_lastVideoFramesDropped = g_videoFramesDroppedNotReady;
            g_lastVideoTargetFPS = MAX(1, g_targetFPS);
            double dropRatio = 100.0 * g_videoFramesDroppedNotReady / totalSeen;
            MRLog(@"📊 Kayit ozeti: %ld kare yazildi, %ld dusuruldu (%%%.1f) @%ldfps hedef",
                  (long)g_videoFramesAppended,
                  (long)g_videoFramesDroppedNotReady,
                  dropRatio,
                  (long)MAX(1, g_targetFPS));
            if (dropRatio > 2.0) {
                NSLog(@"⚠️ Encoder yuku surdurulebilir degil: kare dusme orani %%%.1f. "
                      @"Cozunurluk/FPS/bitrate kombinasyonu bu makine icin agir.",
                      dropRatio);
            }
        }

        // Reset frame counting
        g_frameCount = 0;
        g_firstFrameTime = 0;
        g_videoFramesAppended = 0;
        g_videoFramesDroppedNotReady = 0;
    }
    
    if (g_audioWriter) {
        FinishWriter(g_audioWriter, nil);
        [g_audioWriter release];
        g_audioWriter = nil;
        // Factory-created inputs are borrowed from the writer, just like the
        // video input. Releasing them again would over-release on stop.
        g_systemAudioInput = nil;
        g_microphoneAudioInput = nil;
        g_audioWriterStarted = NO;
        g_audioStartTime = kCMTimeInvalid;
        g_micPrefixWritten = NO;
        g_systemPrefixWritten = NO;
        g_captureMicrophoneEnabled = NO;
        g_captureSystemAudioEnabled = NO;
    }
}

@interface PureScreenCaptureDelegate : NSObject <SCStreamDelegate>
@end

// External helpers for mixing/muxing
extern "C" NSString *currentStandaloneAudioRecordingPath(void);
extern "C" NSString *lastStandaloneAudioRecordingPath(void);
extern "C" BOOL MRMixAudioToSingleTrack(NSString *primaryAudioPath,
                                         NSString *externalMicPath,
                                         BOOL preferInternalTracks);
extern "C" BOOL MRMixAudioToSingleTrackWithGains(NSString *primaryAudioPath,
                                                  NSString *externalMicPath,
                                                  BOOL preferInternalTracks,
                                                  float micGain,
                                                  float systemGain);
extern "C" BOOL MRMuxAudioIntoVideo(NSString *videoPath, NSString *audioPath);

// Encoder'in gercek zamanli yetisip yetismedigini JS tarafina tasir. Kayit
// suruyorsa canli sayaclar, bittiyse son oturumun degerleri dondurulur.
extern "C" void ScreenCaptureKitGetFrameStats(long *appendedOut,
                                              long *droppedOut,
                                              long *targetFPSOut) {
    BOOL live = (g_videoFramesAppended + g_videoFramesDroppedNotReady) > 0;
    if (appendedOut) {
        *appendedOut = (long)(live ? g_videoFramesAppended : g_lastVideoFramesAppended);
    }
    if (droppedOut) {
        *droppedOut = (long)(live ? g_videoFramesDroppedNotReady : g_lastVideoFramesDropped);
    }
    if (targetFPSOut) {
        *targetFPSOut = (long)(live ? MAX(1, g_targetFPS) : g_lastVideoTargetFPS);
    }
}

extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
    @synchronized([ScreenCaptureKitRecorder class]) {
        // Native stop may clear the owned path on another queue while N-API
        // is converting it to a JS string. Return a caller-owned pool copy.
        return [[MRNormalizePath(g_audioOutputPath) copy] autorelease];
    }
}

// All finalization runs on the control queue. Stop publishing samples before
// draining both queues; only then may the pixel buffer adaptor be released.
static void SCKCompleteStop(void) {
    g_isRecording = NO;
    g_isCleaningUp = YES;
    if (g_videoQueue) dispatch_sync(g_videoQueue, ^{});
    if (g_audioQueue) dispatch_sync(g_audioQueue, ^{});
    @try {
        CleanupWriters();
    } @catch (NSException *exception) {
        NSLog(@"[Recorder] Capture finalization failed safely: %@", exception.reason);
    } @finally {
        [ScreenCaptureKitRecorder cleanupVideoWriter];
        SCKMarkSchedulingComplete();
    }
}

static void SCKRequestStop(SCStream *expectedStream) {
    if ((expectedStream && expectedStream != g_stream) || g_isCleaningUp) return;
    ++g_schedulingGeneration; // invalidate pending inventory/start callbacks
    g_isCleaningUp = YES;
    g_isRecording = NO;
    g_isScheduling = NO;
    SCStream *streamToStop = g_stream;
    if (!streamToStop) {
        SCKCompleteStop();
        return;
    }
    @try {
        [streamToStop stopCaptureWithCompletionHandler:^(NSError *error) {
            dispatch_async(ScreenCaptureControlQueue(), ^{
                if (streamToStop != g_stream) return;
                if (error) NSLog(@"[Recorder] Capture stop error: %@", error);
                SCKCompleteStop();
            });
        }];
    } @catch (NSException *exception) {
        NSLog(@"[Recorder] Capture stop failed safely: %@", exception.reason);
        SCKCompleteStop();
    }
}

@implementation PureScreenCaptureDelegate
- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error API_AVAILABLE(macos(12.3)) {
    dispatch_async(ScreenCaptureControlQueue(), ^{
        if (stream != g_stream || g_isCleaningUp) return;
        NSLog(@"[Recorder] Capture source stopped: %@", error);
        ++g_schedulingGeneration;
        SCKCompleteStop();
    });
}
@end

@interface ScreenCaptureKitRecorder (Private)
+ (BOOL)prepareAudioWriterIfNeededWithSampleBuffer:(CMSampleBufferRef)sampleBuffer
                                     isMicrophone:(BOOL)isMicrophone;
@end

@interface ScreenCaptureVideoOutput : NSObject <SCStreamOutput>
@end

@implementation ScreenCaptureVideoOutput
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type API_AVAILABLE(macos(12.3)) {
    if (g_isCleaningUp || stream != g_stream) return;
    @try {
    if (!g_isRecording || type != SCStreamOutputTypeScreen) {
        return;
    }

    if (MRSyncIsPaused()) {
        return;
    }
    
    if (!CMSampleBufferDataIsReady(sampleBuffer)) {
        return;
    }
    
    if (!g_videoWriter || !g_videoInput) {
        return;
    }
    
    CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);

    // Wait for audio to arrive before starting screen video to prevent leading frames.
    if (MRSyncShouldHoldVideoFrame(presentationTime)) {
        return;
    }
    
    if (!g_videoWriterStarted) {
        if (![g_videoWriter startWriting]) {
            NSLog(@"❌ ScreenCaptureKit video writer failed to start: %@", g_videoWriter.error);
            return;
        }
        [g_videoWriter startSessionAtSourceTime:kCMTimeZero];
        // Shared t=0: if camera/audio already fixed the anchor, the screen
        // joins that timeline (getVideoStartTimestamp and JS stopLimit follow).
        g_videoStartTime = MRSyncWriterStartTime(presentationTime);
        g_videoWriterStarted = YES;
        g_videoPrefixWritten = NO;
        g_frameCountSinceStart = 0;
        MRLog(@"🎞️ Video writer session started @ %.3f (zero-based timeline, first frame +%.1fms)",
              CMTimeGetSeconds(g_videoStartTime),
              CMTimeGetSeconds(CMTimeSubtract(presentationTime, g_videoStartTime)) * 1000.0);
    }

    // ELECTRON FIX: Track frame count to ensure ScreenCaptureKit is fully running
    if (!g_firstFrameReceived) {
        g_frameCountSinceStart++;
        if (g_frameCountSinceStart >= 10) {  // Wait for 10 frames (~150ms at 60fps)
            g_firstFrameReceived = YES;
            MRLog(@"✅ ScreenCaptureKit fully initialized after %ld frames", (long)g_frameCountSinceStart);
        }
    }
    
    if (!g_videoInput.readyForMoreMediaData) {
        // Encoder geride kaldi: bu kare kalici olarak kayboluyor. Sessizce
        // dusurmek kalite sikayetlerini teshis edilemez hale getiriyordu; oran
        // %2'yi asarsa encoder yuku (cozunurluk x fps x bitrate) surdurulebilir
        // degil demektir.
        g_videoFramesDroppedNotReady++;
        NSInteger totalSeen = g_videoFramesAppended + g_videoFramesDroppedNotReady;
        if (g_videoFramesDroppedNotReady == 1 || (g_videoFramesDroppedNotReady % 60) == 0) {
            MRLog(@"⚠️ Encoder yetisemedi, kare dusuruldu: %ld/%ld (%.1f%%)",
                  (long)g_videoFramesDroppedNotReady,
                  (long)totalSeen,
                  totalSeen > 0 ? (100.0 * g_videoFramesDroppedNotReady / totalSeen) : 0.0);
        }
        return;
    }

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    if (!pixelBuffer) {
        return;
    }
    
    AVAssetWriterInputPixelBufferAdaptor *adaptorCandidate = CurrentPixelBufferAdaptor();
    if ([adaptorCandidate isKindOfClass:[NSArray class]]) {
        id first = [(NSArray *)adaptorCandidate firstObject];
        if ([first isKindOfClass:[AVAssetWriterInputPixelBufferAdaptor class]]) {
            adaptorCandidate = first;
            if (g_pixelBufferAdaptorRef) {
                CFRelease(g_pixelBufferAdaptorRef);
            }
            g_pixelBufferAdaptorRef = CFBridgingRetain(adaptorCandidate);
        }
    }
    if (![adaptorCandidate isKindOfClass:[AVAssetWriterInputPixelBufferAdaptor class]]) {
        if (adaptorCandidate) {
            MRLog(@"⚠️ Pixel buffer adaptor invalid (%@) – skipping frame", NSStringFromClass([adaptorCandidate class]));
        }
        NSLog(@"❌ Pixel buffer adaptor is nil – cannot append video frames");
        return;
    }
    
    CMTime relativePresentation = presentationTime;
    if (CMTIME_IS_VALID(g_videoStartTime)) {
        relativePresentation = CMTimeSubtract(presentationTime, g_videoStartTime);
        if (CMTIME_COMPARE_INLINE(relativePresentation, <, kCMTimeZero)) {
            relativePresentation = kCMTimeZero;
        }
    }
    relativePresentation = MRSyncAdjustForPauses(relativePresentation);

    double stopLimit = MRSyncGetStopLimitSeconds();
    if (stopLimit > 0) {
        double frameSeconds = CMTimeGetSeconds(relativePresentation);
        double tolerance = g_targetFPS > 0 ? (1.5 / g_targetFPS) : 0.02;
        if (tolerance < 0.02) {
            tolerance = 0.02;
        }
        if (frameSeconds > stopLimit + tolerance) {
            return;
        }
    }
    
    AVAssetWriterInputPixelBufferAdaptor *adaptor = adaptorCandidate;
    if (!g_videoPrefixWritten) {
        // Screen started after the shared t=0: hold the first frame from 0 so
        // the gap stays in the media (some demuxers drop MOV empty edits).
        if (CMTimeCompare(relativePresentation, kCMTimeZero) > 0 &&
            ![adaptor appendPixelBuffer:pixelBuffer withPresentationTime:kCMTimeZero]) {
            NSLog(@"⚠️ Failed appending screen lead-in frame: %@", g_videoWriter.error);
            return;
        }
        g_videoPrefixWritten = YES;
        if (!g_videoInput.readyForMoreMediaData) {
            return;
        }
    }
    BOOL appended = [adaptor appendPixelBuffer:pixelBuffer withPresentationTime:relativePresentation];
    if (!appended) {
        NSLog(@"⚠️ Failed appending pixel buffer: %@", g_videoWriter.error);
    } else {
        g_videoFramesAppended++;
    }

    // Frame rate debugging
    g_frameCount++;
    if (g_firstFrameTime == 0) {
        g_firstFrameTime = CFAbsoluteTimeGetCurrent();
    }
    if (g_frameCount % 60 == 0) {
        CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - g_firstFrameTime;
        double actualFPS = g_frameCount / elapsed;
        MRLog(@"📊 Frame stats: %ld frames in %.1fs = %.1f FPS", (long)g_frameCount, elapsed, actualFPS);
    }
    } @catch (NSException *exception) {
        NSLog(@"[Recorder] Sample callback failed safely: %@", exception.reason);
        dispatch_async(ScreenCaptureControlQueue(), ^{ SCKRequestStop(stream); });
    }

}
@end

@interface ScreenCaptureAudioOutput : NSObject <SCStreamOutput>
@end

@implementation ScreenCaptureAudioOutput
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type API_AVAILABLE(macos(12.3)) {
    if (g_isCleaningUp || stream != g_stream) return;
    @try {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        MRLog(@"🎤 First audio sample callback received from ScreenCaptureKit");
    });

    if (!g_isRecording || !g_shouldCaptureAudio) {
        return;
    }

    if (MRSyncIsPaused()) {
        return;
    }

    BOOL isMicrophoneSample = NO;
    BOOL isSupportedSample = NO;
    if (@available(macOS 15.0, *)) {
        if (type == SCStreamOutputTypeAudio) {
            isSupportedSample = YES;
        } else if (type == SCStreamOutputTypeMicrophone) {
            isSupportedSample = YES;
            isMicrophoneSample = YES;
        }
    } else if (@available(macOS 13.0, *)) {
        if (type == SCStreamOutputTypeAudio) {
            isSupportedSample = YES;
        }
    }

    if (!isSupportedSample) {
        return;
    }

    BOOL routeToMicrophoneTrack = isMicrophoneSample;
    if (!routeToMicrophoneTrack) {
        if (!g_captureSystemAudioEnabled && g_captureMicrophoneEnabled) {
            // Only microphone requested (e.g., macOS < 15), so treat stream as microphone.
            routeToMicrophoneTrack = YES;
        }
    }

    if (!CMSampleBufferDataIsReady(sampleBuffer)) {
        MRLog(@"⚠️ %@ audio sample buffer not ready",
              routeToMicrophoneTrack ? @"Microphone" : @"System");
        return;
    }

    // Mic filters (gate, expander, EQ, compressor, limiter) run on the mic
    // track only; system audio is written untouched. NULL = filters off.
    MRMicFilterScopedBuffer filteredMicSample(
        routeToMicrophoneTrack ? MRMicFilterCopyProcessedBuffer(sampleBuffer) : NULL);
    if (filteredMicSample.buffer) {
        sampleBuffer = filteredMicSample.buffer;
    }
    
    if (![ScreenCaptureKitRecorder prepareAudioWriterIfNeededWithSampleBuffer:sampleBuffer
                                                                 isMicrophone:routeToMicrophoneTrack]) {
        return;
    }
    
    if (!g_audioWriter) {
        return;
    }

    AVAssetWriterInput *targetInput = routeToMicrophoneTrack ? g_microphoneAudioInput : g_systemAudioInput;
    if (!targetInput) {
        return;
    }
    
    CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);

    // When camera and microphone are both active, lip sync must be anchored to
    // the microphone clock. System audio can arrive first on macOS 15; letting
    // it start the shared writer makes camera alignment depend on callback
    // ordering. Drop only the short system-audio lead-in until mic t=0 exists.
    if (g_captureCameraEnabled &&
        g_captureMicrophoneEnabled &&
        !routeToMicrophoneTrack &&
        CMTIME_IS_INVALID(MRSyncAudioFirstTimestamp())) {
        return;
    }

    // A/V SYNC: Hold audio samples until camera produces first frame
    if (MRSyncShouldHoldAudioSample(presentationTime)) {
        return;
    }

    MRSyncMarkAudioSample(presentationTime);

    if (!g_audioWriterStarted) {
        if (![g_audioWriter startWriting]) {
            NSLog(@"❌ Audio writer failed to start: %@", g_audioWriter.error);
            return;
        }
        [g_audioWriter startSessionAtSourceTime:kCMTimeZero];
        // Shared t=0: when video was released before the (late) mic arrived,
        // audio starts at the video anchor and the gap is filled with silence.
        g_audioStartTime = MRSyncWriterStartTime(presentationTime);
        g_audioWriterStarted = YES;
        g_micPrefixWritten = NO;
        g_systemPrefixWritten = NO;
        MRLog(@"🔊 Audio writer session started @ %.3f (source=%@)",
              CMTimeGetSeconds(presentationTime),
              routeToMicrophoneTrack ? @"microphone" : @"system");
    }
    
    static int systemNotReadyCount = 0;
    static int microphoneNotReadyCount = 0;
    int *notReadyCounter = routeToMicrophoneTrack ? &microphoneNotReadyCount : &systemNotReadyCount;

    if (!targetInput.readyForMoreMediaData) {
        if ((*notReadyCounter)++ % 100 == 0) {
            MRLog(@"⚠️ %@ audio input not ready for data (count: %d)",
                  routeToMicrophoneTrack ? @"Microphone" : @"System",
                  *notReadyCounter);
        }
        return;
    }

    if (CMTIME_IS_INVALID(g_audioStartTime)) {
        g_audioStartTime = presentationTime;
    }

    // Samples captured before t=0 would be clamped onto 0 and overlap the
    // next buffer; drop them instead.
    if (CMTimeCompare(presentationTime, g_audioStartTime) < 0) {
        return;
    }

    // Each track (mic / system) that starts after t=0 gets a silent lead-in,
    // otherwise its first sample would sit at an empty edit or shift to 0.
    BOOL *prefixWritten = routeToMicrophoneTrack ? &g_micPrefixWritten : &g_systemPrefixWritten;
    if (!*prefixWritten) {
        double leadSeconds = CMTimeGetSeconds(
            MRSyncAdjustForPauses(CMTimeSubtract(presentationTime, g_audioStartTime)));
        if (leadSeconds > 0.001) {
            CMSampleBufferRef silence = MRSyncCreateSilentAudio(sampleBuffer, leadSeconds);
            if (silence) {
                BOOL silenceAppended = [targetInput appendSampleBuffer:silence];
                CFRelease(silence);
                if (!silenceAppended) {
                    NSLog(@"⚠️ Failed appending %@ silent lead-in: %@",
                          routeToMicrophoneTrack ? @"microphone" : @"system", g_audioWriter.error);
                    return;
                }
                MRLog(@"🔇 %@ track lead-in: %.0f ms of silence (late start)",
                      routeToMicrophoneTrack ? @"Microphone" : @"System", leadSeconds * 1000.0);
            } else {
                MRLog(@"⚠️ Could not build %.0f ms silent lead-in (non-PCM format?)", leadSeconds * 1000.0);
            }
        }
        *prefixWritten = YES;
        if (!targetInput.readyForMoreMediaData) {
            return;
        }
    }
    
    CMSampleBufferRef bufferToAppend = sampleBuffer;
    CMItemCount timingEntryCount = 0;
    OSStatus timingStatus = CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, 0, NULL, &timingEntryCount);
    CMSampleTimingInfo *timingInfo = NULL;
    
    if (timingStatus == noErr && timingEntryCount > 0) {
        timingInfo = (CMSampleTimingInfo *)malloc(sizeof(CMSampleTimingInfo) * timingEntryCount);
        if (timingInfo) {
            timingStatus = CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, timingEntryCount, timingInfo, &timingEntryCount);
            
            if (timingStatus == noErr) {
                for (CMItemCount i = 0; i < timingEntryCount; ++i) {
                    // Shift ScreenCaptureKit audio to start at t=0 so it aligns with camera/mic tracks
                    if (CMTIME_IS_VALID(timingInfo[i].presentationTimeStamp)) {
                        CMTime adjustedPTS = CMTimeSubtract(timingInfo[i].presentationTimeStamp, g_audioStartTime);
                        if (CMTIME_COMPARE_INLINE(adjustedPTS, <, kCMTimeZero)) {
                            adjustedPTS = kCMTimeZero;
                        }
                        timingInfo[i].presentationTimeStamp = MRSyncAdjustForPauses(adjustedPTS);
                    } else {
                        timingInfo[i].presentationTimeStamp = kCMTimeZero;
                    }
                    
                    if (CMTIME_IS_VALID(timingInfo[i].decodeTimeStamp)) {
                        CMTime adjustedDTS = CMTimeSubtract(timingInfo[i].decodeTimeStamp, g_audioStartTime);
                        if (CMTIME_COMPARE_INLINE(adjustedDTS, <, kCMTimeZero)) {
                            adjustedDTS = kCMTimeZero;
                        }
                        timingInfo[i].decodeTimeStamp = MRSyncAdjustForPauses(adjustedDTS);
                    }
                }
                
                CMSampleBufferRef adjustedBuffer = NULL;
                timingStatus = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault,
                                                                     sampleBuffer,
                                                                     timingEntryCount,
                                                                     timingInfo,
                                                                     &adjustedBuffer);
                if (timingStatus == noErr && adjustedBuffer) {
                    bufferToAppend = adjustedBuffer;
                }
            }
            
            free(timingInfo);
            timingInfo = NULL;
        }
    }
    
    double stopLimit = MRSyncGetStopLimitSeconds();
    if (stopLimit > 0) {
        CMTime sampleStart = CMSampleBufferGetPresentationTimeStamp(bufferToAppend);
        double sampleSeconds = CMTimeGetSeconds(sampleStart);
        double sampleDuration = CMTIME_IS_VALID(CMSampleBufferGetDuration(bufferToAppend))
                              ? CMTimeGetSeconds(CMSampleBufferGetDuration(bufferToAppend))
                              : 0.0;
        double tolerance = 0.02;
        if (sampleSeconds > stopLimit + tolerance ||
            (sampleDuration > 0.0 && (sampleSeconds + sampleDuration) > stopLimit + tolerance)) {
            if (bufferToAppend != sampleBuffer) {
                CFRelease(bufferToAppend);
            }
            return;
        }
    }

    BOOL success = [targetInput appendSampleBuffer:bufferToAppend];
    if (!success) {
        NSLog(@"⚠️ Failed appending audio sample buffer: %@", g_audioWriter.error);
    } else {
        static int systemAppendCount = 0;
        static int microphoneAppendCount = 0;
        int *appendCount = routeToMicrophoneTrack ? &microphoneAppendCount : &systemAppendCount;
        if ((*appendCount)++ % 100 == 0) {
            MRLog(@"✅ %@ audio sample appended (count: %d)",
                  routeToMicrophoneTrack ? @"Microphone" : @"System",
                  *appendCount);
        }
    }
    
    if (bufferToAppend != sampleBuffer) {
        CFRelease(bufferToAppend);
    }
    } @catch (NSException *exception) {
        NSLog(@"[Recorder] Sample callback failed safely: %@", exception.reason);
        dispatch_async(ScreenCaptureControlQueue(), ^{ SCKRequestStop(stream); });
    }

}
@end

@implementation ScreenCaptureKitRecorder

+ (BOOL)prepareVideoWriterWithWidth:(NSInteger)width height:(NSInteger)height error:(NSError **)error {
    MRLog(@"🎬 Preparing video writer %ldx%ld", (long)width, (long)height);
    if (!g_outputPath) {
        MRLog(@"❌ Video writer failed: missing output path");
        return NO;
    }
    if (width <= 0 || height <= 0) {
        MRLog(@"❌ Video writer invalid dimensions %ldx%ld", (long)width, (long)height);
        return NO;
    }
    
    NSURL *outputURL = [NSURL fileURLWithPath:g_outputPath];
    [[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
    
    g_videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:error];
    if (!g_videoWriter || (error && *error)) {
        MRLog(@"❌ Failed creating video writer: %@", error && *error ? (*error).localizedDescription : @"unknown");
        return NO;
    }
    
    NSString *normalizedQuality = SCKNormalizeQualityPreset(g_qualityPreset);
    NSInteger encoderFPS = MAX(1, g_targetFPS);
    NSInteger bitrate = 0;
    double bpp = 0.0;
    NSInteger minBitrate = 0;
    NSInteger maxBitrate = 0;
    SCKQualityBitrateForDimensions(normalizedQuality, width, height, encoderFPS,
                                   &bitrate, &bpp, &minBitrate, &maxBitrate);

    MRLog(@"🎬 Screen encoder (%@): %ldx%ld@%ldfps, codec=H.264 High Profile, bitrate=%.2fMbps (%.2f bpp, min=%ldMbps max=%ldMbps)",
          normalizedQuality,
          (long)width,
          (long)height,
          (long)encoderFPS,
          bitrate / (1000.0 * 1000.0),
          bpp,
          (long)(minBitrate / (1000 * 1000)),
          (long)(maxBitrate / (1000 * 1000)));

    NSDictionary *compressionProps = @{
        AVVideoAverageBitRateKey: @(bitrate),
        AVVideoMaxKeyFrameIntervalKey: @(encoderFPS),
        // B-frame'ler (kare yeniden siralama) gercek zamanli yakalamada encoder'a
        // ek gecikme + is yukleyip readyForMoreMediaData'yi geciktiriyor; ekran
        // icerigi zaten neredeyse tamamen statik/kesme oldugu icin kazanci ihmal
        // edilebilir. Kapatmak fansiz makinelerde kare dusmesini azaltir.
        AVVideoAllowFrameReorderingKey: @NO,
        AVVideoExpectedSourceFrameRateKey: @(encoderFPS),
        // AVVideoQualityKey BILEREK YOK: Apple belgesine gore yalnizca JPEG ve
        // ProRes icin gecerli. H.264'te (avc1) donanim encoder'i onu sessizce
        // yok sayiyordu, ama encoder yazilim yoluna dustugunde AVFoundation
        // dogrulamasi sertlesiyor ve AVAssetWriterInput ISTISNA atiyor:
        // "Compression property Quality is not supported for video codec type
        // avc1". Istisna tum kayit kurulumunu iptal ettigi icin kayit "basladi"
        // gorunup dosya hic olusmuyordu (editorde bos canvas). Kaliteyi zaten
        // AVVideoAverageBitRateKey belirliyor.
        AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
        AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
        AVVideoAverageNonDroppableFrameRateKey: @(encoderFPS),
        AVVideoMaxKeyFrameIntervalDurationKey: @(1.0)
    };

    // Renk etiketleri: kayıt sRGB uzayında yakalanıyor (streamConfig.colorSpaceName),
    // BT.709 olarak etiketle ki Chromium/QuickTime decode'da doğru yorumlasın.
    // Etiket olmadan player'lar tahmin yürütüyor ve renkler soluk/kaymış görünüyordu.
    // Bitrate/boyut maliyeti sıfırdır.
    NSDictionary *colorProps = @{
        AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
        AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,
        AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709_2
    };

    NSDictionary *videoSettings = @{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @(width),
        AVVideoHeightKey: @(height),
        AVVideoColorPropertiesKey: colorProps,
        AVVideoCompressionPropertiesKey: compressionProps
    };
    
    // AVFoundation, avc1 icin hangi sikistirma ozelligini kabul ettigine
    // encoder yoluna gore karar veriyor ve reddettiginde NSError DEGIL ISTISNA
    // atiyor. Istisna tum kayit kurulumunu iptal ettigi icin kayit "basladi"
    // gorunup dosya hic olusmuyor; kullanici ancak editorde bos canvas gorunce
    // anliyor. OLCULDU (macOS 26, 1x harici ekran, iTerm penceresi): once
    // "Compression property Quality is not supported for video codec type avc1",
    // o kaldirilinca "AverageNonDroppableFrameRate is not supported" — yani tek
    // tek anahtar elemek guvenilir degil. Ayni ozelliklerle ayni ekrandaki baska
    // pencereler sorunsuz calisiyor.
    //
    // Bu yuzden ayarlar KADEMELI denenir: once tam set, sonra yalnizca her
    // encoder'in destekledigi cekirdek set, en sonda codec+boyut. Ilk kabul
    // edilen kazanir; kayit hicbir kosulda sessizce bos cikmaz.
    NSMutableArray<NSDictionary *> *settingsTiers = [NSMutableArray array];
    [settingsTiers addObject:videoSettings];
    [settingsTiers addObject:@{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @(width),
        AVVideoHeightKey: @(height),
        AVVideoColorPropertiesKey: colorProps,
        AVVideoCompressionPropertiesKey: @{
            AVVideoAverageBitRateKey: @(bitrate),
            AVVideoMaxKeyFrameIntervalKey: @(encoderFPS),
            AVVideoAllowFrameReorderingKey: @NO,
            AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel
        }
    }];
    [settingsTiers addObject:@{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @(width),
        AVVideoHeightKey: @(height),
        AVVideoCompressionPropertiesKey: @{ AVVideoAverageBitRateKey: @(bitrate) }
    }];
    [settingsTiers addObject:@{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @(width),
        AVVideoHeightKey: @(height)
    }];

    g_videoInput = nil;
    NSString *lastRejection = nil;
    for (NSUInteger tier = 0; tier < settingsTiers.count; tier++) {
        @try {
            g_videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                             outputSettings:settingsTiers[tier]];
        } @catch (NSException *exception) {
            g_videoInput = nil;
            lastRejection = exception.reason;
            NSLog(@"⚠️ Video encoder settings tier %lu rejected: %@", (unsigned long)tier, exception.reason);
            continue;
        }
        if (g_videoInput) {
            if (tier > 0) {
                NSLog(@"⚠️ Video encoder fell back to settings tier %lu (%ldx%ld); last rejection: %@",
                      (unsigned long)tier, (long)width, (long)height, lastRejection);
            }
            break;
        }
    }
    if (!g_videoInput) {
        MRLog(@"❌ Video writer failed: no accepted encoder settings (%@)", lastRejection ?: @"unknown");
        if (error) {
            *error = [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-101 userInfo:@{
                NSLocalizedDescriptionKey: [NSString stringWithFormat:@"No accepted H.264 encoder settings: %@",
                                            lastRejection ?: @"unknown"]
            }];
        }
        return NO;
    }
    g_videoInput.expectsMediaDataInRealTime = YES;
    
    AVAssetWriterInputPixelBufferAdaptor *pixelAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:g_videoInput sourcePixelBufferAttributes:@{
        (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
        (NSString *)kCVPixelBufferWidthKey: @(width),
        (NSString *)kCVPixelBufferHeightKey: @(height),
        (NSString *)kCVPixelBufferCGImageCompatibilityKey: @YES,
        (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
    }];

    if (![g_videoWriter canAddInput:g_videoInput]) {
        MRLog(@"❌ Cannot add video input to writer");
        if (error) {
            *error = [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-100 userInfo:@{NSLocalizedDescriptionKey: @"Cannot add video input to writer"}];
        }
        return NO;
    }
    
    [g_videoWriter addInput:g_videoInput];
    if (g_pixelBufferAdaptorRef) {
        CFRelease(g_pixelBufferAdaptorRef);
        g_pixelBufferAdaptorRef = NULL;
    }
    if (pixelAdaptor) {
        g_pixelBufferAdaptorRef = CFBridgingRetain(pixelAdaptor);
    }
    g_videoWriterStarted = NO;
    g_videoStartTime = kCMTimeInvalid;
    g_videoPrefixWritten = NO;
    MRLog(@"✅ Video writer ready %ldx%ld", (long)width, (long)height);

    return YES;
}

+ (BOOL)prepareAudioWriterIfNeededWithSampleBuffer:(CMSampleBufferRef)sampleBuffer
                                     isMicrophone:(BOOL)isMicrophone {
    if (!g_shouldCaptureAudio || !g_audioOutputPath) {
        return g_audioWriter != nil || !g_shouldCaptureAudio;
    }

    CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
    if (!formatDescription) {
        NSLog(@"⚠️ Missing audio format description");
        return NO;
    }

    const AudioStreamBasicDescription *asbd =
        CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription);
    if (!asbd) {
        NSLog(@"⚠️ Unsupported audio format description");
        return NO;
    }

    if (!g_audioWriter) {
        g_configuredSampleRate = (NSInteger)asbd->mSampleRate;
        g_configuredChannelCount = asbd->mChannelsPerFrame;

        NSString *originalPath = g_audioOutputPath ?: @"";
        NSURL *audioURL = [NSURL fileURLWithPath:originalPath];
        [[NSFileManager defaultManager] removeItemAtURL:audioURL error:nil];

        NSError *writerError = nil;
        AVFileType requestedFileType = AVFileTypeQuickTimeMovie;

        NSString *audioPath = originalPath;
        if (![audioPath.pathExtension.lowercaseString isEqualToString:@"mov"]) {
            MRLog(@"⚠️ Audio path has wrong extension '%@', changing to .mov", audioPath.pathExtension);
            audioPath = [[audioPath stringByDeletingPathExtension] stringByAppendingPathExtension:@"mov"];
            SCKSetOwnedString(&g_audioOutputPath, audioPath);
        }
        audioURL = [NSURL fileURLWithPath:audioPath];
        [[NSFileManager defaultManager] removeItemAtURL:audioURL error:nil];

        @try {
            g_audioWriter = [[AVAssetWriter alloc] initWithURL:audioURL
                                                      fileType:requestedFileType
                                                         error:&writerError];
        } @catch (NSException *exception) {
            NSDictionary *info = @{
                NSLocalizedDescriptionKey: exception.reason ?: @"Failed to initialize audio writer"
            };
            writerError = [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-201 userInfo:info];
            g_audioWriter = nil;
        }

        if (!g_audioWriter || writerError) {
            NSLog(@"❌ Failed to create audio writer: %@", writerError);
            return NO;
        }

        // Reset tracking flags whenever we create a new writer
        g_audioWriterStarted = NO;
        g_audioStartTime = kCMTimeInvalid;
        g_micPrefixWritten = NO;
        g_systemPrefixWritten = NO;

        // CRITICAL FIX: Add BOTH system and microphone inputs NOW (before startWriting)
        // if both are enabled. AVAssetWriter cannot add inputs after startWriting() is called.
        NSLog(@"🎙️ Creating audio writer - system=%d, microphone=%d",
              g_captureSystemAudioEnabled, g_captureMicrophoneEnabled);
    }

    AVAssetWriterInput **targetInput = isMicrophone ? &g_microphoneAudioInput : &g_systemAudioInput;
    if (*targetInput) {
        return YES;
    }

    // If writer was just created and BOTH sources are enabled, create BOTH inputs now
    if (g_audioWriter && g_captureSystemAudioEnabled && g_captureMicrophoneEnabled) {
        if (!g_systemAudioInput && !g_microphoneAudioInput) {
            NSLog(@"🎙️ Both audio sources enabled - creating both inputs from first sample");

            NSUInteger channelCount = MAX((NSUInteger)1, (NSUInteger)asbd->mChannelsPerFrame);
            AudioChannelLayout layout = {0};
            size_t layoutSize = 0;
            if (channelCount == 1) {
                layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
                layoutSize = sizeof(AudioChannelLayout);
            } else if (channelCount == 2) {
                layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
                layoutSize = sizeof(AudioChannelLayout);
            }

            NSMutableDictionary *audioSettings = [@{
                AVFormatIDKey: @(kAudioFormatMPEG4AAC),
                AVSampleRateKey: @(asbd->mSampleRate),
                AVNumberOfChannelsKey: @(channelCount),
                AVEncoderBitRateKey: @(192000)
            } mutableCopy];

            if (layoutSize > 0) {
                audioSettings[AVChannelLayoutKey] = [NSData dataWithBytes:&layout length:layoutSize];
            }

            // ELECTRON FIX: Create microphone input FIRST (stream 0)
            // Electron plays stream 0 by default, so put the more important audio first
            // TODO: Mix both streams into single track for full Electron compatibility
            AVAssetWriterInput *micInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
                                                                              outputSettings:audioSettings];
            micInput.expectsMediaDataInRealTime = YES;
            if ([g_audioWriter canAddInput:micInput]) {
                [g_audioWriter addInput:micInput];
                g_microphoneAudioInput = micInput;
                NSLog(@"✅ Microphone audio input created (stream 0 - Electron default)");
            } else {
                NSLog(@"❌ Cannot add microphone audio input");
            }

            // Create system audio input (stream 1)
            AVAssetWriterInput *systemInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
                                                                                  outputSettings:audioSettings];
            systemInput.expectsMediaDataInRealTime = YES;
            if ([g_audioWriter canAddInput:systemInput]) {
                [g_audioWriter addInput:systemInput];
                g_systemAudioInput = systemInput;
                NSLog(@"✅ System audio input created (stream 1)");
            } else {
                NSLog(@"❌ Cannot add system audio input");
            }

            return YES;
        }
    }

    NSUInteger channelCount = MAX((NSUInteger)1, (NSUInteger)asbd->mChannelsPerFrame);
    AudioChannelLayout layout = {0};
    size_t layoutSize = 0;
    if (channelCount == 1) {
        layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
        layoutSize = sizeof(AudioChannelLayout);
    } else if (channelCount == 2) {
        layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
        layoutSize = sizeof(AudioChannelLayout);
    }

    NSMutableDictionary *audioSettings = [@{
        AVFormatIDKey: @(kAudioFormatMPEG4AAC),
        AVSampleRateKey: @(asbd->mSampleRate),
        AVNumberOfChannelsKey: @(channelCount),
        AVEncoderBitRateKey: @(192000)
    } mutableCopy];

    if (layoutSize > 0) {
        audioSettings[AVChannelLayoutKey] = [NSData dataWithBytes:&layout length:layoutSize];
    }

    AVAssetWriterInput *newInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
                                                                      outputSettings:audioSettings];
    newInput.expectsMediaDataInRealTime = YES;

    if (![g_audioWriter canAddInput:newInput]) {
        NSLog(@"❌ Audio writer cannot add %@ input", isMicrophone ? @"microphone" : @"system");
        return NO;
    }
    [g_audioWriter addInput:newInput];

    if (isMicrophone) {
        g_microphoneAudioInput = newInput;
        MRLog(@"🎙️ Microphone audio input created: sampleRate=%.0f, channels=%ld",
              asbd->mSampleRate, (long)channelCount);
    } else {
        g_systemAudioInput = newInput;
        MRLog(@"🔈 System audio input created: sampleRate=%.0f, channels=%ld",
              asbd->mSampleRate, (long)channelCount);
    }

    return YES;
}

+ (BOOL)isScreenCaptureKitAvailable {
    if (@available(macOS 15.0, *)) {
        return [SCShareableContent class] != nil && [SCStream class] != nil && [SCRecordingOutput class] != nil;
    }
    return NO;
}

+ (void)prewarmShareableContent {
    if (@available(macOS 15.0, *)) {
        // Kayit sirasindaki SCShareableContent cagrisi yavas olabiliyor
        // (TCC kontrolu + ekran/pencere envanteri). Ozellikle ekran
        // konfigurasyonu degistikten sonra (clamshell) bu envanter sifirdan
        // kuruluyor ve saniyeler surebiliyor.
        //
        // Sadece "isitmak" yetmiyordu: kayit aninda cagri YINE bastan
        // yapiliyordu. Artik sonucu saklayip kayitta yeniden kullaniyoruz,
        // yani bu adim tamamen atlanabiliyor.
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
          @try {
            CFAbsoluteTime fetchStart = CFAbsoluteTimeGetCurrent();
            [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *content, NSError *contentError) {
                if (contentError || !content) {
                    MRLog(@"⚠️ Prewarm shareable content failed: %@", contentError.localizedDescription);
                    return;
                }
                SCKStoreShareableContent(content);
                NSLog(@"🔥 Prewarm: shareable content hazir ve saklandi (%.0fms, %lu ekran, %lu pencere)",
                      (CFAbsoluteTimeGetCurrent() - fetchStart) * 1000.0,
                      (unsigned long)content.displays.count,
                      (unsigned long)content.windows.count);
            }];
          } @catch (NSException *exception) {
            NSLog(@"[Recorder] Prewarm failed safely: %@", exception.reason);
          }
        });
    }
}

+ (BOOL)startRecordingWithConfiguration:(NSDictionary *)config delegate:(id)delegate error:(NSError **)error {
    if (!config) {
        return NO;
    }

    NSDictionary *configCopy = [config copy];
    dispatch_queue_t controlQueue = ScreenCaptureControlQueue();
    __block BOOL accepted = NO;
    __block uint64_t generation = 0;

    dispatch_sync(controlQueue, ^{
        if (g_isRecording || g_isCleaningUp || g_isScheduling) {
            MRLog(@"⚠️ ScreenCaptureKit busy (recording:%d cleaning:%d scheduling:%d)", g_isRecording.load(), g_isCleaningUp.load(), g_isScheduling.load());
            accepted = NO;
            return;
        }
        g_isCleaningUp = NO;
        g_isScheduling = YES;
        generation = ++g_schedulingGeneration;
        accepted = YES;
    });

    if (!accepted) {
        [configCopy release];
        return NO;
    }

    // Prewarm ile alinmis taze envanter varsa cagriyi TAMAMEN atla.
    // Bu cagri clamshell'de saniyeler surebiliyor ve kayit baslatmanin
    // en buyuk gecikme kalemi.
    if (@available(macOS 15.0, *)) {
        SCShareableContent *prewarmed = (SCShareableContent *)SCKTakeCachedShareableContent();
        if (prewarmed) {
            NSLog(@"⚡ Prewarmed shareable content kullanildi — envanter cagrisi atlandi");
            dispatch_async(controlQueue, ^{
                SCKPerformRecordingSetup(configCopy, prewarmed, generation);
            });
            [configCopy release];
            // NOT: Burada onbellegi tazelemek YOK.
            // Kayit setup'i calisirken yeni bir SCShareableContent istegi
            // baslatmak sistem envanterini kayitla ayni anda sorgular ve
            // kamera/stream baslatmayla yarisir. Tazeleme, kayit bittikten
            // sonra desktop tarafindaki periyodik isitmaya birakiliyor.
            return YES;
        }
    }

    // CRITICAL FIX: Use dispatch_get_global_queue instead of main_queue
    // because Node.js standalone doesn't run macOS main event loop (only Electron does)
    NSLog(@"🚀 Requesting shareable content...");
    CFAbsoluteTime contentFetchStart = CFAbsoluteTimeGetCurrent();
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        @try {
            [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *content, NSError *contentError) {
                dispatch_async(controlQueue, ^{
                    if (generation != g_schedulingGeneration) return;
                    if (contentError || !content) {
                        NSLog(@"[Recorder] Content error: %@", contentError);
                        SCKFailScheduling();
                        return;
                    }
                    SCKPerformRecordingSetup(configCopy, content, generation);
                });
            }];
        } @catch (NSException *exception) {
            NSLog(@"[Recorder] Content discovery failed safely: %@", exception.reason);
            dispatch_async(controlQueue, ^{
                if (generation == g_schedulingGeneration) SCKFailScheduling();
            });
        }
    });
    [configCopy release];

    return YES;
}

+ (void)stopRecording {
    dispatch_sync(ScreenCaptureControlQueue(), ^{ SCKRequestStop(nil); });
}

+ (BOOL)isScheduling {
    return g_isScheduling;
}

+ (BOOL)isRecording {
    return g_isRecording;
}

+ (BOOL)isFullyInitialized {
    return g_firstFrameReceived;
}

+ (NSTimeInterval)getVideoStartTimestamp {
    if (!CMTIME_IS_VALID(g_videoStartTime)) {
        return 0;
    }
    // Return as milliseconds since epoch - approximate using current time
    // and relative offset from video start
    NSDate *now = [NSDate date];
    NSTimeInterval currentTimestamp = [now timeIntervalSince1970] * 1000;

    // Calculate time elapsed since video start
    CMTime currentCMTime = CMClockGetTime(CMClockGetHostTimeClock());
    CMTime elapsedCMTime = CMTimeSubtract(currentCMTime, g_videoStartTime);
    NSTimeInterval elapsedSeconds = CMTimeGetSeconds(elapsedCMTime);

    // Video start timestamp = current timestamp - elapsed time
    NSTimeInterval videoStartTimestamp = currentTimestamp - (elapsedSeconds * 1000);
    return videoStartTimestamp;
}

+ (BOOL)isCleaningUp {
    return g_isCleaningUp;
}

@end

// Export C function for checking cleanup state
BOOL isScreenCaptureKitCleaningUp() API_AVAILABLE(macos(12.3)) {
    return [ScreenCaptureKitRecorder isCleaningUp];
}

@implementation ScreenCaptureKitRecorder (Methods)

+ (BOOL)setupVideoWriter {
    // No setup needed - SCRecordingOutput handles everything
    return YES;
}

+ (void)finalizeRecording {
    @synchronized([ScreenCaptureKitRecorder class]) {
        MRLog(@"🎬 Finalizing pure ScreenCaptureKit recording");
        
        // Set cleanup flag now that we're actually cleaning up
        g_isCleaningUp = YES;
        g_isRecording = NO;
        
        [ScreenCaptureKitRecorder cleanupVideoWriter];
    }
}

+ (void)finalizeVideoWriter {
    // Alias for finalizeRecording to maintain compatibility
    [ScreenCaptureKitRecorder finalizeRecording];
}

+ (void)cleanupVideoWriter {
    @synchronized([ScreenCaptureKitRecorder class]) {
        MRLog(@"🧹 Starting ScreenCaptureKit cleanup");
        
        // Clean up in proper order to prevent crashes
        if (g_stream) {
            [g_stream release];
            g_stream = nil;
            MRLog(@"✅ Stream reference cleared");
        }
        
        if (g_streamDelegate) {
            [(id)g_streamDelegate release];
            g_streamDelegate = nil;
            MRLog(@"✅ Stream delegate reference cleared");
        }
        
        [g_videoStreamOutput release];
        [g_audioStreamOutput release];
        g_videoStreamOutput = nil;
        g_audioStreamOutput = nil;
        g_videoQueue = nil;
        g_audioQueue = nil;
        if (g_pixelBufferAdaptorRef) {
            CFRelease(g_pixelBufferAdaptorRef);
            g_pixelBufferAdaptorRef = NULL;
        }
        SCKSetOwnedString(&g_audioOutputPath, nil);
        g_shouldCaptureAudio = NO;
        g_captureMicrophoneEnabled = NO;
        g_captureSystemAudioEnabled = NO;
        g_captureCameraEnabled = NO;

        g_isRecording = NO;
        g_isCleaningUp = NO;  // Reset cleanup flag
        SCKSetOwnedString(&g_outputPath, nil);

        // ELECTRON FIX: Reset frame tracking
        g_firstFrameReceived = NO;
        g_frameCountSinceStart = 0;
        
        MRLog(@"🧹 Pure ScreenCaptureKit cleanup complete");
    }
}

@end
static dispatch_queue_t ScreenCaptureControlQueue(void) {
    static dispatch_queue_t controlQueue = NULL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        controlQueue = dispatch_queue_create("com.macrecorder.screencapture.control", DISPATCH_QUEUE_SERIAL);
    });
    return controlQueue;
}

static void SCKMarkSchedulingComplete(void) {
    g_isScheduling = NO;
}

static void SCKFailScheduling(void) {
    SCKRequestStop(nil);
}

static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *content, uint64_t generation) API_AVAILABLE(macos(12.3)) {
    @autoreleasepool {
      @try {
        if (generation != g_schedulingGeneration) return;
        if (!config || !content) {
            SCKFailScheduling();
            return;
        }

        // CRITICAL FIX: Reset frame tracking at START of new recording
        g_firstFrameReceived = NO;
        g_frameCountSinceStart = 0;
        NSLog(@"🔄 Frame tracking reset for new recording");

        NSString *outputPath = config[@"outputPath"];
        if (!outputPath || [outputPath length] == 0) {
            NSLog(@"❌ Invalid output path provided");
            SCKFailScheduling();
            return;
        }
        SCKSetOwnedString(&g_outputPath, outputPath);

        NSNumber *displayId = config[@"displayId"];
        NSNumber *windowId = config[@"windowId"];
        NSDictionary *captureRect = config[@"captureRect"];
        NSNumber *captureCursor = config[@"captureCursor"];
        NSNumber *includeMicrophone = config[@"includeMicrophone"];
        NSNumber *includeSystemAudio = config[@"includeSystemAudio"];
        NSString *microphoneDeviceId = config[@"microphoneDeviceId"];
        NSString *audioOutputPath = MRNormalizePath(config[@"audioOutputPath"]);
        NSNumber *sessionTimestampNumber = config[@"sessionTimestamp"];
        NSNumber *frameRateNumber = config[@"frameRate"];
        NSNumber *mixAudioNumber = config[@"mixAudio"];
        g_mixAudioEnabled = mixAudioNumber ? [mixAudioNumber boolValue] : YES;
        NSNumber *mixMicGainNumber = config[@"mixMicGain"];
        NSNumber *mixSystemGainNumber = config[@"mixSystemGain"];
        if (mixMicGainNumber && [mixMicGainNumber respondsToSelector:@selector(floatValue)]) {
            g_mixMicGain = [mixMicGainNumber floatValue];
            if (g_mixMicGain < 0.f) g_mixMicGain = 0.f;
            if (g_mixMicGain > 2.f) g_mixMicGain = 2.f;
        }
        if (mixSystemGainNumber && [mixSystemGainNumber respondsToSelector:@selector(floatValue)]) {
            g_mixSystemGain = [mixSystemGainNumber floatValue];
            if (g_mixSystemGain < 0.f) g_mixSystemGain = 0.f;
            if (g_mixSystemGain > 2.f) g_mixSystemGain = 2.f;
        }
        SCKSetOwnedString(&g_qualityPreset, SCKNormalizeQualityPreset(config[@"quality"]));
        MRLog(@"🎚️ Requested quality preset: %@", g_qualityPreset);
        NSNumber *captureCamera = config[@"captureCamera"];

        if (frameRateNumber && [frameRateNumber respondsToSelector:@selector(intValue)]) {
            NSInteger fps = [frameRateNumber intValue];
            if (fps < 1) fps = 1;
            if (fps > 120) fps = 120;
            g_targetFPS = fps;
        } else {
            g_targetFPS = 60;
        }

        MRLog(@"🎬 Starting PURE ScreenCaptureKit recording (NO AVFoundation)");
        MRLog(@"🔧 Config: cursor=%@ mic=%@ system=%@ display=%@ window=%@ crop=%@",
              captureCursor, includeMicrophone, includeSystemAudio, displayId, windowId, captureRect);
        MRLog(@"🔍 AUDIO DEBUG: includeMicrophone type=%@ value=%d", [includeMicrophone class], [includeMicrophone boolValue]);
        MRLog(@"🔍 AUDIO DEBUG: includeSystemAudio type=%@ value=%d", [includeSystemAudio class], [includeSystemAudio boolValue]);
        MRLog(@"🎚️ Post-mix enabled: %@ (mic=%.2f, sys=%.2f)", g_mixAudioEnabled ? @"YES" : @"NO", g_mixMicGain, g_mixSystemGain);

        MRLog(@"✅ Got %lu displays, %lu windows for pure recording",
              content.displays.count, content.windows.count);
        MRLog(@"🔍 ScreenCaptureKit available displays:");
        for (SCDisplay *display in content.displays) {
            MRLog(@"   Display ID=%u, Size=%dx%d, Frame=(%.0f,%.0f,%.0fx%.0f)",
                  display.displayID, (int)display.width, (int)display.height,
                  display.frame.origin.x, display.frame.origin.y,
                  display.frame.size.width, display.frame.size.height);
        }

        SCContentFilter *filter = nil;
        NSInteger recordingWidth = 0;
        NSInteger recordingHeight = 0;
        SCDisplay *targetDisplay = nil;

        if (windowId && [windowId integerValue] != 0) {
            SCRunningApplication *targetApp = nil;
            SCWindow *targetWindow = nil;

            for (SCWindow *window in content.windows) {
                if (window.windowID == [windowId unsignedIntValue]) {
                    targetWindow = window;
                    targetApp = window.owningApplication;
                    break;
                }
            }

            if (targetWindow && targetApp) {
                CGFloat windowLogicalWidth = targetWindow.frame.size.width;
                CGFloat windowLogicalHeight = targetWindow.frame.size.height;

                // Find display containing this window to get scale factor
                CGFloat scaleFactor = 1.0;
                SCDisplay *windowDisplay = nil;
                CGPoint windowCenter = CGPointMake(
                    targetWindow.frame.origin.x + windowLogicalWidth / 2.0,
                    targetWindow.frame.origin.y + windowLogicalHeight / 2.0
                );
                for (SCDisplay *disp in content.displays) {
                    CGRect dispBounds = CGRectMake(disp.frame.origin.x, disp.frame.origin.y,
                                                   disp.frame.size.width, disp.frame.size.height);
                    if (CGRectContainsPoint(dispBounds, windowCenter)) {
                        windowDisplay = disp;
                        scaleFactor = SCKBackingScaleForDisplay(disp);
                        break;
                    }
                }
                // Fallback: use main display scale factor
                if (scaleFactor == 1.0 && content.displays.count > 0) {
                    SCDisplay *mainDisp = content.displays.firstObject;
                    if (!windowDisplay) windowDisplay = mainDisp;
                    scaleFactor = SCKBackingScaleForDisplay(mainDisp);
                }

                // DOGRUDAN pencere yakalama. Ekrandan kirpma/kompozit YOK: bu
                // filtre pencereyi kendi basina yakalar, pencere tasinsa da takip
                // eder. (Display kompozit alternatifi denendi ve reddedildi.)
                filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:targetWindow];

                CGFloat displayScale = scaleFactor;
                CGFloat filterScaleLog = -1.0;
                if (@available(macOS 14.0, *)) {
                    // filter.pointPixelScale, SCK'nin bu filtre icin GERCEKTEN
                    // uretecegi point->pixel oranidir; tek yetkili kaynak odur.
                    //
                    // Eskiden MAX(displayScale, filterScale) aliniyordu. Bu yanlis:
                    // olculdu ki harici 3440x1440 (1x) ekranda display heuristigi
                    // 2.00 donerken filtre dogru sekilde 1.00 donuyor. MAX ile 2x
                    // istenince SCK yine 1x uretiyor ve icerik karenin sol ust
                    // ceyregine sikisip geri kalani siyah kaliyordu.
                    // Retina'da filtre zaten 2.00 donuyor (olculdu), yani Retina
                    // keskinligi kaybi YOK.
                    CGFloat filterScale = filter.pointPixelScale;
                    filterScaleLog = filterScale;
                    if (isfinite(filterScale) && filterScale >= 1.0 && filterScale <= 4.0) {
                        scaleFactor = filterScale;
                    }
                }
                scaleFactor = MIN(4.0, MAX(1.0, scaleFactor));

                NSInteger physicalWindowWidth = (NSInteger)llround(windowLogicalWidth * scaleFactor);
                NSInteger physicalWindowHeight = (NSInteger)llround(windowLogicalHeight * scaleFactor);

                MRLog(@"🪟 Recording window: %@ logical=%ux%u, physical=%ldx%ld, scale=%.2fx "
                      @"(displayScale=%.2f filterScale=%.2f)",
                      targetWindow.title, (unsigned)windowLogicalWidth, (unsigned)windowLogicalHeight,
                      (long)physicalWindowWidth, (long)physicalWindowHeight, scaleFactor,
                      displayScale, filterScaleLog);
                recordingWidth = physicalWindowWidth;
                recordingHeight = physicalWindowHeight;
            } else {
                NSLog(@"❌ Window ID %@ not found", windowId);
                SCKFailScheduling();
                return;
            }
        } else {
            if (displayId) {
                for (SCDisplay *display in content.displays) {
                    if (display.displayID == [displayId unsignedIntValue]) {
                        targetDisplay = display;
                        break;
                    }
                }

                if (!targetDisplay && content.displays.count > 0) {
                    NSUInteger count = content.displays.count;
                    NSUInteger idx0 = (NSUInteger)[displayId unsignedIntValue];
                    if (idx0 < count) {
                        targetDisplay = content.displays[idx0];
                    } else if ([displayId unsignedIntegerValue] > 0) {
                        NSUInteger idx1 = [displayId unsignedIntegerValue] - 1;
                        if (idx1 < count) {
                            targetDisplay = content.displays[idx1];
                        }
                    }
                }
            }

            if (!targetDisplay && content.displays.count > 0) {
                targetDisplay = content.displays.firstObject;
            }

            if (targetDisplay) {
                CGDirectDisplayID displayID = targetDisplay.displayID;
                filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:@[]];

                // CGDisplayPixelsWide/High reports the current Quartz coordinate
                // resolution on scaled Retina modes (for example 1800x1169), not
                // necessarily the backing pixels ScreenCaptureKit can deliver.
                // The filter exposes the authoritative point -> pixel scale and
                // content rect, so derive the requested stream size from those.
                CGFloat logicalWidth = targetDisplay.frame.size.width;
                CGFloat logicalHeight = targetDisplay.frame.size.height;
                CGFloat scaleFactor = SCKBackingScaleForDisplay(targetDisplay);
                if (@available(macOS 14.0, *)) {
                    CGRect filterRect = filter.contentRect;
                    CGFloat filterScale = filter.pointPixelScale;
                    if (filterRect.size.width > 0 && filterRect.size.height > 0) {
                        logicalWidth = filterRect.size.width;
                        logicalHeight = filterRect.size.height;
                    }
                    // Pencere dalindaki ile ayni kural: SCK'nin uretecegi olcek
                    // filtreden okunur. MAX(display, filter) yanlisti — harici
                    // 3440x1440 (1x) ekranda display heuristigi 2.00 donuyor ve
                    // istenen boyut SCK'nin urettiginin iki kati oluyordu.
                    if (isfinite(filterScale) && filterScale >= 1.0 && filterScale <= 4.0) {
                        scaleFactor = filterScale;
                    }
                }
                scaleFactor = MIN(4.0, MAX(1.0, scaleFactor));

                NSInteger physicalWidth = (NSInteger)llround(logicalWidth * scaleFactor);
                NSInteger physicalHeight = (NSInteger)llround(logicalHeight * scaleFactor);
                MRLog(@"🖥️ Recording display %u: logical=%.0fx%.0f, physical=%ldx%ld, scale=%.2fx (filter-backed)",
                      displayID, logicalWidth, logicalHeight,
                      (long)physicalWidth, (long)physicalHeight, scaleFactor);

                recordingWidth = physicalWidth;
                recordingHeight = physicalHeight;
            } else {
                NSLog(@"❌ No display available");
                SCKFailScheduling();
                return;
            }
        }

        // desktopIndependentWindow kendi içerik sınırını zaten uygular. JS
        // katmanındaki window captureArea cursor metadata'sı olarak tutulabilir;
        // burada tekrar crop boyutu gibi kullanılırsa yukarıda hesaplanan Retina
        // physicalWindowWidth/Height logical 1x ölçülerle ezilir. Bu blok yalnızca
        // gerçek display/area kayıtlarında çıktı ölçüsünü değiştirmeli.
        BOOL isWindowCapture = windowId && [windowId integerValue] != 0;
        if (!isWindowCapture && captureRect && captureRect[@"width"] && captureRect[@"height"]) {
            CGFloat cropWidth = [captureRect[@"width"] doubleValue];
            CGFloat cropHeight = [captureRect[@"height"] doubleValue];
            if (cropWidth > 0 && cropHeight > 0) {
                // Scale crop dimensions for Retina displays
                CGFloat cropScaleFactor = 1.0;
                if (targetDisplay) {
                    cropScaleFactor = SCKBackingScaleForDisplay(targetDisplay);
                    if (@available(macOS 14.0, *)) {
                        // Ekran/pencere dallariyla ayni kural: yetkili kaynak filtre.
                        CGFloat filterScale = filter.pointPixelScale;
                        if (isfinite(filterScale) && filterScale >= 1.0 && filterScale <= 4.0) {
                            cropScaleFactor = filterScale;
                        }
                    }
                }
                cropScaleFactor = MIN(4.0, MAX(1.0, cropScaleFactor));
                NSInteger physicalCropWidth = (NSInteger)(cropWidth * cropScaleFactor);
                NSInteger physicalCropHeight = (NSInteger)(cropHeight * cropScaleFactor);
                MRLog(@"🔲 Crop area: logical=%.0fx%.0f, physical=%ldx%ld, scale=%.2fx at (%.0f,%.0f)",
                      cropWidth, cropHeight, (long)physicalCropWidth, (long)physicalCropHeight, cropScaleFactor,
                      [captureRect[@"x"] doubleValue], [captureRect[@"y"] doubleValue]);
                recordingWidth = physicalCropWidth;
                recordingHeight = physicalCropHeight;
            }
        }

        CGFloat qualityScale = SCKQualityScaleForPreset(g_qualityPreset);
        if (qualityScale < 0.999 && recordingWidth > 0 && recordingHeight > 0) {
            NSInteger scaledWidth = MAX(1, (NSInteger)((double)recordingWidth * qualityScale + 0.5));
            NSInteger scaledHeight = MAX(1, (NSInteger)((double)recordingHeight * qualityScale + 0.5));
            MRLog(@"🎚️ Quality '%@': scaling output to %ldx%ld (%.0f%% of source %ldx%ld)",
                  g_qualityPreset,
                  (long)scaledWidth,
                  (long)scaledHeight,
                  qualityScale * 100.0,
                  (long)recordingWidth,
                  (long)recordingHeight);
            recordingWidth = scaledWidth;
            recordingHeight = scaledHeight;
        } else {
            MRLog(@"🎚️ Quality '%@': using full resolution %ldx%ld",
                  g_qualityPreset,
                  (long)recordingWidth,
                  (long)recordingHeight);
        }

        // H.264 4:2:0 luma duzlemi CIFT boyut ister; tek sayili genislik/yukseklik
        // encoder'i gereksiz yere yazilim yoluna itebiliyor. 1x harici ekranda
        // pencere boyutlari dogrudan piksele esitlendigi icin (Retina'da 2x ile
        // her zaman cift olur) tek sayilar yalnizca orada ortaya cikiyor.
        // En fazla 1 piksel kirpilir; olcek/konum degismez.
        // NOT: bu tek basina "bos kayit" hatasini cozmez — asil koruma asagidaki
        // kademeli encoder ayari geri dususudur.
        if (recordingWidth % 2 != 0) recordingWidth -= 1;
        if (recordingHeight % 2 != 0) recordingHeight -= 1;
        recordingWidth = MAX(2, recordingWidth);
        recordingHeight = MAX(2, recordingHeight);

        SCStreamConfiguration *streamConfig = [[SCStreamConfiguration alloc] init];
        streamConfig.width = recordingWidth;
        streamConfig.height = recordingHeight;
        streamConfig.minimumFrameInterval = CMTimeMake(1, (int)MAX(1, g_targetFPS));
        streamConfig.pixelFormat = kCVPixelFormatType_32BGRA;
        streamConfig.scalesToFit = NO;
        if (@available(macOS 13.0, *)) {
            streamConfig.queueDepth = 8;
        }
        if (@available(macOS 14.0, *)) {
            // captureResolution = SCCaptureResolutionBest BILEREK KULLANILMIYOR.
            //
            // Retina kalitesi icin eklenmisti ama GPU (Metal) ile cizen
            // uygulamalarda pencere kaydinda ICERIGI TAMAMEN DUSURUYOR: iTerm2'de
            // sekme cubugu/bolme basliklari (AppKit) geliyor, oturum icerigi
            // (Metal katmani) simsiyah cikiyordu. Olculdu: ayni pencere, ayni
            // kosullar, sadece bu satir kapaliyken icerik geri geldi.
            // Cozunurluk zaten streamConfig.width/height ile isteniyor; bu ayara
            // ihtiyac yok.
            //
            // shouldBeOpaque ve colorSpaceName ayni deneyde TEST EDILDI ve
            // masum cikti (ikisi de acikken icerik geliyor) - dokunulmadi.
            streamConfig.shouldBeOpaque = YES;
        }
        if (@available(macOS 13.0, *)) {
            // Frame'leri bilinen bir renk uzayında (sRGB) iste; encoder tarafında
            // BT.709 etiketiyle birlikte uçtan uca tutarlı renk sağlar.
            // Aksi halde P3 ekranlarda display-native, etiketsiz piksel gelir.
            streamConfig.colorSpaceName = kCGColorSpaceSRGB;
        }

        BOOL shouldCaptureMic = includeMicrophone ? [includeMicrophone boolValue] : NO;
        BOOL shouldCaptureSystemAudio = includeSystemAudio ? [includeSystemAudio boolValue] : NO;
        g_shouldCaptureAudio = shouldCaptureSystemAudio || shouldCaptureMic;
        g_captureMicrophoneEnabled = shouldCaptureMic;
        g_captureSystemAudioEnabled = shouldCaptureSystemAudio;
        g_captureCameraEnabled = captureCamera ? [captureCamera boolValue] : NO;

        if (audioOutputPath && ![audioOutputPath isKindOfClass:[NSString class]]) {
            MRLog(@"⚠️ audioOutputPath type mismatch: %@, converting...", NSStringFromClass([audioOutputPath class]));
            SCKSetOwnedString(&g_audioOutputPath, nil);
        } else {
            SCKSetOwnedString(&g_audioOutputPath, audioOutputPath);
        }

        if (g_shouldCaptureAudio && (!g_audioOutputPath || [g_audioOutputPath length] == 0)) {
            NSLog(@"⚠️ Audio capture requested but no audio output path supplied – audio will be disabled");
            g_shouldCaptureAudio = NO;
            g_captureMicrophoneEnabled = NO;
            g_captureSystemAudioEnabled = NO;
        }

        if (@available(macos 13.0, *)) {
            streamConfig.capturesAudio = g_shouldCaptureAudio;
            streamConfig.sampleRate = g_configuredSampleRate ?: 48000;
            streamConfig.channelCount = g_configuredChannelCount ?: 2;
            streamConfig.excludesCurrentProcessAudio = !shouldCaptureSystemAudio;
            NSLog(@"🎤 Audio config (macOS 13+): capturesAudio=%d, excludeProcess=%d (mic=%d sys=%d)",
                  g_shouldCaptureAudio, streamConfig.excludesCurrentProcessAudio,
                  shouldCaptureMic, shouldCaptureSystemAudio);
        }

        if (@available(macos 15.0, *)) {
            streamConfig.captureMicrophone = shouldCaptureMic;
            NSString *micIdToUse = microphoneDeviceId;
            if (micIdToUse && micIdToUse.length > 0) {
                // Validate UniqueID; if invalid, fall back to default to avoid silencing mic
                AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:micIdToUse];
                if (!dev) {
                    NSLog(@"⚠️ Invalid microphone deviceID '%@' – falling back to default", micIdToUse);
                    micIdToUse = nil;
                }
            }

            // NOTE: Voice processing (microphoneMode) is iOS-only and not available on macOS
            // Voice isolation is configured in audio_recorder.mm for AVFoundation-based recordings
            // ScreenCaptureKit microphone capture on macOS 15+ doesn't support microphoneMode

            if (micIdToUse && micIdToUse.length > 0) {
                streamConfig.microphoneCaptureDeviceID = micIdToUse;
            }
            NSLog(@"🎤 Microphone (macOS 15+): enabled=%d, deviceID=%@",
                  shouldCaptureMic, micIdToUse ?: @"default");
        }

        if (captureRect && captureRect[@"x"] && captureRect[@"y"] && captureRect[@"width"] && captureRect[@"height"] && targetDisplay) {
            // CRITICAL FIX: captureRect comes from index.js as ALREADY display-relative coordinates
            // (see index.js:371-376 where global coords are converted to display-relative)
            // So we should NOT subtract display origin again - just use them directly!
            CGFloat displayRelativeX = [captureRect[@"x"] doubleValue];
            CGFloat displayRelativeY = [captureRect[@"y"] doubleValue];
            CGFloat cropWidth = [captureRect[@"width"] doubleValue];
            CGFloat cropHeight = [captureRect[@"height"] doubleValue];
            CGRect displayBounds = targetDisplay.frame;

            MRLog(@"🔍 CROP DEBUG: Input coords=(%.0f,%.0f) size=(%.0fx%.0f)",
                  displayRelativeX, displayRelativeY, cropWidth, cropHeight);
            MRLog(@"🔍 CROP DEBUG: Display bounds origin=(%.0f,%.0f) size=(%.0fx%.0f)",
                  displayBounds.origin.x, displayBounds.origin.y,
                  displayBounds.size.width, displayBounds.size.height);

            if (displayRelativeX >= 0 && displayRelativeY >= 0 &&
                displayRelativeX + cropWidth <= displayBounds.size.width &&
                displayRelativeY + cropHeight <= displayBounds.size.height) {
                CGRect sourceRect = CGRectMake(displayRelativeX, displayRelativeY, cropWidth, cropHeight);
                streamConfig.sourceRect = sourceRect;
                MRLog(@"✂️ Crop sourceRect applied: (%.0f,%.0f) %.0fx%.0f (display-relative)",
                      displayRelativeX, displayRelativeY, cropWidth, cropHeight);
            } else {
                NSLog(@"❌ Crop coordinates out of display bounds - skipping crop");
                MRLog(@"   Coords: (%.0f,%.0f) size:(%.0fx%.0f) vs display:(%.0fx%.0f)",
                      displayRelativeX, displayRelativeY, cropWidth, cropHeight,
                      displayBounds.size.width, displayBounds.size.height);
            }
        }

        BOOL shouldShowCursor = captureCursor ? [captureCursor boolValue] : YES;
        streamConfig.showsCursor = shouldShowCursor;
        MRLog(@"🎥 Pure ScreenCapture config: %ldx%ld @ %ldfps, cursor=%d",
              recordingWidth, recordingHeight, (long)g_targetFPS, shouldShowCursor);

        NSError *writerError = nil;
        if (![ScreenCaptureKitRecorder prepareVideoWriterWithWidth:recordingWidth height:recordingHeight error:&writerError]) {
            NSLog(@"❌ Failed to prepare video writer: %@", writerError);
            SCKFailScheduling();
            return;
        }

        g_videoQueue = dispatch_queue_create("screen_capture_video_queue", DISPATCH_QUEUE_SERIAL);
        g_audioQueue = dispatch_queue_create("screen_capture_audio_queue", DISPATCH_QUEUE_SERIAL);
        g_videoStreamOutput = [[ScreenCaptureVideoOutput alloc] init];
        if (g_shouldCaptureAudio) {
            g_audioStreamOutput = [[ScreenCaptureAudioOutput alloc] init];
        } else {
            g_audioStreamOutput = nil;
        }

        g_streamDelegate = [[PureScreenCaptureDelegate alloc] init];
        g_stream = [[SCStream alloc] initWithFilter:filter configuration:streamConfig delegate:g_streamDelegate];
        if (!g_stream) {
            NSLog(@"❌ Failed to create pure stream");
            SCKFailScheduling();
            return;
        }

        NSError *outputError = nil;
        BOOL videoOutputAdded = [g_stream addStreamOutput:g_videoStreamOutput type:SCStreamOutputTypeScreen sampleHandlerQueue:g_videoQueue error:&outputError];
        if (!videoOutputAdded || outputError) {
            NSLog(@"❌ Failed to add video output: %@", outputError);
            SCKFailScheduling();
            return;
        }

        if (g_shouldCaptureAudio) {
            if (@available(macOS 13.0, *)) {
                NSError *audioError = nil;
                BOOL anyAudioAdded = NO;
                if (@available(macOS 15.0, *)) {
                    // On macOS 15+, microphone has its own output type
                    if (g_captureMicrophoneEnabled) {
                        NSLog(@"➕ Adding microphone output stream...");
                        audioError = nil;
                        BOOL micAdded = [g_stream addStreamOutput:g_audioStreamOutput
                                                             type:SCStreamOutputTypeMicrophone
                                               sampleHandlerQueue:g_audioQueue
                                                            error:&audioError];
                        if (!micAdded || audioError) {
                            NSLog(@"❌ Failed to add microphone output: %@", audioError);
                            SCKFailScheduling();
                            return;
                        }
                        anyAudioAdded = YES;
                        NSLog(@"✅ Microphone output added successfully");
                    }
                    if (g_captureSystemAudioEnabled) {
                        NSLog(@"➕ Adding system audio output stream...");
                        audioError = nil;
                        BOOL sysAdded = [g_stream addStreamOutput:g_audioStreamOutput
                                                            type:SCStreamOutputTypeAudio
                                              sampleHandlerQueue:g_audioQueue
                                                           error:&audioError];
                        if (!sysAdded || audioError) {
                            NSLog(@"❌ Failed to add system audio output: %@", audioError);
                            SCKFailScheduling();
                            return;
                        }
                        anyAudioAdded = YES;
                        NSLog(@"✅ System audio output added successfully");
                    }
                } else {
                    // macOS 13/14: only SCStreamOutputTypeAudio exists
                    NSLog(@"➕ Adding audio output stream (macOS 13/14)...");
                    audioError = nil;
                    BOOL audAdded = [g_stream addStreamOutput:g_audioStreamOutput
                                                        type:SCStreamOutputTypeAudio
                                          sampleHandlerQueue:g_audioQueue
                                                       error:&audioError];
                    if (!audAdded || audioError) {
                        NSLog(@"❌ Failed to add audio output: %@", audioError);
                        SCKFailScheduling();
                        return;
                    }
                    anyAudioAdded = YES;
                    NSLog(@"✅ Audio output added successfully");
                }

                if (!anyAudioAdded) {
                    NSLog(@"❌ No audio outputs added (unexpected configuration)");
                    SCKFailScheduling();
                    return;
                }
            } else {
                NSLog(@"⚠️ Audio capture requested but requires macOS 13.0+");
                g_shouldCaptureAudio = NO;
                g_captureMicrophoneEnabled = NO;
                g_captureSystemAudioEnabled = NO;
            }
        }

        MRLog(@"✅ Stream outputs configured (audio=%d)", g_shouldCaptureAudio);
        if (sessionTimestampNumber) {
            MRLog(@"🕒 Session timestamp: %@", sessionTimestampNumber);
        }

        NSLog(@"🚀 CALLING startCaptureWithCompletionHandler (async)...");
        SCStream *startingStream = g_stream;
        [startingStream startCaptureWithCompletionHandler:^(NSError *startError) {
            dispatch_async(ScreenCaptureControlQueue(), ^{
                if (generation != g_schedulingGeneration || startingStream != g_stream) {
                    // Cancellation can precede the asynchronous start reply.
                    if (!startError) {
                        @try { [startingStream stopCaptureWithCompletionHandler:^(NSError *error) {}]; }
                        @catch (NSException *exception) { NSLog(@"[Recorder] Stale stream stop: %@", exception.reason); }
                    }
                    return;
                }
                if (startError) {
                    NSLog(@"❌ Failed to start pure capture: %@", startError);
                    NSLog(@"❌ Error domain: %@, code: %ld", startError.domain, (long)startError.code);
                    NSLog(@"❌ Error userInfo: %@", startError.userInfo);
                            SCKFailScheduling();
                } else {
                    NSLog(@"🎉 PURE ScreenCaptureKit recording started successfully!");
                    NSLog(@"🎤 Audio capture enabled: %d (mic=%d, system=%d)", g_shouldCaptureAudio, g_captureMicrophoneEnabled, g_captureSystemAudioEnabled);
                    @synchronized([ScreenCaptureKitRecorder class]) {
                        g_isRecording = YES;
                    }
                    SCKMarkSchedulingComplete();
                }
            });
        }];
      } @catch (NSException *exception) {
        NSLog(@"[Recorder] Capture setup failed safely: %@", exception.reason);
        SCKFailScheduling();
      }
    }
}
