package com.volcengine.reactnative.veplayer.pictureInpicture;

import android.app.Activity;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.SurfaceTexture;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Gravity;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import com.facebook.react.bridge.ReactContext;
import com.ss.ttvideoengine.TTVideoEngine;
import com.ss.ttvideoengine.VideoEngineCallback;
import com.volcengine.VolcApiEngine.pip.FloatingWindowHelper;
import com.volcengine.VolcApiEngine.pip.SystemPictureInPictureHelper;
import com.volcengine.reactnative.veplayer.BuildConfig;
import com.volcengine.reactnative.veplayer.VeplayerView;
import com.volcengine.reactnative.veplayer.events.PlayerMultiObserver;


/**
 * Manager class that provides easy access to Picture-in-Picture functionality
 * for React Native code.
 */
public class PictureInPictureManager {
  private static final String TAG = "PipManager";
  private static final int ERR_NO = 0;
  private static final int ERR_RETRY = 1;
  private final FloatingWindowHelper mFloatingWindowHelper;
  private static void logD(String msg) {
    if (BuildConfig.DEBUG) {
      Log.d(TAG, msg);
    }
  }

  // Singleton instance
  private static volatile PictureInPictureManager sInstance;
  // Static reference to current activity for system PIP
  private static volatile Activity sCurrentActivity;

  private Context mContext;
  private String mViewKind; // View type
  private SurfaceView mSurfaceView;
  private TextureView mTextureView;
  private TTVideoEngine mPlayer;
  // Flag indicating whether we're in the process of switching to PIP mode
  // Used to control SurfaceView switching state
  private boolean mSwitchingToPip = false;
  private FloatingWindowHelper.Config mConfig = new FloatingWindowHelper.Config(
      16f / 9f, 0, 0, FloatingWindowHelper.Config.VIEW_TYPE_TEXTURE_VIEW, true, true);
  private Listener mListener;
  private PlayerMultiObserver mPlayerObserver;
  // Container view reference for syncing player view config
  private View mContainerView;
  // Whether to sync player view config (size and fill mode)
  private boolean mSyncPlayerViewConfig = false;
  // Picture in picture type: "system" or "floating"
  private String mPictureInPictureType = "floating";
  // React context reference for getting current activity
  private ReactContext mReactContext;
  // Whether to auto-enter PiP when app goes to background (Android 12+)
  private boolean mAutoEnterEnabled = false;
  // Temporarily disable auto-enter when video is paused (user controlled pause)
  private boolean mTempAutoEnterDisabled = false;
  // Flag indicating PiP params need to be updated when activity becomes available
  private boolean mNeedsPipParamsUpdate = false;
  // Track whether we were in PiP mode to detect transitions
  private boolean mWasInPictureInPicture = false;
  // True when fullscreen layout was already applied before enterPictureInPictureMode() so the
  // onPictureInPictureModeChanged entering branch can skip the redundant second apply.
  private boolean mPipLayoutAppliedBeforeEnter = false;

  /** System PiP: expanded player layout + hidden siblings */
  private boolean mSystemPipLayoutActive = false;
  private final List<ViewVisibilitySnap> mPipSavedVisibilities = new ArrayList<>();
  private final List<ViewLayoutSnap> mPipSavedLayouts = new ArrayList<>();
  private Consumer mPipModeChangedConsumer;
  private Activity mPipListenerActivity;

  private static final class ViewVisibilitySnap {
    final View view;
    final int visibility;

    ViewVisibilitySnap(View view, int visibility) {
      this.view = view;
      this.visibility = visibility;
    }
  }

  private static final class ViewLayoutSnap {
    final View view;
    final ViewGroup.LayoutParams layoutParams;

    ViewLayoutSnap(View view, ViewGroup.LayoutParams layoutParams) {
      this.view = view;
      this.layoutParams = layoutParams;
    }
  }

  private static final class ViewZSnap {
    final View view;
    final float elevation;
    final float translationZ;

    ViewZSnap(View view, float elevation, float translationZ) {
      this.view = view;
      this.elevation = elevation;
      this.translationZ = translationZ;
    }
  }

  /** System PiP: saved z / elevation for restore */
  private final List<ViewZSnap> mPipSavedZ = new ArrayList<>();

  /**
   * Set current activity for system picture-in-picture
   * Should be called from React Native Activity
   */
  public static void setCurrentActivity(Activity activity) {
    sCurrentActivity = activity;
  }

  /**
   * Set ReactContext for resolving current Activity.
   *
   * <p>Note: {@link ReactContext#getCurrentActivity()} can be null depending on lifecycle timing.
   * This is an optional signal path; system PiP still prefers resolving Activity from the player
   * view context when available.</p>
   */
  public void setReactContext(@Nullable ReactContext reactContext) {
    mReactContext = reactContext;
  }

  /**
   * Get current activity for system picture-in-picture
   */
  private static Activity getCurrentActivityStatic() {
    return sCurrentActivity;
  }

  private PictureInPictureManager() {
    mFloatingWindowHelper = FloatingWindowHelper.getInstance();
    setupFloatingWindowListener();
  }

  public static synchronized PictureInPictureManager getInstance() {
    if (sInstance == null) {
      sInstance = new PictureInPictureManager();
    }
    return sInstance;
  }

  /**
   * Set the player instance to be managed
   */
  public void setupPlayer(TTVideoEngine player,
                          Context context,
                          String viewKind, // view type
                          View view) {
    mPlayer = player;
    mContext = context;
    mViewKind = viewKind;
    mContainerView = view;

    if (view == null) {
      Log.w(TAG, "setupPlayer: view is null, will retry when view is available");
    } else if ("SurfaceView".equals(viewKind) && view instanceof SurfaceView) {
      mSurfaceView = (SurfaceView) view;
      mTextureView = null;
    } else if ("TextureView".equals(viewKind) && view instanceof TextureView) {
      mTextureView = (TextureView) view;
      mSurfaceView = null;
    } else {
      Log.w(TAG, "setupPlayer: invalid view type " + view.getClass().getSimpleName() + ", expected " + viewKind);
    }

    if (mSurfaceView != null || mTextureView != null) {
      setupFloatingWindowListener();
      ensurePictureInPictureModeChangedListener(getCurrentActivity());
    }

    if (mNeedsPipParamsUpdate) {
      updatePictureInPictureParams();
    }
  }

  /**
   * Setup player without an attached view.
   *
   * <p>Used when the engine's view is not ready (or temporarily missing).
   * This avoids passing a null/undefined view down into the native layer.</p>
   */
  public void setupPlayerWithoutView(TTVideoEngine player,
                                      Context context,
                                      String viewKind) {
    mPlayer = player;
    mContext = context;
    mViewKind = viewKind;
    mContainerView = null;
    mSurfaceView = null;
    mTextureView = null;
  }

  /**
   * Set whether to sync player view config
   * Must be called before startPictureInPicture
   * @param sync Whether to sync player view config
   */
  public void setSyncPlayerViewConfig(boolean sync) {
    boolean wasDifferent = (mSyncPlayerViewConfig != sync);
    mSyncPlayerViewConfig = sync;
    if (wasDifferent && "system".equals(mPictureInPictureType)) {
      updatePictureInPictureParams();
    }
  }

  /**
   * Set picture in picture type
   * Must be called before startPictureInPicture
   * @param type Picture in picture type: "system" or "floating"
   */
  public void setPictureInPictureType(String type) {
    final String newType;
    if ("system".equals(type) || "floating".equals(type)) {
      newType = type;
    } else {
      Log.w(TAG, "setPictureInPictureType: invalid type '" + type + "', using floating");
      newType = "floating";
    }
    if (!"system".equals(newType) && mSystemPipLayoutActive) {
      if (Looper.myLooper() == Looper.getMainLooper()) {
        applySystemPipFullscreenLayout(false);
        mPictureInPictureType = newType;
      } else {
        new Handler(Looper.getMainLooper()).post(() -> {
          applySystemPipFullscreenLayout(false);
          mPictureInPictureType = newType;
        });
      }
      if (mAutoEnterEnabled) {
        mAutoEnterEnabled = false;
      }
      return;
    }
    mPictureInPictureType = newType;
    if (!"system".equals(newType) && mAutoEnterEnabled) {
      mAutoEnterEnabled = false;
    }
  }

  /**
   * Set auto-enter picture-in-picture enabled
   * When enabled, the system will automatically enter PiP mode when the activity goes to background.
   * Only works on Android 12 (API 31) and above.
   * @param enabled true to enable auto-enter PiP, false otherwise
   */
  public void setAutoEnterEnabled(boolean enabled) {
    mAutoEnterEnabled = enabled;
    if ("system".equals(mPictureInPictureType)) {
      updatePictureInPictureParams();
    }
  }

  /**
   * Update PictureInPictureParams for the current activity.
   * This is called when auto-enter enabled/disabled or when config changes.
   */
  private void updatePictureInPictureParams() {
    if (!"system".equals(mPictureInPictureType)) {
      return;
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
      return;
    }

    Activity activity = getCurrentActivity();
    if (activity == null) {
      Log.w(TAG, "updatePictureInPictureParams: activity is null, deferring");
      mNeedsPipParamsUpdate = true;
      return;
    }

    mNeedsPipParamsUpdate = false;

    try {
      float aspectRatio = 16f / 9f;
      int width, height;

      if (mSyncPlayerViewConfig && mContainerView != null) {
        width = mContainerView.getWidth();
        height = mContainerView.getHeight();
      } else if (mPlayer != null) {
        width = mPlayer.getVideoWidth();
        height = mPlayer.getVideoHeight();
      } else {
        width = 0;
        height = 0;
      }

      if (width > 0 && height > 0) {
        aspectRatio = (float) width / height;
      }

      PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
      int denom = 100;
      int num = Math.round(aspectRatio * denom);
      if (num < 1) {
        num = 1;
      }
      builder.setAspectRatio(new android.util.Rational(num, denom));

      if (mContainerView != null && mContainerView.getWidth() > 0 && mContainerView.getHeight() > 0) {
        android.graphics.Rect sourceRectHint = calculateSourceRectHint(mContainerView);
        if (sourceRectHint != null && sourceRectHint.width() > 0 && sourceRectHint.height() > 0) {
          builder.setSourceRectHint(sourceRectHint);
        } else {
          Log.w(TAG, "updatePictureInPictureParams: sourceRectHint is invalid, skipping");
        }
      }

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        boolean actualAutoEnter = mAutoEnterEnabled && !mTempAutoEnterDisabled;
        builder.setAutoEnterEnabled(actualAutoEnter);
      }

      activity.setPictureInPictureParams(builder.build());
    } catch (Exception e) {
      Log.w(TAG, "updatePictureInPictureParams: failed", e);
    }
  }

  /**
   * Calculate the source rect hint for PiP transition animation.
   * This helps the system create a smooth animation from the video view to the PiP window.
   */
  private android.graphics.Rect calculateSourceRectHint(View view) {
    if (view == null) {
      return new android.graphics.Rect(0, 0, 0, 0);
    }

    android.graphics.Rect visibleRect = new android.graphics.Rect();
    boolean success = view.getGlobalVisibleRect(visibleRect);

    int[] locationOnScreen = new int[2];
    view.getLocationOnScreen(locationOnScreen);

    if (!success || visibleRect.isEmpty()) {
      visibleRect.set(locationOnScreen[0], locationOnScreen[1],
                     locationOnScreen[0] + view.getWidth(),
                     locationOnScreen[1] + view.getHeight());
    } else {
      visibleRect.set(locationOnScreen[0], locationOnScreen[1],
                     locationOnScreen[0] + view.getWidth(),
                     locationOnScreen[1] + view.getHeight());
    }

    return visibleRect;
  }

  public void setupConfig(float aspectRatio, int x, int y) {
    mConfig = new FloatingWindowHelper.Config(aspectRatio, x, y,
        FloatingWindowHelper.Config.VIEW_TYPE_TEXTURE_VIEW, false, true);
  }

  /**
   * Query whether a PiP mode is supported without using {@link #mPictureInPictureType}.
   *
   * @param type {@code "system"}, {@code "floating"}, or null/empty to query floating (default)
   * @return whether that mode is supported on this device
   */
  public boolean isPictureInPictureSupportedForType(@Nullable String type) {
    String t = type;
    if (t == null || t.isEmpty()) {
      t = "floating";
    } else if (!"system".equals(t) && !"floating".equals(t)) {
      t = "floating";
    }
    if ("system".equals(t)) {
    return SystemPictureInPictureHelper.isSupported();
  }
  return FloatingWindowHelper.isPictureInPictureSupported();
  }

  /**
   * Same as {@link #isPictureInPictureSupportedForType} with null — queries <strong>floating</strong>
   * window PiP support, not the currently configured engine type.
   */
  public boolean isPictureInPictureSupported() {
    return isPictureInPictureSupportedForType(null);
  }

  /**
   * Check if system picture-in-picture is supported
   * @return true if Android 8.0 or higher
   */
  public boolean isSystemPictureInPictureSupported() {
    return SystemPictureInPictureHelper.isSupported();
  }

  public boolean startPictureInPicture() {
    if ("system".equals(mPictureInPictureType)) {
      return startSystemPictureInPicture();
    } else {
      if (mAutoEnterEnabled) {
        mAutoEnterEnabled = false;
        updatePictureInPictureParams();
      }
      setFloatingWindowSize();
      return startPictureInPicture(mConfig);
    }
  }

  /**
   * Start system picture-in-picture mode (Android 8.0+)
   * @return true if PIP was started, false otherwise
   */
  private boolean startSystemPictureInPicture() {
    if (Looper.myLooper() == Looper.getMainLooper()) {
      return startSystemPictureInPictureOnUiThread();
    }
    final boolean[] result = new boolean[1];
    final CountDownLatch latch = new CountDownLatch(1);
    new Handler(Looper.getMainLooper()).post(() -> {
      try {
        result[0] = startSystemPictureInPictureOnUiThread();
      } finally {
        latch.countDown();
      }
    });
    try {
      if (!latch.await(8, TimeUnit.SECONDS)) {
        Log.e(TAG, "startSystemPictureInPicture: timed out waiting for main thread");
        return false;
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      return false;
    }
    return result[0];
  }

  private boolean startSystemPictureInPictureOnUiThread() {
    if (mPlayer == null) {
      Log.e(TAG, "startSystemPictureInPicture: player is null");
      return false;
    }

    if (!SystemPictureInPictureHelper.isSupported()) {
      Log.e(TAG, "startSystemPictureInPicture: requires API 26+");
      return false;
    }

    Activity activity = getCurrentActivity();
    if (activity == null) {
      Log.e(TAG, "startSystemPictureInPicture: activity is null");
      return false;
    }

    ensurePictureInPictureModeChangedListener(activity);

    // Apply fullscreen layout BEFORE calling enterPictureInPictureMode so the PiP window
    // immediately shows only the player surface without a brief full-page flash.
    // The transition animation still looks correct because sourceRectHint (set via
    // updatePictureInPictureParams / setPictureInPictureParams) tells the system where the
    // video was — the system uses the hint, not the live view position, for the shrink animation.
    applySystemPipFullscreenLayout(true);
    mPipLayoutAppliedBeforeEnter = true;

    // Some OEMs / helper implementations may refuse entering PiP if the player is not in PLAYING state.
    // If caller triggers PiP while paused/buffering, just abort early and tell the caller via logs.
    try {
      int playbackState = mPlayer.getPlaybackState();
      boolean isPlaying = playbackState == TTVideoEngine.PLAYBACK_STATE_PLAYING;
      if (!isPlaying) {
        Log.w(TAG, "startSystemPictureInPicture: player not playing (state=" + playbackState + "), aborting");
        mPipLayoutAppliedBeforeEnter = false;
        applySystemPipFullscreenLayout(false);
        return false;
      }
    } catch (Throwable e) {
      Log.w(TAG, "startSystemPictureInPicture: failed to query playback state", e);
      mPipLayoutAppliedBeforeEnter = false;
      applySystemPipFullscreenLayout(false);
      return false;
    }

    float aspectRatio = 16f / 9f;
    int width, height;
    if (mSyncPlayerViewConfig && mContainerView != null) {
      width = mContainerView.getWidth();
      height = mContainerView.getHeight();
    } else {
      width = mPlayer.getVideoWidth();
      height = mPlayer.getVideoHeight();
    }
    if (width > 0 && height > 0) {
      aspectRatio = (float) width / height;
    }

    boolean actualAutoEnter = mAutoEnterEnabled;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
      actualAutoEnter = mAutoEnterEnabled && !mTempAutoEnterDisabled;
    }

    logD("startSystemPictureInPicture: aspectRatio=" + aspectRatio);
    boolean result = SystemPictureInPictureHelper.enterPictureInPicture(
        activity, aspectRatio, actualAutoEnter);

    if (result) {
      if (mListener != null) {
        mListener.onStartPictureInPicture();
      }
    } else {
      Log.e(TAG, "startSystemPictureInPicture: enterPictureInPictureMode failed");
      mPipLayoutAppliedBeforeEnter = false;
      applySystemPipFullscreenLayout(false);
    }

    return result;
  }

  /**
   * Resolves the host Activity for system PiP. Prefer the player view (after {@link #setupPlayer})
   * so we match the window that owns the surface; RN {@link ReactContext#getCurrentActivity()} can
   * be null on some call paths while the view still has a resolvable context.
   */
  private Activity getCurrentActivity() {
    if (mContainerView != null) {
      Activity activity = getActivityFromView(mContainerView);
      if (activity != null) {
        return activity;
      }
    }
    if (mReactContext != null) {
      Activity activity = mReactContext.getCurrentActivity();
      if (activity != null) {
        return activity;
      }
    }
    return sCurrentActivity;
  }

  private static Activity getActivityFromView(View view) {
    if (view == null) {
      return null;
    }
    Context ctx = view.getContext();
    if (ctx instanceof ReactContext) {
      Activity fromReact = ((ReactContext) ctx).getCurrentActivity();
      if (fromReact != null) {
        return fromReact;
      }
    }
    while (ctx instanceof ContextWrapper) {
      if (ctx instanceof Activity) {
        return (Activity) ctx;
      }
      ctx = ((ContextWrapper) ctx).getBaseContext();
    }
    return null;
  }

  /**
   * Subscribe to system PiP mode changes so we can fullscreen the player surface and restore
   * the layout when leaving PiP.
   */
  public void ensurePictureInPictureModeChangedListener(Activity activity) {
    if (activity == null) {
      activity = getCurrentActivity();
    }
    if (activity == null || !(activity instanceof ComponentActivity)) {
      logD("ensurePictureInPictureModeChangedListener: activity unavailable or not ComponentActivity");
      return;
    }
    ComponentActivity ca = (ComponentActivity) activity;
    if (mPipListenerActivity == activity && mPipModeChangedConsumer != null) {
      if (mNeedsPipParamsUpdate) {
        updatePictureInPictureParams();
      }
      return;
    }
    if (mPipListenerActivity instanceof ComponentActivity && mPipModeChangedConsumer != null) {
      ((ComponentActivity) mPipListenerActivity)
          .removeOnPictureInPictureModeChangedListener(mPipModeChangedConsumer);
    }
    final Activity finalActivity = activity;
    mPipModeChangedConsumer =
        (Object info) -> {
          if (!"system".equals(mPictureInPictureType)) {
            return;
          }
          boolean isInPiP = isInPictureInPictureMode(info);

          if (isInPiP && !mWasInPictureInPicture) {
            logD("onPictureInPictureModeChanged: entering PiP");
            if (mListener != null) {
              mListener.onStartPictureInPicture();
            }
          } else if (!isInPiP && mWasInPictureInPicture) {
            logD("onPictureInPictureModeChanged: exiting PiP");
            if (mListener != null) {
              mListener.onStopPictureInPicture();
              mListener.onClickPictureInPictureRestoreBtn(finalActivity);
            }
          }

          mWasInPictureInPicture = isInPiP;
          if (isInPiP && mPipLayoutAppliedBeforeEnter) {
            // Layout was already applied before enterPictureInPictureMode() to avoid the
            // full-page flash; just clear the flag and skip the redundant apply.
            mPipLayoutAppliedBeforeEnter = false;
          } else {
            applySystemPipFullscreenLayout(isInPiP);
          }
        };
    ca.addOnPictureInPictureModeChangedListener(mPipModeChangedConsumer);
    mPipListenerActivity = activity;
    mWasInPictureInPicture = ca.isInPictureInPictureMode();
    logD("ensurePictureInPictureModeChangedListener: registered for " + activity.getClass().getSimpleName());

    if (mNeedsPipParamsUpdate) {
      updatePictureInPictureParams();
    }
  }

  private static boolean isInPictureInPictureMode(Object info) {
    if (info instanceof Boolean) {
      return (Boolean) info;
    }
    if (info == null) {
      return false;
    }
    try {
      // androidx.activity provides PictureInPictureModeChangedInfo with this method.
      java.lang.reflect.Method m = info.getClass().getMethod("isInPictureInPictureMode");
      Object res = m.invoke(info);
      return res instanceof Boolean && (Boolean) res;
    } catch (Throwable ignored) {
      return false;
    }
  }

  private static boolean isHostInSubtree(View root, View host) {
    if (root == host) {
      return true;
    }
    if (!(root instanceof ViewGroup)) {
      return false;
    }
    ViewGroup vg = (ViewGroup) root;
    for (int i = 0; i < vg.getChildCount(); i++) {
      if (isHostInSubtree(vg.getChildAt(i), host)) {
        return true;
      }
    }
    return false;
  }

  private void saveLayoutSnapshot(View view) {
    ViewGroup parent = view.getParent() instanceof ViewGroup ? (ViewGroup) view.getParent() : null;
    if (parent == null) {
      return;
    }
    ViewGroup.LayoutParams current = view.getLayoutParams();
    if (current == null) {
      return;
    }
    ViewGroup.LayoutParams copy;
    if (current instanceof FrameLayout.LayoutParams) {
      copy = new FrameLayout.LayoutParams((FrameLayout.LayoutParams) current);
    } else if (current instanceof ViewGroup.MarginLayoutParams) {
      // CoordinatorLayout.LayoutParams / LinearLayout.LayoutParams / etc.
      copy = new ViewGroup.MarginLayoutParams((ViewGroup.MarginLayoutParams) current);
    } else {
      copy = new ViewGroup.LayoutParams(current);
    }
    mPipSavedLayouts.add(new ViewLayoutSnap(view, copy));
  }

  /**
   * Restores width/height/margins onto the view's <em>existing</em> LayoutParams so the concrete
   * subclass (e.g. CoordinatorLayout.LayoutParams) is preserved — replacing with a plain
   * MarginLayoutParams causes ClassCastException when the parent is CoordinatorLayout.
   */
  private void restoreLayoutParamsFromSnapshot(View view, ViewGroup.LayoutParams saved) {
    if (view == null || saved == null) {
      return;
    }
    ViewGroup.LayoutParams cur = view.getLayoutParams();
    if (cur == null) {
      // ViewGroup.generateLayoutParams(LayoutParams) is protected — cannot rebuild from outside.
      // Snapshotted views are always attached with non-null LayoutParams in normal PiP flow.
      Log.w(TAG, "restoreLayoutParamsFromSnapshot: LayoutParams is null, skipping");
      return;
    }
    cur.width = saved.width;
    cur.height = saved.height;
    if (cur instanceof ViewGroup.MarginLayoutParams && saved instanceof ViewGroup.MarginLayoutParams) {
      ViewGroup.MarginLayoutParams cm = (ViewGroup.MarginLayoutParams) cur;
      ViewGroup.MarginLayoutParams sm = (ViewGroup.MarginLayoutParams) saved;
      cm.leftMargin = sm.leftMargin;
      cm.topMargin = sm.topMargin;
      cm.rightMargin = sm.rightMargin;
      cm.bottomMargin = sm.bottomMargin;
    }
    if (cur instanceof FrameLayout.LayoutParams && saved instanceof FrameLayout.LayoutParams) {
      ((FrameLayout.LayoutParams) cur).gravity = ((FrameLayout.LayoutParams) saved).gravity;
    }
    view.setLayoutParams(cur);
  }

  private void saveAndRaiseElevation(View v) {
    if (v == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
      return;
    }
    mPipSavedZ.add(new ViewZSnap(v, v.getElevation(), v.getTranslationZ()));
    float d = v.getResources().getDisplayMetrics().density;
    v.setElevation(Math.max(v.getElevation(), 32f * d));
    v.setTranslationZ(Math.max(v.getTranslationZ(), 6f * d));
  }

  private void restorePipZOrder() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
      mPipSavedZ.clear();
      return;
    }
    for (int i = mPipSavedZ.size() - 1; i >= 0; i--) {
      ViewZSnap s = mPipSavedZ.get(i);
      s.view.setElevation(s.elevation);
      s.view.setTranslationZ(s.translationZ);
    }
    mPipSavedZ.clear();
  }

  /**
   * Brings the player surface branch to the top of each parent so AppBar / siblings do not cover
   * it after we set them GONE (CoordinatorLayout draw order, etc.).
   */
  private void bringSystemPipBranchToFront(View host) {
    if (mContainerView != null) {
      ViewGroup p = mContainerView.getParent() instanceof ViewGroup ? (ViewGroup) mContainerView.getParent() : null;
      if (p != null) {
        p.bringChildToFront(mContainerView);
      }
    }
    View child = host;
    while (true) {
      ViewParent vp = child.getParent();
      if (!(vp instanceof ViewGroup)) {
        break;
      }
      ViewGroup parent = (ViewGroup) vp;
      parent.bringChildToFront(child);
      if (parent.getId() == android.R.id.content) {
        break;
      }
      child = parent;
    }
  }

  private void raiseSystemPipBranchElevation(View host) {
    if (mContainerView != null) {
      saveAndRaiseElevation(mContainerView);
    }
    View child = host;
    while (true) {
      saveAndRaiseElevation(child);
      ViewParent vp = child.getParent();
      if (!(vp instanceof ViewGroup)) {
        break;
      }
      ViewGroup parent = (ViewGroup) vp;
      if (parent.getId() == android.R.id.content) {
        break;
      }
      child = parent;
    }
  }

  /**
   * Notifies VeplayerView about PiP state changes.
   * VeplayerView will handle view reparenting internally.
   */
  private void notifyVeplayerViewPictureInPicture(@Nullable View containerView, boolean entering) {
    VeplayerView veplayerView = findVeplayerView(containerView);
    if (veplayerView != null) {
      if (entering) {
        veplayerView.enterPictureInPictureMode();
      } else {
        veplayerView.exitPictureInPictureMode();
      }
    } else {
      Log.w(TAG, "notifyVeplayerViewPictureInPicture: VeplayerView not found");
    }
  }

  /**
   * Finds the VeplayerView from a given view.
   * Performs a comprehensive search of the entire view hierarchy.
   */
  private VeplayerView findVeplayerView(@Nullable View view) {
    if (view == null) {
      return null;
    }

    if (view instanceof VeplayerView) {
      return (VeplayerView) view;
    }

    // Search up the parent hierarchy
    ViewParent vp = view.getParent();
    int depth = 0;
    while (vp instanceof View) {
      View v = (View) vp;
      if (v instanceof VeplayerView) {
        return (VeplayerView) v;
      }
      vp = v.getParent();
      depth++;
      if (depth > 20) {
        Log.w(TAG, "findVeplayerView: parent hierarchy too deep, aborting upward search");
        break;
      }
    }

    // Search down the view hierarchy
    if (view instanceof ViewGroup) {
      VeplayerView found = findVeplayerViewInTree((ViewGroup) view, 0);
      if (found != null) {
        return found;
      }
    }

    // Fallback: search from activity decor view
    if (view instanceof TextureView || view instanceof SurfaceView) {
      try {
        Activity activity = getCurrentActivity();
        if (activity != null) {
          VeplayerView found = findVeplayerViewInTree(
            (ViewGroup) activity.getWindow().getDecorView(), 0);
          if (found != null) {
            return found;
          }
        }
      } catch (Exception e) {
        Log.w(TAG, "findVeplayerView: activity fallback search failed", e);
      }
    }

    Log.w(TAG, "findVeplayerView: VeplayerView not found");
    return null;
  }

  /**
   * Recursively searches for VeplayerView in a view tree.
   */
  private static VeplayerView findVeplayerViewInTree(ViewGroup root, int depth) {
    if (depth > 30) {
      return null; // Prevent stack overflow
    }

    for (int i = 0; i < root.getChildCount(); i++) {
      View child = root.getChildAt(i);

      if (child instanceof VeplayerView) {
        return (VeplayerView) child;
      }

      if (child instanceof ViewGroup) {
        VeplayerView found = findVeplayerViewInTree((ViewGroup) child, depth + 1);
        if (found != null) {
          return found;
        }
      }
    }
    return null;
  }

  private void applySystemPipFullscreenLayout(boolean entering) {
    if (!"system".equals(mPictureInPictureType)) {
      return;
    }

    notifyVeplayerViewPictureInPicture(mContainerView, entering);

    mSystemPipLayoutActive = entering;

    if (!entering) {
      mPipSavedVisibilities.clear();
      mPipSavedLayouts.clear();
    }
  }

  public void setFloatingWindowSize() {
    if (mPlayer != null) {
      int width, height;

      if (mSyncPlayerViewConfig && mContainerView != null) {
        width = mContainerView.getWidth();
        height = mContainerView.getHeight();
      } else {
        width = mPlayer.getVideoWidth();
        height = mPlayer.getVideoHeight();
      }

      float aspectRatio = 16f / 9f;
      if (width > 0 && height > 0) {
        aspectRatio = (float) width / height;
      } else {
        Log.w(TAG, "setFloatingWindowSize: invalid dimensions, using default aspect ratio");
      }

      setupConfig(aspectRatio, mConfig.x, mConfig.y);
    }
  }

  /**
   * Start picture-in-picture mode
   * @return true if PIP was started, false otherwise
   */
  public boolean startPictureInPicture(FloatingWindowHelper.Config config) {
    logD("startPictureInPicture: floating");
    if (mPlayer == null) {
      Log.e(TAG, "startPictureInPicture: player is null");
      return false;
    }

    try {
      int playbackState = mPlayer.getPlaybackState();
      boolean isPlaying = (playbackState == TTVideoEngine.PLAYBACK_STATE_PLAYING);
      if (!isPlaying) {
        Log.w(TAG, "startPictureInPicture: player not playing (state=" + playbackState + "), aborting");
        return false;
      }
    } catch (Throwable e) {
      Log.w(TAG, "startPictureInPicture: failed to query playback state", e);
      return false;
    }

    // Guard: never open floating window while system PiP is active.
    // Otherwise we can end up with two PiP windows (system + floating) at the same time.
    Activity activity = getCurrentActivity();
    if (SystemPictureInPictureHelper.isActive(activity)) {
      Log.w(TAG, "startPictureInPicture: system PiP is active, skipping floating window");
      return false;
    }

    if (mFloatingWindowHelper.isOpen()) {
      logD("startPictureInPicture: already open, reconnecting player");
      View pipView = mFloatingWindowHelper.getCurrentView();
      if (pipView != null && mPlayer != null) {
        if (pipView instanceof TextureView) {
          TextureView pipTextureView = (TextureView) pipView;
          SurfaceTexture surfaceTexture = pipTextureView.getSurfaceTexture();
          if (surfaceTexture != null) {
            mPlayer.setSurface(new Surface(surfaceTexture));
            mPlayer.forceDraw();
          } else {
            pipTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
              @Override
              public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                if (mPlayer != null) {
                  mPlayer.setSurface(new Surface(surface));
                  mPlayer.forceDraw();
                }
              }

              @Override
              public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}

              @Override
              public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                return false;
              }

              @Override
              public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
            });
          }
        } else if (pipView instanceof SurfaceView) {
          mPlayer.setSurfaceHolder(((SurfaceView) pipView).getHolder());
        }

        int playbackState = mPlayer.getPlaybackState();
        mFloatingWindowHelper.updatePlaybackState(playbackState == 1);
        mFloatingWindowHelper.updateConfig(mConfig.aspectRatio);
      }

      return true;
    }

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M &&
        !android.provider.Settings.canDrawOverlays(mContext)) {
      mFloatingWindowHelper.requestOverlayPermission(mContext);
      return false;
    }

    mSwitchingToPip = true;

    if (mAutoEnterEnabled) {
      mAutoEnterEnabled = false;
      updatePictureInPictureParams();
    }

    mFloatingWindowHelper.openFloatingWindow(mContext, config, new HashMap<>());
    return true;
  }

  /**
   * Stop picture-in-picture mode
   */
  public void stopPictureInPicture() {
    logD("stopPictureInPicture");

    if ("system".equals(mPictureInPictureType)) {
      Runnable restoreLayout =
          () -> {
            if (mSystemPipLayoutActive) {
              applySystemPipFullscreenLayout(false);
            }
          };
      if (Looper.myLooper() == Looper.getMainLooper()) {
        restoreLayout.run();
      } else {
        new Handler(Looper.getMainLooper()).post(restoreLayout);
      }
      if (mListener != null) {
        mListener.onStopPictureInPicture();
      }
    } else {
      if (mFloatingWindowHelper.isOpen() && mContext != null) {
        mFloatingWindowHelper.closeFloatingWindow(mContext);
      }
    }

    mSwitchingToPip = false;
  }

  /**
   * Check if picture-in-picture is active
   *
   * @return true if PIP is active, false otherwise
   */
  public boolean isPictureInPictureActive() {
    if ("system".equals(mPictureInPictureType)) {
      return SystemPictureInPictureHelper.isActive(getCurrentActivity());
    } else {
      return mFloatingWindowHelper.isOpen();
    }
  }

  /**
   * Request overlay permission if needed
   *
   * @param context Android context
   */
  public void requestOverlayPermission(Context context) {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
      if (!android.provider.Settings.canDrawOverlays(context)) {
        mFloatingWindowHelper.requestOverlayPermission(context);
      }
    }
  }

  /**
   * Set up floating window listener
   */
  private void setupFloatingWindowListener() {
    mFloatingWindowHelper.setEventListener(
        new FloatingWindowHelper.Listener() {
          @Override
          public void onOpenFloatingWindowResult(
              int errCode, Map<String, Object> extraData) {
            if (errCode == ERR_RETRY) {
              startPictureInPicture(mConfig);
              return;
            }
            if (mListener != null) {
              if (errCode == ERR_NO) {
                mListener.onStartPictureInPicture();
                int playbackState = mPlayer.getPlaybackState();
                if (mFloatingWindowHelper.isOpen()) {
                  mFloatingWindowHelper.updatePlaybackState(playbackState == 1);
                }
              } else {
                mListener.onError(errCode, extraData);
              }
            }
          }

          @Override
          public void onWindowViewUpdate(View windowView) {
            if (mSwitchingToPip && mPlayer != null && windowView != null) {
              if (windowView instanceof TextureView) {
                TextureView textureView = (TextureView) windowView;
                SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
                if (surfaceTexture != null) {
                  mPlayer.setSurface(new Surface(surfaceTexture));
                  mSwitchingToPip = false;
                } else {
                  textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                    @Override
                    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                      if (mPlayer != null && mSwitchingToPip) {
                        mPlayer.setSurface(new Surface(surface));
                        mSwitchingToPip = false;
                      }
                    }

                    @Override
                    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}

                    @Override
                    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                      return false;
                    }

                    @Override
                    public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
                  });
                }
              } else if (windowView instanceof SurfaceView) {
                mPlayer.setSurfaceHolder(((SurfaceView) windowView).getHolder());
                mSwitchingToPip = false;
              }
            }
          }

          @Override
          public void onClickFloatingWindow(Context context) {
            if (mListener != null) {
              mListener.onClickPictureInPicture();
            }
          }

          @Override
          public void onClickFloatingWindowCloseBtn(Context context) {
            logD("onClickFloatingWindowCloseBtn");
            if (mListener != null) {
              mListener.onClickPictureInPictureCloseBtn(context);
            }
            if (mPlayer != null) {
              mPlayer.pause();
            }
          }

          @Override
          public void onClickFloatingWindowRestoreBtn(Context context) {
            logD("onClickFloatingWindowRestoreBtn");
            if (mListener != null) {
              mListener.onClickPictureInPictureRestoreBtn(context);
            }
          }

          @Override
          public void onCustomBtnClick(Context context, String buttonType, Map<String, Object> extraData) {
            logD("onCustomBtnClick: " + buttonType);
            if (mPlayer != null) {
              if ("play".equals(buttonType)) {
                mPlayer.play();
              } else if ("pause".equals(buttonType)) {
                mPlayer.pause();
              }
            }
          }

          @Override
          public void onCloseFloatingWindow(Map<String, Object> extraData) {
            mSwitchingToPip = false;
            if (mPlayer != null) {
              logD("onCloseFloatingWindow: restoring original surface");
              if ("SurfaceView".equals(mViewKind) && mSurfaceView != null) {
                mPlayer.setSurfaceHolder(mSurfaceView.getHolder());
                mPlayer.forceDraw();
              } else if (Objects.equals(mViewKind, "TextureView") && mTextureView != null) {
                scheduleTextureViewRestore();
              }
            }

            if (mListener != null) {
              mListener.onStopPictureInPicture();
            }
          }

          @Override
          public void onFloatingWindowScale(Context context, boolean isEnlarged,
              int fromWidth, int fromHeight,
              int toWidth, int toHeight) {
            if (mPlayer != null) {
              if (mPlayer.getPlaybackState() == TTVideoEngine.PLAYBACK_STATE_PAUSED) {
                // 暂停时，缩放窗口需要forceDraw，重新绘制下视图
                mPlayer.forceDraw();
              }
            }
          }
        });
  }

  public void setListener(Listener listener) { mListener = listener; }

  /**
   * Set player observer
   */
  public void setPlayerObserver(PlayerMultiObserver playerObserver) {
    if (mPlayerObserver != null) {
      mPlayerObserver.removeObserver("pip_manager");
      mPlayerObserver = null;
    }

    mPlayerObserver = playerObserver;

    setPlayerObserver();
  }

  /**
   * Get player observer instance for use by FloatingWindowService
   */
  public PlayerMultiObserver getPlayerObserver() {
    return mPlayerObserver;
  }


  private void updateVideoSize(TTVideoEngine engine) {
    int videoWidth = engine.getVideoWidth();
    int videoHeight = engine.getVideoHeight();

    if (videoWidth > 0 && videoHeight > 0) {
      float aspectRatio = (float) videoWidth / videoHeight;
      setupConfig(aspectRatio, mConfig.x, mConfig.y);
      int duration = engine.getDuration();
      int currentTime = engine.getCurrentPlaybackTime();

      if (mFloatingWindowHelper != null && mFloatingWindowHelper.isOpen()) {
        mFloatingWindowHelper.updateProgress(currentTime, duration);
        mFloatingWindowHelper.updateConfig(mConfig.aspectRatio);
      }

      if ("system".equals(mPictureInPictureType)) {
        updatePictureInPictureParams();
      }
    } else {
      Log.w(TAG, "updateVideoSize: invalid dimensions");
    }
  }

  /**
   * Create and set up player observer (external call)
   */
  public void setPlayerObserver() {
    if (mPlayer == null) {
      Log.w(TAG, "setPlayerObserver: player is null");
      return;
    }

    if (mPlayerObserver == null) {
      Log.w(TAG, "setPlayerObserver: observer is null");
      return;
    }

    mPlayerObserver.addObserverForCallback("pip_manager", new VideoEngineCallback() {
      @Override
      public void onPlaybackStateChanged(TTVideoEngine engine, int playbackState) {
        boolean isPlaying = (playbackState == 1);
        int duration = engine.getDuration();
        int currentTime = engine.getCurrentPlaybackTime();

        if ("system".equals(mPictureInPictureType) && mAutoEnterEnabled) {
          if (isPlaying) {
            if (mTempAutoEnterDisabled) {
              mTempAutoEnterDisabled = false;
              if (getCurrentActivity() != null) {
                updatePictureInPictureParams();
              }
            }
          } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !mTempAutoEnterDisabled) {
              mTempAutoEnterDisabled = true;
              if (getCurrentActivity() != null) {
                updatePictureInPictureParams();
              }
            }
          }
        }

        if (mFloatingWindowHelper != null && mFloatingWindowHelper.isOpen()) {
          mFloatingWindowHelper.updatePlaybackState(isPlaying);
          mFloatingWindowHelper.updateProgress(currentTime, duration);
        }
      }

      @Override
      public void onVideoSizeChanged(TTVideoEngine engine, int width, int height) {
        updateVideoSize(engine);
      }

      @Override
      public void onCurrentPlaybackTimeUpdate(TTVideoEngine engine, int currentPlaybackTime) {
        int duration = engine.getDuration();
        int playbackState = engine.getPlaybackState();
        boolean isPlaying = (playbackState == 1);
        if (mFloatingWindowHelper != null && mFloatingWindowHelper.isOpen()) {
          // Some devices can miss onPlaybackStateChanged or receive it out-of-order.
          // Use time updates as a periodic truth source to keep the floating window UI in sync.
          mFloatingWindowHelper.updatePlaybackState(isPlaying);
          mFloatingWindowHelper.updateProgress(currentPlaybackTime, duration);
        }
      }

      @Override
      public void onPrepared(TTVideoEngine engine) {
        int duration = engine.getDuration();
        int currentTime = engine.getCurrentPlaybackTime();
        if (mFloatingWindowHelper != null && mFloatingWindowHelper.isOpen()) {
          mFloatingWindowHelper.updateProgress(currentTime, duration);
        }

        if ("system".equals(mPictureInPictureType)) {
          int videoWidth = engine.getVideoWidth();
          int videoHeight = engine.getVideoHeight();
          if (videoWidth > 0 && videoHeight > 0) {
            Activity activity = getCurrentActivity();
            if (activity != null) {
              ensurePictureInPictureModeChangedListener(activity);
            } else {
              Log.w(TAG, "onPrepared: activity is null, cannot register PiP listener");
            }
            updatePictureInPictureParams();
          }
        }
      }
    });

    // Check if video is already prepared (onPrepared might have been called before observer was set)
    if (mPlayer != null && "system".equals(mPictureInPictureType)) {
      int videoWidth = mPlayer.getVideoWidth();
      int videoHeight = mPlayer.getVideoHeight();
      if (videoWidth > 0 && videoHeight > 0) {
        Activity activity = getCurrentActivity();
        if (activity != null) {
          ensurePictureInPictureModeChangedListener(activity);
        } else {
          Log.w(TAG, "setPlayerObserver: video already prepared but activity is null");
        }
        updatePictureInPictureParams();
      }
    }
  }

  /**
   * Try to rebind via VePlayerHelper, utilizing VeplayerView's normal flow
   */
  private boolean tryRestoreViaVePlayerHelper() {
    try {
      if (mTextureView.getParent() instanceof com.volcengine.reactnative.veplayer.VeplayerView) {
        com.volcengine.reactnative.veplayer.VePlayerHelper.engineBindView(mPlayer, mTextureView);
        return true;
      }
    } catch (Exception e) {
      Log.w(TAG, "tryRestoreViaVePlayerHelper: failed", e);
    }
    return false;
  }

  /**
   * Delayed TextureView restoration, giving TextureView time to prepare
   */
  private void scheduleTextureViewRestore() {
    new Handler(android.os.Looper.getMainLooper()).postDelayed(
        this::retryRestoreTextureViewSurface, 100);
  }

  /**
   * Retry TextureView Surface restoration
   */
  private void retryRestoreTextureViewSurface() {
    if (mPlayer == null || mTextureView == null) {
      return;
    }

    if (tryRestoreViaVePlayerHelper()) {
      return;
    }

    SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
    if (surfaceTexture != null) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        if (surfaceTexture.isReleased()) {
          Log.w(TAG, "retryRestoreTextureViewSurface: SurfaceTexture is released");
          return;
        }
      }

      try {
        mPlayer.setSurface(new Surface(surfaceTexture));
        mPlayer.forceDraw();
      } catch (Exception e) {
        Log.e(TAG, "retryRestoreTextureViewSurface: failed", e);
      }
    } else {
      Log.w(TAG, "retryRestoreTextureViewSurface: SurfaceTexture is null");
    }
  }

  /**
   * Clean up player observer
   */
  private void clearPlayerObserver() {
    if (mPlayerObserver != null) {
      mPlayerObserver.removeObserver("pip_manager");
    }
  }

  /**
   * Enable auto-start picture-in-picture for system PiP (Android 12+).
   * When enabled, the system will automatically enter PiP mode when the activity goes to background.
   * This is a no-op for floating window PiP.
   */
  public void enableAutoStartPictureInPicture() {
    if ("system".equals(mPictureInPictureType)) {
      mAutoEnterEnabled = true;
      mTempAutoEnterDisabled = false;
      updatePictureInPictureParams();

      Activity activity = getCurrentActivity();
      if (activity != null) {
        ensurePictureInPictureModeChangedListener(activity);
      }
    }
  }

  /**
   * Disable auto-start picture-in-picture.
   */
  public void disableAutoStartPictureInPicture() {
    if ("system".equals(mPictureInPictureType)) {
      mAutoEnterEnabled = false;
      updatePictureInPictureParams();
    }
  }

  public interface Listener {
    /**
     * Callback when picture-in-picture mode starts
     * Default empty implementation, subclasses can override as needed
     */
    public default void onStartPictureInPicture() {
      // Empty implementation, subclasses can override as needed
    }

    /**
     * Callback when picture-in-picture mode stops
     * Default empty implementation, subclasses can override as needed
     */
    public default void onStopPictureInPicture() {
      // Empty implementation, subclasses can override as needed
    }

    /**
     * Callback when the picture-in-picture window is clicked
     * Default empty implementation, subclasses can override as needed
     */
    public default void onClickPictureInPicture() {
      // Empty implementation, subclasses can override as needed
    }

    /**
     * Callback when the close button is clicked
     * Default empty implementation, subclasses can override as needed
     */
    public default void onClickPictureInPictureCloseBtn(Context context) {
      // Empty implementation, subclasses can override as needed
    }

    public default void onClickPictureInPictureRestoreBtn(Context context) {
      // Empty implementation, subclasses can override as needed
    }

    /**
     * Error callback
     * @param errCode Error code, 0 means success, other values indicate errors
     * @param extraData Additional data, can be used to pass error information
     * Default empty implementation, subclasses can override as needed
     */
    public default void onError(int errCode, Map<String, Object> extraData) {
      // Empty implementation, subclasses can override as needed
    }
  }
  ;
}
