package com.foxx.androidcast.sender;

/*********************************************************************
 * SenderStreamMetrics.java
 * Created at: Fri 15 May 2026 20:37:44 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class SenderStreamMetrics
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (3 commits, 84 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	65d468449f4cd236559457f2519730c3c07688bfe3082a961c081d6bd629d7f0
**********************************************************************/
import com.foxx.androidcast.network.diag.DiagPingMetrics;

/** Sender-side encode/send counters for notification diagnostics. */
public final class SenderStreamMetrics {
    private long encodedFrames;
    private long keyFrames;
    private long windowFrames;
    private long windowStartMs;
    private float lastSecondFps;
    private int lastSendKbps;
    private int peerRttMs;
    private DiagPingMetrics.Snapshot diagPing = DiagPingMetrics.Snapshot.EMPTY;

    public synchronized void onDiagPingSample(DiagPingMetrics.Snapshot sample) {
        diagPing = sample != null ? sample : DiagPingMetrics.Snapshot.EMPTY;
        if (diagPing.avgRttMs > 0) {
            peerRttMs = diagPing.avgRttMs;
        } else if (diagPing.lastRttMs > 0) {
            peerRttMs = diagPing.lastRttMs;
        }
    }

    public synchronized void onEncodedFrame(boolean keyFrame) {
        long now = System.currentTimeMillis();
        encodedFrames++;
        if (keyFrame) {
            keyFrames++;
        }
        if (windowStartMs == 0) {
            windowStartMs = now;
        }
        if (now - windowStartMs >= 1000) {
            lastSecondFps = windowFrames;
            windowFrames = 0;
            windowStartMs = now;
        }
        windowFrames++;
    }

    public synchronized float getLastSecondFps() {
        return lastSecondFps;
    }

    public synchronized void onNetworkSample(int sendKbps, int rttMs) {
        if (sendKbps > 0) {
            lastSendKbps = sendKbps;
        }
        if (rttMs > 0) {
            peerRttMs = rttMs;
        }
    }

    public synchronized String format(String videoCodec, int captureW, int captureH, CastSendPump pump,
            SenderAudioMetrics audio, boolean audioEnabled) {
        float fps = lastSecondFps > 0 ? lastSecondFps : 0f;
        StringBuilder sb = new StringBuilder();
        if (videoCodec != null && !videoCodec.isEmpty()) {
            sb.append(com.foxx.androidcast.media.CodecNegotiator.displayName(videoCodec)).append(' ');
        }
        sb.append(captureW).append('x').append(captureH);
        sb.append(" · enc ").append(fps > 0 ? String.format("%.0f", fps) : "?").append(" fps");
        if (lastSendKbps > 0) {
            sb.append(" · tx ").append(lastSendKbps).append(" kbps");
        }
        if (peerRttMs > 0) {
            sb.append(" · rtt ").append(peerRttMs).append("ms");
        }
        if (diagPing.enabled && diagPing.pongsRx > 0) {
            sb.append(" · ping ").append(diagPing.avgRttMs > 0 ? diagPing.avgRttMs : diagPing.lastRttMs).append("ms");
        }
        if (pump != null) {
            int dropped = pump.getDroppedPFrames();
            int q = pump.getQueueDepth();
            if (dropped > 0 || q > 8) {
                sb.append(" · q").append(q);
                if (dropped > 0) {
                    sb.append(" drop").append(dropped);
                }
            }
        }
        if (audioEnabled && audio != null) {
            int vu = audio.getVuPercent();
            sb.append(" · aud ").append(vu).append('%');
            if (audio.getAudibleBlocks() == 0 && audio.getSilentBlocks() > 30) {
                sb.append(" (silent cap)");
            }
        }
        return sb.toString();
    }
}
