#import "PictureInPictureManager.h"
#import "VolcApiEngine/VePictureInPictureController.h"
#import <AVKit/AVKit.h>
#import <TTSDKFramework/TTSDKFramework.h>
#import <TTSDKFramework/TTVideoEngine+Options.h>
#import <UIKit/UIKit.h>
#import <VideoToolbox/VideoToolbox.h>

@protocol PictureInPictureManagerListener;

/**
 * Map player scaling mode to PiP render mode.
 * TTVideoEngineScalingMode -> VePictureInPictureRenderMode
 * - 0 (None) -> 1 (AspectFit)
 * - 1 (AspectFit) -> 1 (AspectFit)
 * - 2 (AspectFill) -> 0 (AspectFill)
 * - 3 (Fill) -> 2 (FullFill)
 */
static inline NSInteger
VePictureInPictureRenderModeFromScalingMode(NSInteger scalingMode) {
  switch (scalingMode) {
  case 0:
  case 1:  return 1; // AspectFit
  case 2:  return 0; // AspectFill
  case 3:  return 2; // FullFill
  default: return 1;
  }
}

@interface PictureInPictureManager () <PictureInPictureManagerListener>

@property(nonatomic, strong) TTVideoEngine *player;
@property(nonatomic, strong) VePictureInPictureController *pipController;
@property(nonatomic, weak) id<PictureInPictureManagerListener> listener;
@property(nonatomic, weak) UIView *contentView;
@property(nonatomic, assign) VePictureInPictureType VePictureInPictureType;
@property(nonatomic, assign) VePictureInPictureStatus state;
@property(nonatomic, strong) id savedVideoScaleMode;
@property(nonatomic, copy, nullable) void (^startPipCompletion)(BOOL success);
// Pixel buffer transformation for syncPlayerViewConfig (ContentSource PiP ignores videoGravity)
@property(nonatomic, assign) BOOL pipTransformEnabled;
@property(nonatomic, assign) CGSize pipTransformTargetSize;
@property(nonatomic, assign) NSInteger pipTransformRenderMode;
@property(nonatomic, assign) CVPixelBufferPoolRef pipTransformPool;
// TTSDK/VolcApiEngine 1.50.x release builds may leave the underlying
// AVPictureInPictureController impossible after the user closes PiP outside the
// app. Rebuild lazily on the next foreground play/manual start, not inside
// DidStop where iOS teardown is still in progress.
@property(nonatomic, assign) BOOL pipControllerNeedsRecreateAfterStop;

@end

typedef struct EngineVideoWrapperContext {
  EngineVideoWrapper *videoWrapper;
  void *pictureInPictureManager;
} EngineVideoWrapperContext;

@implementation PictureInPictureManager

+ (instancetype)sharedInstance {
  static PictureInPictureManager *instance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    instance = [[self alloc] init];
  });
  return instance;
}

+ (instancetype)getInstance {
  return [self sharedInstance];
}

- (instancetype)init {
  if (self = [super init]) {
    _state = VePictureInPictureStatusIde;
    _VePictureInPictureType = VePictureInPictureTypeContentSource;
    _autoHideViewController = YES;
    if (@available(iOS 14.2, *)) {
      _canStartPictureInPictureAutomaticallyFromInline = NO;
    }
  }
  //    [TTVideoEngine setGlobalForKey:VEGSKeyIgnoreGlActive value:@(1)];
  return self;
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
#if !__has_feature(objc_arc)
  [super dealloc];
#endif
}

// --------------------------------------------------------------------------
// MARK: - PipController init (IDENTICAL to master)
// --------------------------------------------------------------------------

- (void)initPipController {
  if (self.pipController != nil) {
    self.pipController = nil;
    return;
  }
  self.pipController = [[VePictureInPictureController alloc]
      initWithType:(self.VePictureInPictureType)
       contentView:self.player.playerView];
  [self.pipController setRenderMode:1];
  self.pipController.delegate = self;
  self.pipController.autoConfigAudioSession = YES;
  if (@available(iOS 14.2, *)) {
    self.pipController.canStartPictureInPictureAutomaticallyFromInline =
        self.canStartPictureInPictureAutomaticallyFromInline;
  }
  if (@available(iOS 14.0, *)) {
    self.pipController.requiresLinearPlayback = NO;
  }
  self.pipControllerNeedsRecreateAfterStop = NO;
}

// --------------------------------------------------------------------------
// MARK: - ensurePipControllerReady (IDENTICAL to master — two separate ifs)
// --------------------------------------------------------------------------

- (void)ensurePipControllerReady:(TTVideoEngine *)player
                      completion:(nullable void (^)(void))completion {
  if (player && self.player != player) {
    if (self.player) {
      [self.player setVideoWrapper:[self createVideoWrapperWithEmpty:self]];
    }
    self.player = player;
    self.contentView = player.playerView;
  }

  if (self.pipControllerNeedsRecreateAfterStop && self.pipController) {
    NSLog(@"PIP: Recreate controller after previous PiP stop");
    self.pipController.delegate = nil;
    [self.pipController stopPictureInPicture];
    self.pipController = nil;
    self.contentView = self.player.playerView;
    self.pipControllerNeedsRecreateAfterStop = NO;
  }

  if (@available(iOS 14.0, *)) {
    if (self.VePictureInPictureType == VePictureInPictureTypeContentSource) {
      [self.player setSupportPictureInPictureMode:NO];
    } else {
      [self.player setSupportPictureInPictureMode:YES];
    }
  }

  // If no pipController, initialize first
  if (!self.pipController) {
    [self initPipController];
    [self.player setVideoWrapper:[self createVideoWrapper:self]];
    [self updatePipControllerWithCompletion:completion];
  }

  // If pipController exists but content view needs update
  if (self.contentView && self.pipController) {
    if (@available(iOS 14.2, *)) {
      self.pipController.canStartPictureInPictureAutomaticallyFromInline =
          self.canStartPictureInPictureAutomaticallyFromInline;
    }
    [self updatePipControllerWithCompletion:completion];
  } else {
    if (completion) {
      completion();
    }
  }
}

// --------------------------------------------------------------------------
// MARK: - updatePipControllerWithCompletion
// --------------------------------------------------------------------------

- (void)updatePipControllerWithCompletion:(nullable void (^)(void))completion {
  __weak __typeof(self) weakSelf = self;
  [self.pipController
      setContentView:self.contentView
          completion:^{
            [weakSelf setVideoSizeWithCompletion:^{
              // First auto-PiP reads the content source geometry before
              // WillStart/DidStart. Apply sync config here so the initial PiP
              // window uses the player view size and render mode.
              [weakSelf applySyncPlayerViewConfigForPipStart];
              [weakSelf.player
                  setVideoWrapper:[weakSelf createVideoWrapper:weakSelf]];
              // Re-arm auto-start after the full content/frame-delivery chain.
              // SDK 1.50.x resets canStartPictureInPictureAutomaticallyFromInline
              // inside setContentView; we re-apply here so the second (and
              // subsequent) auto-PiP sessions work correctly in Release builds.
              if (@available(iOS 14.2, *)) {
                if (weakSelf.canStartPictureInPictureAutomaticallyFromInline) {
                  weakSelf.pipController
                      .canStartPictureInPictureAutomaticallyFromInline = YES;
                }
              }
              dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) {
                  completion();
                }
              });
            }];
          }];
}

// --------------------------------------------------------------------------
// MARK: - setupPlayer (IDENTICAL to master)
// --------------------------------------------------------------------------

- (void)setupPlayer:(TTVideoEngine *)player {
  [self setupPlayer:player completion:nil];
}

- (void)setupPlayer:(TTVideoEngine *)player
         completion:(nullable void (^)(void))completion {
  [self ensurePipControllerReady:player completion:completion];
}

// --------------------------------------------------------------------------
// MARK: - VideoWrapper creation (IDENTICAL to master)
// --------------------------------------------------------------------------

- (EngineVideoWrapper *)createVideoWrapper:(id)managerContext {
  EngineVideoWrapper *videoWrapper = malloc(sizeof(EngineVideoWrapper));
  videoWrapper->process = processVideoFrame;
  videoWrapper->release = releaseVideoWrapper;

  EngineVideoWrapperContext *context =
      malloc(sizeof(EngineVideoWrapperContext));
  context->videoWrapper = videoWrapper;
  context->pictureInPictureManager = (__bridge void *)managerContext;

  videoWrapper->context = context;
  return videoWrapper;
}

- (EngineVideoWrapper *)createVideoWrapperWithEmpty:(id)managerContext {
  EngineVideoWrapper *videoWrapper = malloc(sizeof(EngineVideoWrapper));
  videoWrapper->process = processVideoFrameEmpty;
  videoWrapper->release = releaseVideoWrapperEmpty;

  EngineVideoWrapperContext *context =
      malloc(sizeof(EngineVideoWrapperContext));
  context->videoWrapper = videoWrapper;
  context->pictureInPictureManager = (__bridge void *)managerContext;

  videoWrapper->context = context;
  return videoWrapper;
}

// --------------------------------------------------------------------------
// MARK: - Pixel buffer transform (for syncPlayerViewConfig manual-start only)
// --------------------------------------------------------------------------

- (void)setupPipTransformIfNeeded {
  if (!self.syncPlayerViewConfig || !self.player || !self.player.playerView) {
    self.pipTransformEnabled = NO;
    return;
  }

  id scaleMode = [self.player getOptionBykey:VEKKEY(VEKKeyViewScaleMode_ENUM)];
  NSInteger scalingMode = scaleMode ? [scaleMode integerValue] : 1;
  NSInteger renderMode = VePictureInPictureRenderModeFromScalingMode(scalingMode);

  CGFloat scale = UIScreen.mainScreen.scale;
  CGSize viewSize = self.player.playerView.bounds.size;
  CGSize targetSize =
      CGSizeMake(ceil(viewSize.width * scale), ceil(viewSize.height * scale));

  if (targetSize.width < 1 || targetSize.height < 1) {
    self.pipTransformEnabled = NO;
    return;
  }

  self.pipTransformTargetSize = targetSize;
  self.pipTransformRenderMode = renderMode;

  [self releasePipTransformPool];
  NSDictionary *bufferAttrs = @{
    (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
    (NSString *)kCVPixelBufferWidthKey : @((int)targetSize.width),
    (NSString *)kCVPixelBufferHeightKey : @((int)targetSize.height),
    (NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
    (NSString *)kCVPixelBufferCGImageCompatibilityKey : @YES,
    (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,
  };
  CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL,
                          (__bridge CFDictionaryRef)bufferAttrs,
                          &_pipTransformPool);
  self.pipTransformEnabled = (_pipTransformPool != NULL);
}

- (void)releasePipTransformPool {
  if (_pipTransformPool) {
    CVPixelBufferPoolRelease(_pipTransformPool);
    _pipTransformPool = NULL;
  }
}

- (void)disablePipTransform {
  self.pipTransformEnabled = NO;
  [self releasePipTransformPool];
}

- (void)applySyncPlayerViewConfigForPipStart {
  if (!self.syncPlayerViewConfig || !self.player || !self.pipController) {
    return;
  }

  id scaleMode =
      [self.player getOptionBykey:VEKKEY(VEKKeyViewScaleMode_ENUM)];
  NSInteger scalingMode = scaleMode ? [scaleMode integerValue] : 1;
  NSInteger renderMode =
      VePictureInPictureRenderModeFromScalingMode(scalingMode);
  [self.pipController setRenderMode:renderMode];

  UIView *playerView = self.player.playerView;
  if (playerView) {
    CGSize viewSize = playerView.bounds.size;
    if (!CGSizeEqualToSize(viewSize, CGSizeZero)) {
      [self.pipController setVideoSize:viewSize completion:nil];
    }
  }

  [self setupPipTransformIfNeeded];
  if (self.pipTransformEnabled) {
    [self.pipController setRenderMode:2]; // FullFill
  }
}

- (CVPixelBufferRef)transformPixelBufferForPip:(CVPixelBufferRef)sourceBuffer {
  if (!_pipTransformPool || !sourceBuffer) {
    return CVPixelBufferRetain(sourceBuffer);
  }

  CGFloat targetW = self.pipTransformTargetSize.width;
  CGFloat targetH = self.pipTransformTargetSize.height;
  size_t srcW = CVPixelBufferGetWidth(sourceBuffer);
  size_t srcH = CVPixelBufferGetHeight(sourceBuffer);
  CGFloat srcAspect = (srcH > 0) ? ((CGFloat)srcW / (CGFloat)srcH) : 1.0;
  CGFloat targetAspect = (targetH > 0) ? (targetW / targetH) : 1.0;

  // Skip transform if aspects already match and not FullFill
  if (fabs(srcAspect - targetAspect) < 0.01 &&
      self.pipTransformRenderMode != 2) {
    return CVPixelBufferRetain(sourceBuffer);
  }

  CVPixelBufferRef targetBuffer = NULL;
  CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(
      kCFAllocatorDefault, _pipTransformPool, &targetBuffer);
  if (ret != kCVReturnSuccess || !targetBuffer) {
    return CVPixelBufferRetain(sourceBuffer);
  }

  CGImageRef srcImage = NULL;
  OSStatus status =
      VTCreateCGImageFromCVPixelBuffer(sourceBuffer, NULL, &srcImage);
  if (status != noErr || !srcImage) {
    CVPixelBufferRelease(targetBuffer);
    return CVPixelBufferRetain(sourceBuffer);
  }

  CVPixelBufferLockBaseAddress(targetBuffer, 0);
  void *baseAddr = CVPixelBufferGetBaseAddress(targetBuffer);
  size_t bytesPerRow = CVPixelBufferGetBytesPerRow(targetBuffer);
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  CGContextRef ctx = CGBitmapContextCreate(
      baseAddr, (size_t)targetW, (size_t)targetH, 8, bytesPerRow, colorSpace,
      kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);

  if (ctx) {
    CGContextSetRGBFillColor(ctx, 0, 0, 0, 1);
    CGContextFillRect(ctx, CGRectMake(0, 0, targetW, targetH));

    CGRect drawRect;
    switch (self.pipTransformRenderMode) {
    case 1: { // AspectFit
      if (targetAspect > srcAspect) {
        CGFloat h = targetH;
        CGFloat w = h * srcAspect;
        drawRect = CGRectMake((targetW - w) / 2, 0, w, h);
      } else {
        CGFloat w = targetW;
        CGFloat h = w / srcAspect;
        drawRect = CGRectMake(0, (targetH - h) / 2, w, h);
      }
      break;
    }
    case 0: { // AspectFill
      if (targetAspect > srcAspect) {
        CGFloat w = targetW;
        CGFloat h = w / srcAspect;
        drawRect = CGRectMake(0, (targetH - h) / 2, w, h);
      } else {
        CGFloat h = targetH;
        CGFloat w = h * srcAspect;
        drawRect = CGRectMake((targetW - w) / 2, 0, w, h);
      }
      break;
    }
    case 2: // FullFill
    default:
      drawRect = CGRectMake(0, 0, targetW, targetH);
      break;
    }

    CGContextDrawImage(ctx, drawRect, srcImage);
    CGContextRelease(ctx);
  }

  CGColorSpaceRelease(colorSpace);
  CGImageRelease(srcImage);
  CVPixelBufferUnlockBaseAddress(targetBuffer, 0);

  return targetBuffer;
}

// --------------------------------------------------------------------------
// MARK: - processVideoFrame (master + optional transform)
// --------------------------------------------------------------------------

static void processVideoFrame(void *context, CVPixelBufferRef frame,
                              int64_t timestamp) {
  if (!context || !frame)
    return;

  EngineVideoWrapperContext *ctx = (EngineVideoWrapperContext *)context;
  PictureInPictureManager *managerContext =
      (__bridge PictureInPictureManager *)ctx->pictureInPictureManager;

  if ([managerContext.pipController
          isKindOfClass:[VePictureInPictureController class]]) {
    CFTimeInterval currentTime = CACurrentMediaTime();
    static CFTimeInterval prevInvalidateTime = 0;
    if (currentTime - prevInvalidateTime >= 1.0) {
      prevInvalidateTime = currentTime;
      [managerContext.pipController invalidatePlaybackState];
    }
    CMTime playerCurrentTime =
        CMTimeMakeWithSeconds(managerContext.player.currentPlaybackTime, 1);

    if (managerContext.pipTransformEnabled) {
      CVPixelBufferRef transformedBuffer =
          [managerContext transformPixelBufferForPip:frame];
      [managerContext.pipController enqueuePixelBuffer:transformedBuffer
                                             timestamp:playerCurrentTime];
      CVPixelBufferRelease(transformedBuffer);
    } else {
      // Identical to master: pass frame directly
      [managerContext.pipController enqueuePixelBuffer:frame
                                             timestamp:playerCurrentTime];
    }
  }
}

static void releaseVideoWrapper(void *context) {
  if (!context)
    return;
  EngineVideoWrapperContext *ctx = (EngineVideoWrapperContext *)context;
  free(ctx->videoWrapper);
  free(ctx);
}

static void processVideoFrameEmpty(void *context, CVPixelBufferRef frame,
                                   int64_t timestamp) {
}

static void releaseVideoWrapperEmpty(void *context) {
  if (!context)
    return;
  EngineVideoWrapperContext *ctx = (EngineVideoWrapperContext *)context;
  free(ctx->videoWrapper);
  free(ctx);
}

// --------------------------------------------------------------------------
// MARK: - setVideoSize (IDENTICAL to master)
// --------------------------------------------------------------------------

- (void)setVideoSize {
  [self setVideoSizeWithCompletion:nil];
}

- (void)setVideoSizeWithCompletion:(nullable void (^)(void))completion {
  NSInteger width = [[self.player
      getOptionBykey:VEKKEY(VEKGetKeyPlayerVideoWidth_NSInteger)] integerValue];
  NSInteger height =
      [[self.player getOptionBykey:VEKKEY(VEKGetKeyPlayerVideoHeight_NSInteger)]
          integerValue];
  if (!CGSizeEqualToSize(CGSizeZero, CGSizeMake(width, height))) {
    [self.pipController setVideoSize:CGSizeMake(width, height)
                          completion:completion];
  } else {
    if (completion) {
      completion();
    }
  }
}

// --------------------------------------------------------------------------
// MARK: - startPictureInPicture (IDENTICAL to master)
// --------------------------------------------------------------------------

- (BOOL)startPictureInPictureWithPlayer:(TTVideoEngine *)player {
  [self startPictureInPictureWithPlayer:player completion:nil];
  return YES;
}

- (void)startPictureInPictureWithPlayer:(TTVideoEngine *)player
                             completion:
                                 (nullable void (^)(BOOL success))completion {
  if (!player) {
    if (completion) {
      completion(NO);
    }
    return;
  }
  TTVideoEnginePlaybackState state = player.playbackState;
  BOOL isPaused = (state == TTVideoEnginePlaybackStatePaused ||
                   state == TTVideoEnginePlaybackStateStopped ||
                   state == TTVideoEnginePlaybackStateError);
  if (isPaused) {
    if (completion) {
      completion(NO);
    }
    return;
  }

  self.startPipCompletion = completion;

  [self ensurePipControllerReady:player
                      completion:^{
                        dispatch_async(dispatch_get_main_queue(), ^{
                          self.savedVideoScaleMode = [self.player
                              getOptionBykey:VEKKEY(VEKKeyViewScaleMode_ENUM)];
                          BOOL startSuccess =
                              [self.pipController startPictureInPicture];
                          if (!startSuccess) {
                            NSLog(@"PIP: Failed to start picture-in-picture");
                            if (self.startPipCompletion) {
                              void (^completionBlock)(BOOL) =
                                  self.startPipCompletion;
                              self.startPipCompletion = nil;
                              completionBlock(NO);
                            }
                          }
                        });
                      }];
}

// --------------------------------------------------------------------------
// MARK: - startPictureInPicture with syncPlayerViewConfig (NEW — does not
//         touch auto-PiP paths; uses ensurePipControllerReady:completion:
//         unchanged from master, then applies sync overrides in completion)
// --------------------------------------------------------------------------

- (void)startPictureInPictureWithPlayer:(TTVideoEngine *)player
                   syncPlayerViewConfig:(BOOL)syncPlayerViewConfig
                             completion:
                                 (nullable void (^)(BOOL success))completion {
  if (!player) {
    if (completion) {
      completion(NO);
    }
    return;
  }
  TTVideoEnginePlaybackState state = player.playbackState;
  BOOL isPaused = (state == TTVideoEnginePlaybackStatePaused ||
                   state == TTVideoEnginePlaybackStateStopped ||
                   state == TTVideoEnginePlaybackStateError);
  if (isPaused) {
    if (completion) {
      completion(NO);
    }
    return;
  }

  self.startPipCompletion = completion;
  self.syncPlayerViewConfig = syncPlayerViewConfig;

  [self ensurePipControllerReady:player  // master's method, unchanged
                      completion:^{
    dispatch_async(dispatch_get_main_queue(), ^{
      [self applySyncPlayerViewConfigForPipStart];

      self.savedVideoScaleMode =
          [self.player getOptionBykey:VEKKEY(VEKKeyViewScaleMode_ENUM)];
      BOOL startSuccess = [self.pipController startPictureInPicture];
      if (!startSuccess) {
        NSLog(@"PIP: Failed to start picture-in-picture (syncPlayerViewConfig)");
        [self disablePipTransform];
        if (self.startPipCompletion) {
          void (^completionBlock)(BOOL) = self.startPipCompletion;
          self.startPipCompletion = nil;
          completionBlock(NO);
        }
      }
    });
  }];
}

- (void)startPictureInPictureWithPlayer:(TTVideoEngine *)player
                   syncPlayerViewConfig:(BOOL)syncPlayerViewConfig {
  [self startPictureInPictureWithPlayer:player
                   syncPlayerViewConfig:syncPlayerViewConfig
                             completion:nil];
}

// --------------------------------------------------------------------------
// MARK: - stop / destroy (IDENTICAL to master, plus disablePipTransform)
// --------------------------------------------------------------------------

- (void)stopPictureInPicture {
  [self stopPictureInPictureWithRestore:self.pipController
                                            .autoHideViewController];
}

- (void)stopPictureInPictureWithRestore:(BOOL)restore {
  self.pipController.autoHideViewController = restore;
  if (restore) {
    [self.pipController
        stopPictureInPictureWithType:VePictureInPictureStopTypeFromApiRestore];
  } else {
    [self.pipController stopPictureInPicture];
  }
}

- (void)destroyPictureInPicture {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self stopPictureInPicture];
    [self disablePipTransform];
    self.pipControllerNeedsRecreateAfterStop = NO;
    self.player = nil;
    self.pipController = nil;
    [self setCanStartPictureInPictureAutomaticallyFromInline:NO];
  });
}

- (void)destroyPictureInPictureSync {
  NSAssert([NSThread isMainThread], @"Must be called on main thread");
  [self stopPictureInPicture];
  [self disablePipTransform];
  self.pipControllerNeedsRecreateAfterStop = NO;
  self.player = nil;
  self.pipController = nil;
  [self setCanStartPictureInPictureAutomaticallyFromInline:NO];
}

// --------------------------------------------------------------------------
// MARK: - Misc (IDENTICAL to master)
// --------------------------------------------------------------------------

- (void)setListener:(id<PictureInPictureManagerListener>)listener {
  _listener = listener;
}

- (void)invalidatePlaybackState API_AVAILABLE(ios(15.0)) {
  if (self.pipController) {
    [self.pipController invalidatePlaybackState];
  }
}

- (BOOL)isPictureInPictureSupported {
  return [VePictureInPictureController isPictureInPictureSupported];
}

- (BOOL)isPictureInPictureStarted {
  return [VePictureInPictureController isPictureInPictureStarted];
}

- (BOOL)setupPictureInPictureControllerWithType:(VePictureInPictureType)type {
  if (self.VePictureInPictureType != type && self.pipController != nil) {
    [self.pipController stopPictureInPicture];
    self.pipController = nil;
  }
  self.VePictureInPictureType = type;
  [self initPipController];
  return YES;
}

- (void)notifyPictureInPictureStateUpdate:(VePictureInPictureStatus)state
                                  message:(nullable NSString *)message {
  self.state = state;
  if (self.listener && [self.listener respondsToSelector:@selector
                                      (onPictureInPictureStateUpdate:
                                                             message:)]) {
    [self.listener onPictureInPictureStateUpdate:state message:message];
  }
}

- (void)setCanStartPictureInPictureAutomaticallyFromInline:
    (BOOL)canStartPictureInPictureAutomaticallyFromInline
    API_AVAILABLE(ios(14.2)) {
  if (_canStartPictureInPictureAutomaticallyFromInline !=
      canStartPictureInPictureAutomaticallyFromInline) {
    _canStartPictureInPictureAutomaticallyFromInline =
        canStartPictureInPictureAutomaticallyFromInline;
    if (self.pipController != nil) {
      self.pipController.canStartPictureInPictureAutomaticallyFromInline =
          canStartPictureInPictureAutomaticallyFromInline;
    }
  }
}

- (void)enableAutoStartPictureInPicture:(TTVideoEngine *)player
    API_AVAILABLE(ios(14.2)) {
  NSLog(@"PIP: Enable Auto Start Picture-in-picture");
  if (@available(iOS 14.2, *)) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [self setCanStartPictureInPictureAutomaticallyFromInline:YES];

      // Root cause (SDK 1.50.x, Release builds):
      // setContentView:completion: → _setContentView: → _setupPiPPlaceHolderView:YES
      // (force=YES) briefly REMOVES sampleBufferView from the window hierarchy.
      // In SDK 1.50.x Release builds this causes AVPictureInPictureController.
      // isPictureInPicturePossible to permanently drop to NO.
      //
      // Fix: when the controller is already set up for this player, skip
      // setContentView entirely.  Just reconnect the pixel-buffer callback and
      // re-arm canStartPictureInPictureAutomaticallyFromInline.  The SDK's own
      // DidStop → Ready transition already calls _setupPiPPlaceHolderView:NO
      // (no forced removal), keeping the view hierarchy intact.
      //
      // Only call the full ensurePipControllerReady (with setContentView) on
      // first-time setup, player changes, or a post-stop rebuild.
      if (!self.pipControllerNeedsRecreateAfterStop &&
          self.pipController != nil &&
          (player == nil || self.player == player)) {
        // Controller already ready for this player — lightweight reconnect only.
        [self applySyncPlayerViewConfigForPipStart];
        [self.player setVideoWrapper:[self createVideoWrapper:self]];
        if (@available(iOS 14.2, *)) {
          self.pipController.canStartPictureInPictureAutomaticallyFromInline =
              YES;
        }
      } else {
        // First-time setup, or player changed — full init required.
        [self ensurePipControllerReady:player completion:nil];
      }
    });
  } else {
    NSLog(@"PIP: Auto-start feature requires iOS 14.2 or later");
  }
}

- (void)disableAutoStartPictureInPicture API_AVAILABLE(ios(14.2)) {
  NSLog(@"PIP: Disable Auto Start Picture-in-picture");
  if (@available(iOS 14.2, *)) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [self setCanStartPictureInPictureAutomaticallyFromInline:NO];
      if (self.pipController) {
        [self destroyPictureInPicture];
      }
    });
  } else {
    NSLog(@"PIP: Auto-start feature requires iOS 14.2 or later");
  }
}

- (void)setAutoHideViewController:(BOOL)autoHideViewController {
  _autoHideViewController = autoHideViewController;
  self.pipController.autoHideViewController = autoHideViewController;
}

// --------------------------------------------------------------------------
// MARK: - AVPictureInPictureSampleBufferPlaybackDelegate (IDENTICAL to master)
// --------------------------------------------------------------------------

- (BOOL)pictureInPictureControllerIsPlaybackPaused:
    (nonnull AVPictureInPictureController *)pictureInPictureController {
  if (self.player) {
    TTVideoEnginePlaybackState state = self.player.playbackState;
    BOOL isPaused = (state == TTVideoEnginePlaybackStatePaused ||
                     state == TTVideoEnginePlaybackStateStopped ||
                     state == TTVideoEnginePlaybackStateError);
    return isPaused;
  }
  NSLog(@"PIP: No player, returning YES (paused)");
  return YES;
}

- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:
    (AVPictureInPictureController *)pictureInPictureController {
  if (!self.player) {
    NSLog(@"PIP: No player, returning infinite time range (live content)");
    return CMTimeRangeMake(kCMTimeZero, kCMTimePositiveInfinity);
  }

  TTVideoEnginePlaybackState state = self.player.playbackState;

  if (state != TTVideoEnginePlaybackStatePlaying &&
      state != TTVideoEnginePlaybackStatePaused) {
    return CMTimeRangeMake(kCMTimeZero, CMTimeMake(100, 1));
  }

  NSTimeInterval duration = self.player.duration;
  CMTime durationTime = CMTimeMakeWithSeconds(duration, NSEC_PER_SEC);
  return CMTimeRangeMake(kCMTimeZero, durationTime);
}

- (void)pictureInPictureController:
            (AVPictureInPictureController *)pictureInPictureController
                        setPlaying:(BOOL)playing {
  if (playing) {
    [self.player play];
  } else {
    [self.player pause];
  }
}

- (void)pictureInPictureController:
            (AVPictureInPictureController *)pictureInPictureController
                    skipByInterval:(CMTime)skipInterval
                 completionHandler:(void (^)(void))completionHandler
    API_AVAILABLE(ios(15.0)) {
  if (!self.player) {
    if (completionHandler) {
      completionHandler();
    }
    return;
  }

  NSTimeInterval currentTime = self.player.currentPlaybackTime;
  NSTimeInterval duration = self.player.duration;
  NSTimeInterval skipSeconds = CMTimeGetSeconds(skipInterval);
  NSTimeInterval targetTime = currentTime + skipSeconds;

  if (targetTime < 0) {
    targetTime = 0;
  } else if (duration > 0 && targetTime > duration) {
    targetTime = duration;
  }

  [self.player setCurrentPlaybackTime:targetTime
                             complete:^(BOOL finished) {
                               if (completionHandler) {
                                 completionHandler();
                               }
                             }];
}

// --------------------------------------------------------------------------
// MARK: - VePictureInPictureDelegate
//   WillStart / DidStart / DidStop: IDENTICAL to master
//   DidStop: + [self disablePipTransform] (no-op when syncConfig not used)
// --------------------------------------------------------------------------

- (void)pictureInPictureController:
            (VePictureInPictureController *)pictureInPictureController
                     statusChanged:(VePictureInPictureStatus)fromStatus
                                to:(VePictureInPictureStatus)toStatus {
  switch (toStatus) {
  case VePictureInPictureStatusIde: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusIde)
                                    message:nil];
  } break;
  case VePictureInPictureStatusPreparing: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusPreparing)
                                    message:nil];
  } break;
  case VePictureInPictureStatusReady: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusReady)
                                    message:nil];
  } break;
  case VePictureInPictureStatusWillStart: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusWillStart)
                                    message:nil];
    if (self.syncPlayerViewConfig) {
      self.savedVideoScaleMode = nil;
      [self applySyncPlayerViewConfigForPipStart];
    }
    [TTVideoEngine setGlobalForKey:VEGSKeyIgnoreGlActive value:@(1)];
  } break;
  case VePictureInPictureStatusDidStart: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusDidStart)
                                    message:nil];
    if (self.syncPlayerViewConfig) {
      [self applySyncPlayerViewConfigForPipStart];
    }
    if (self.listener &&
        [self.listener respondsToSelector:@selector(onStartPictureInPicture)]) {
      [self.listener onStartPictureInPicture];
    }
    if (self.startPipCompletion) {
      void (^completionBlock)(BOOL) = self.startPipCompletion;
      self.startPipCompletion = nil;
      completionBlock(YES);
    }
    break;
  case VePictureInPictureStatusWillChange: {
  } break;
  case VePictureInPictureStatusDidChange: {
  } break;
  case VePictureInPictureStatusRestoreUI: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusRestoreUI)
                                    message:nil];
    if (self.listener && [self.listener respondsToSelector:@selector
                                        (onClickPictureInPictureRestoreBtn)]) {
      [self.listener onClickPictureInPictureRestoreBtn];
    }
  } break;
  case VePictureInPictureStatusWillStop: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusWillStop)
                                    message:nil];
  } break;
  case VePictureInPictureStatusDidStop: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusDidStop)
                                    message:nil];
    // Disable pixel buffer transform (no-op if not active)
    [self disablePipTransform];
    if (self.listener &&
        [self.listener respondsToSelector:@selector(onStopPictureInPicture)]) {
      [self.listener onStopPictureInPicture];
    }
    if (self.savedVideoScaleMode) {
      [self.player setOptionForKey:VEKKEY(VEKKeyViewScaleMode_ENUM)
                             value:self.savedVideoScaleMode];
    }
    if (!self.canStartPictureInPictureAutomaticallyFromInline) {
      [self destroyPictureInPicture];
    } else {
      if (self.pipController != nil && self.player != nil) {
        self.pipControllerNeedsRecreateAfterStop = YES;
      }
    }
    [TTVideoEngine setGlobalForKey:VEGSKeyIgnoreGlActive value:@(0)];
  } break;
  case VePictureInPictureStatusError: {
    [self notifyPictureInPictureStateUpdate:(VePictureInPictureStatusError)
                                    message:pictureInPictureController.error
                                                .localizedDescription];
    if (self.listener &&
        [self.listener respondsToSelector:@selector(onError:extraData:)]) {
      [self.listener onError:pictureInPictureController.error.code
                   extraData:pictureInPictureController.error.userInfo];
    }
    NSLog(@"PIP:error %d", pictureInPictureController.error.code);
    if (self.startPipCompletion) {
      void (^completionBlock)(BOOL) = self.startPipCompletion;
      self.startPipCompletion = nil;
      completionBlock(NO);
    }
  } break;
  }
  }
}

@end
