package com.foxx.androidcast.sender;

/*********************************************************************
 * SenderScreenPreview.java
 * Created at: Sun 17 May 2026 18:17:14 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class SenderScreenPreview
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (4 commits, 219 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	b9b6dbedccd033a03ad46b7c964368a50143de6c208d2b14a4dd51ad359e4237
**********************************************************************/
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.view.WindowManager;

import com.foxx.androidcast.CastActiveState;
import com.foxx.androidcast.CastSettings;

/**
 * Live screen preview via MediaProjection into a {@link TextureView} (prep only, not the cast encoder).
 */
public final class SenderScreenPreview {
    private static final String TAG = "SenderScreenPreview";
    private static final int PREVIEW_DPI = 160;

    private final Context appContext;
    private MediaProjection projection;
    private VirtualDisplay virtualDisplay;
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private int resultCode;
    private Intent resultData;
    private TextureView boundTexture;
    private CastSettings.CaptureMode boundMode;

    public SenderScreenPreview(Context context) {
        appContext = context.getApplicationContext();
    }

    public boolean needsProjection(CastSettings.CaptureMode mode) {
        return mode == CastSettings.CaptureMode.FULL_SCREEN
                || mode == CastSettings.CaptureMode.USER_CHOICE;
    }

    public void setProjectionResult(int resultCode, Intent data) {
        this.resultCode = resultCode;
        this.resultData = data != null ? new Intent(data) : null;
    }

    public Intent getProjectionData() {
        return resultData != null ? new Intent(resultData) : null;
    }

    public int getProjectionResultCode() {
        return resultCode;
    }

    /** User granted capture consent (may not have an active {@link MediaProjection} after pause). */
    public boolean hasConsent() {
        return resultCode == Activity.RESULT_OK && resultData != null;
    }

    public boolean hasProjection() {
        return hasConsent();
    }

    public void attach(TextureView textureView, CastSettings.CaptureMode mode) {
        if (CastActiveState.isSenderCasting() || !needsProjection(mode) || !hasConsent()) {
            return;
        }
        boundTexture = textureView;
        boundMode = mode;
        textureView.setSurfaceTextureListener(surfaceListener);
        textureView.post(() -> ensureVirtualDisplay(textureView, mode));
    }

    private final TextureView.SurfaceTextureListener surfaceListener =
            new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    if (boundTexture != null && boundMode != null) {
                        ensureVirtualDisplay(boundTexture, boundMode);
                    }
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                    if (boundTexture != null && boundMode != null && width > 0 && height > 0) {
                        ensureVirtualDisplay(boundTexture, boundMode);
                    }
                }

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

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
            };

    private void ensureProjection() {
        if (projection != null || resultData == null) {
            return;
        }
        if (!CastActiveState.isSenderCasting() && !SenderPreviewService.isRunning()) {
            SenderPreviewService.start(appContext);
        }
        MediaProjectionManager mgr =
                appContext.getSystemService(MediaProjectionManager.class);
        projection = mgr.getMediaProjection(resultCode, resultData);
        if (projection == null) {
            Log.w(TAG, "MediaProjection unavailable for preview");
            return;
        }
        projection.registerCallback(new MediaProjection.Callback() {
            @Override
            public void onStop() {
                pausePreview();
                projection = null;
            }
        }, mainHandler);
    }

    private void ensureVirtualDisplay(TextureView textureView, CastSettings.CaptureMode mode) {
        if (CastActiveState.isSenderCasting() || !needsProjection(mode) || !hasConsent()) {
            return;
        }
        try {
            ensureProjection();
            if (projection == null) {
                return;
            }
            DisplayMetrics metrics = new DisplayMetrics();
            WindowManager wm = (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getRealMetrics(metrics);
            int viewW = textureView.getWidth();
            int viewH = textureView.getHeight();
            if (viewW <= 0 || viewH <= 0) {
                textureView.post(() -> ensureVirtualDisplay(textureView, mode));
                return;
            }
            int w = Math.max(320, viewW);
            int h = Math.max(240, viewH);
            SurfaceTexture st = textureView.getSurfaceTexture();
            if (st == null) {
                return;
            }
            st.setDefaultBufferSize(w, h);
            if (virtualDisplay != null) {
                virtualDisplay.resize(w, h, PREVIEW_DPI);
                virtualDisplay.setSurface(new Surface(st));
                Log.i(TAG, "Screen preview resized " + w + "x" + h);
                return;
            }
            virtualDisplay = projection.createVirtualDisplay(
                    "CastPreview",
                    w,
                    h,
                    PREVIEW_DPI,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                    new Surface(st),
                    null,
                    mainHandler);
            Log.i(TAG, "Screen preview " + w + "x" + h);
        } catch (Exception e) {
            Log.w(TAG, "Screen preview failed: " + e.getMessage(), e);
            pausePreview();
        }
    }

    /** Tear down preview display only; keep consent for resume or cast START. */
    public void pausePreview() {
        if (virtualDisplay != null) {
            virtualDisplay.release();
            virtualDisplay = null;
        }
    }

    public void stop() {
        boundTexture = null;
        boundMode = null;
        pausePreview();
        if (projection != null) {
            try {
                projection.stop();
            } catch (Exception ignored) {
            }
            projection = null;
        }
        if (!CastActiveState.isSenderCasting()) {
            SenderPreviewService.stop(appContext);
        }
    }

    /** Pause preview surface only; keep MediaProjection consent for cast START. */
    public void pauseForCastStart() {
        boundTexture = null;
        boundMode = null;
        pausePreview();
        if (projection != null) {
            try {
                projection.stop();
            } catch (Exception ignored) {
            }
            projection = null;
        }
        SenderPreviewService.stop(appContext);
    }

    /** Release consent and active capture (leaving sender screen). */
    public void releaseConsent() {
        stop();
        resultCode = 0;
        resultData = null;
    }
}
