def check_file(file_path: str, chat_id: int, item_id: int, app) -> dict:
    """
    - Se originale <= limite: original_ok.
    - Altrimenti:
        Tentativo 1: calcolo plan con limite "fudgiato" (solo qui).
        Se OK (<= limite reale): replace e encoded_ok.
        Se overshoot e RETRY_ON_OVERSHOOT=True: Tentativo 2.
            - Mantiene audio e vf_height del 1°
            - Ricalcola SOLO v_kbps 'spalmando' l'eccesso + safety.
        Se 2° OK: replace e encoded_ok.
        Altrimenti: too_big (lo split lo farà chi chiama).
    """
    try:
        src = Path(file_path)
        if not src.exists():
            msg = "❌ File non trovato"
            try: send_telegram_message(chat_id, msg)
            except Exception: pass
            return {"status": "error", "msg": msg, "path": None}

        orig_size = src.stat().st_size
        if orig_size <= safe_limit_bytes:
            # logger.info("[check] Originale sotto soglia: %s <= %s", orig_size, safe_limit_bytes)
            logger.info(
                "[check] originale <= limite: size=%s (%d B) <= limit=%s (%d B) — skip encode.",
                _fmt_bytes(orig_size), orig_size, _fmt_bytes(safe_limit_bytes), safe_limit_bytes
            )
            return {"status": "original_ok", "path": str(src), "size": orig_size}

        if app == "sonarr":
            api_key = sonarr_api
            host = sonarr_host
            port = sonarr_port
        elif app == "radarr":
            api_key = radarr_api
            host = radarr_host
            port = radarr_port
        else:
            raise ValueError("app must be 'radarr' or 'sonarr'")
        
        profile_language = get_profile_language_iso639_2(app, api_key, host, port, item_id)

        langs = _analyze_audio_languages_with_docker(src)
        filter_enabled, keep_relidx, notify_msg, summary_str = _decide_audio_selection(profile_language, langs)

        # Log elenco tracce rilevate
        logger.info("[audio] Tracce: %s", summary_str)

        # Notifiche all'owner, se richieste dalla logica
        if notify_msg:
            try:
                send_telegram_message(owner_chatid, f"ℹ️ {src.name}: {notify_msg}")
            except Exception:
                pass

        ctx = extract_encode_context(str(src))

        # Report keep/remove
        if filter_enabled:
            keep_1based = [i+1 for i in sorted(keep_relidx)]
            all_1based   = [int(t.get("track")) for t in langs]
            remove_1based = [t for t in all_1based if t not in keep_1based]
            # logger.info("[audio] Keep: %s | Remove: %s",
            #             ", ".join(f"#{i}" for i in keep_1based) if keep_1based else "(none)",
            #             ", ".join(f"#{i}" for i in remove_1based) if remove_1based else "(none)")
            logger.info(
                "[audio] selection: keep=%s (%d/%d), remove=%s",
                ", ".join(f"#{i}" for i in keep_1based) if keep_1based else "(none)",
                len(keep_1based), len(langs),
                ", ".join(f"#{i}" for i in remove_1based) if remove_1based else "(none)"
            )
        else:
            logger.info("[audio] selection: keep=all (%d/%d), remove=(none)", ctx.n_audio, ctx.n_audio)

        # Context e output temporaneo
        # ctx = extract_encode_context(str(src))
        out_tmp = str(src.with_name(f"{src.stem}.encode{REENCODE_OUTPUT_EXT}"))
        try:
            if os.path.exists(out_tmp):
                os.remove(out_tmp)
        except Exception:
            pass

        dur_txt = _fmt_hms(ctx.duration_s)
        try:
            send_telegram_message(
                chat_id,
                "🗜️ Provo compressione GPU (HEVC NVENC, multipass)…"
            )

            logger.info(
                "[check] source: name=%s, dur=%s (%.0fs), size=%s (%d B), limit=%s (%d B), app=%s, profile_lang=%s",
                src.name, _fmt_hms(ctx.duration_s), ctx.duration_s,
                _fmt_bytes(orig_size), orig_size,
                _fmt_bytes(safe_limit_bytes), safe_limit_bytes,
                app, profile_language or "n/a"
            )

            a_src_est = max(0, ctx.source_total_kbps_est - ctx.source_video_kbps_est)
            logger.info(
                "[src] video: %dx%d, %s-bit, v_src≈%d kb/s",
                ctx.width, ctx.height, ("10" if ctx.is_10bit else "8"), ctx.source_video_kbps_est
            )
            logger.info(
                "[src] audio: tracks=%d, a_src_est≈%d kb/s",
                ctx.n_audio, a_src_est
            )
            logger.info(
                "[src] container: total_src≈%d kb/s, dur=%s (%.0fs), size=%s (%d B), ext=%s",
                ctx.source_total_kbps_est, dur_txt, ctx.duration_s,
                _fmt_bytes(orig_size), orig_size, ctx.ext_low
            )

        except Exception:
            pass

        # ---- Tentativo 1 (fudge -1% sul limite totale) ----
        limit_try1 = int(safe_limit_bytes * (1.0 - FUDGE_VBR_FIRST_TRY))

        n_audio_eff = len(keep_relidx) if filter_enabled else ctx.n_audio

        plan1 = compute_target_for_size(ctx, limit_try1, n_audio_override=n_audio_eff)

        # logger.info("[check] Tentativo 1 → target_kbps_tot=%s, audio=%s, video=%s, vf_h=%s",
        #             plan1.safe_total_kbps, plan1.target_audio_kbps_total, plan1.v_kbps, plan1.vf_height)

        logger.info(
            "[plan] try#1: total=%s kb/s (v=%s, a=%s), vf_h=%s, limit_fudged=%s (%d B, fudge=%.2f%%), dur=%s (%.0fs)",
            plan1.safe_total_kbps, plan1.v_kbps, plan1.target_audio_kbps_total, plan1.vf_height,
            _fmt_bytes(limit_try1), limit_try1, (FUDGE_VBR_FIRST_TRY * 100.0),
            _fmt_hms(ctx.duration_s), ctx.duration_s
        )

        # Se filtriamo, passiamo la mappa esplicita; altrimenti None per comportamento corrente
        audio_map = keep_relidx if filter_enabled else None
        rc1, el1, out1 = run_encode_once(out_tmp, ctx, plan1, audio_map_relidx=audio_map)

        logger.info("[encode] v_kbps=%s, vf_h=%s, audio_kbps_total=%s, safe_total_kbps=%s, src_v_kbps=%s",
                    plan1.v_kbps, plan1.vf_height, plan1.target_audio_kbps_total,
                    plan1.safe_total_kbps, getattr(plan1, "source_video_kbps_est", "n/a"))

        if rc1 != 0 or out1 <= 0 or not os.path.exists(out_tmp):
            try: send_telegram_message(chat_id, "❌ Compress. fallita (1° tentativo).")
            except Exception: pass
            try:
                if os.path.exists(out_tmp):
                    os.remove(out_tmp)
            except Exception:
                pass
            return {"status": "error", "path": str(src), "msg": "encode1_failed"}

        logger.info("[encode] OK rc=%s, size=%s bytes", rc1, out1)

        if out1 <= safe_limit_bytes:
            # SUCCESSO → sostituisco l'originale
            try:
                dest_path = src.with_suffix(REENCODE_OUTPUT_EXT)
                os.replace(out_tmp, dest_path)
                if dest_path != src:
                    try:
                        os.remove(src)
                    except Exception:
                        pass

                try:
                    eff_n_audio = (len(audio_map) if audio_map is not None else ctx.n_audio)
                    update_mkv_metadata_final(dest_path, src, eff_n_audio)
                    logger.info("[final] mkvpropedit: track statistics tags rigenerati + segment title/default audio impostati.")
                except Exception:
                    pass

                final_size = os.path.getsize(dest_path)

                try:
                    eff_total_kbps_1 = int((final_size * 8) / max(1.0, ctx.duration_s) / 1000)
                    margin_pct_1 = (1.0 - (final_size / safe_limit_bytes)) * 100.0
                    logger.info(
                        "[final] try#1 OK: size=%s (%d B) ≤ limit=%s (%d B), margin=%.2f%%, "
                        "eff_total=%d kb/s vs plan_total=%s, v=%s, vf_h=%s, audio_streams=%d, dest=%s",
                        _fmt_bytes(final_size), final_size,
                        _fmt_bytes(safe_limit_bytes), safe_limit_bytes, margin_pct_1,
                        eff_total_kbps_1, plan1.safe_total_kbps, plan1.v_kbps, plan1.vf_height,
                        (len(audio_map) if filter_enabled else ctx.n_audio),
                        str(dest_path)
                    )

                    size_factor = (orig_size / final_size) if final_size > 0 else 0.0
                    size_redux_pct = 100.0 * (1.0 - (final_size / orig_size))
                    tot_redux_pct  = 100.0 * (1.0 - (eff_total_kbps_1 / max(1, ctx.source_total_kbps_est)))
                    v_redux_pct    = 100.0 * (1.0 - (plan1.v_kbps / max(1, ctx.source_video_kbps_est)))

                    logger.info(
                        "[final] delta#1: total≈%d→%d kb/s (Δ=%.2f%%), v≈%d→%d kb/s (plan, Δ=%.2f%%), "
                        "size %s→%s (x%.2f, Δ=%.2f%%), vf_h=%s, a_streams=%d",
                        ctx.source_total_kbps_est, eff_total_kbps_1, tot_redux_pct,
                        ctx.source_video_kbps_est, plan1.v_kbps, v_redux_pct,
                        _fmt_bytes(orig_size), _fmt_bytes(final_size), size_factor, size_redux_pct,
                        plan1.vf_height, (len(audio_map) if filter_enabled else ctx.n_audio)
                    )

                except Exception:
                    pass

            except Exception:
                try: os.remove(out_tmp)
                except Exception: pass
                final_size = out1  # best effort

            try:
                send_telegram_message(chat_id, f"✅ Compressione ok: {_fmt_bytes(final_size)} (limite {_fmt_bytes(safe_limit_bytes)})")
            except Exception:
                pass

            # return {"status": "encoded_ok", "path": file_path, "size": final_size,
            return {"status": "encoded_ok", "path": dest_path, "size": final_size,
                    "plan": {"vf_height": plan1.vf_height, "v_kbps": plan1.v_kbps,
                             "audio_kbps": plan1.target_audio_kbps_total,
                             "safe_total_kbps": plan1.safe_total_kbps,
                             "src_video_kbps_est": getattr(plan1, "source_video_kbps_est", None)}}

        # ---- Overshoot → valutazione retry ----
        if not RETRY_ON_OVERSHOOT:
            try:
                send_telegram_message(chat_id, f"ℹ️ Output {_fmt_bytes(out1)} > limite ({_fmt_bytes(safe_limit_bytes)}). Retry disattivato.")

                logger.info(
                    "[overshoot] try#1: size=%s (%d B) > limit=%s (%d B) by %.2f%% — retry disattivato.",
                    _fmt_bytes(out1), out1, _fmt_bytes(safe_limit_bytes), safe_limit_bytes,
                    ((out1 - safe_limit_bytes) / safe_limit_bytes) * 100.0
                )

            except Exception:
                pass
            try:
                if os.path.exists(out_tmp):
                    os.remove(out_tmp)
            except Exception:
                pass
            return {"status": "too_big", "path": str(src), "size": out1, "msg": "overshoot_no_retry"}

        overs_bytes = out1 - safe_limit_bytes
        pct = (overs_bytes / safe_limit_bytes) * 100.0
        
        min_floor = _floor_value_min(H265_FLOOR_KBPS)

        v2 = _compute_retry_v_kbps(
            ctx.duration_s, out1, safe_limit_bytes, plan1.v_kbps, RETRY_SAFETY_PCT,
            floor_kbps=min_floor
        )

        try:
            plan2 = adjust_plan_after_retry(ctx, plan1, v2)  # usa H265_FLOOR_KBPS e MAX_TARGET_HEIGHT interni/globali
        except EncodeNotFeasible as e:
            # Retry non fattibile (v2 < floor minore, o nessun bucket compatibile) → chiedi split
            try:
                send_telegram_message(chat_id,
                    f"❌ Retry impossibile: {e}. Suggerito split.")
            except Exception:
                pass
            try:
                if os.path.exists(out_tmp):
                    os.remove(out_tmp)
            except Exception:
                pass
            return {"status": "too_big", "path": str(src), "size": out1, "msg": "overshoot_retry_unfeasible"}

        try:
            send_telegram_message(
                chat_id,
                "ℹ️ Overshoot: {} > limite ({}) di {:.2f}%.\n"
                "Retry: video {} → {} kb/s; risoluzione {} → {}."
                .format(_fmt_bytes(out1), _fmt_bytes(safe_limit_bytes), pct,
                        plan1.v_kbps, plan2.v_kbps, plan1.vf_height, plan2.vf_height)
            )

            logger.info(
                "[overshoot] try#1: size=%s (%d B) > limit=%s (%d B) by %.2f%% — "
                "retry plan: v=%s→%s kb/s, vf_h=%s→%s",
                _fmt_bytes(out1), out1, _fmt_bytes(safe_limit_bytes), safe_limit_bytes, pct,
                plan1.v_kbps, plan2.v_kbps, plan1.vf_height, plan2.vf_height
            )
        except Exception:
            pass

        logger.info(
            "[plan] try#2: total=%s kb/s (v=%s, a=%s), vf_h=%s",
            plan2.safe_total_kbps, plan2.v_kbps, plan2.target_audio_kbps_total, plan2.vf_height
        )

        rc2, el2, out2 = run_encode_once(out_tmp, ctx, plan2, audio_map_relidx=audio_map)
        logger.info("[encode] v_kbps=%s, vf_h=%s, audio_kbps_total=%s, safe_total_kbps=%s, src_v_kbps=%s",
                    plan2.v_kbps, plan2.vf_height, plan2.target_audio_kbps_total,
                    plan2.safe_total_kbps, getattr(plan2, "source_video_kbps_est", "n/a"))

        if rc2 != 0 or out2 <= 0 or not os.path.exists(out_tmp):
            try: send_telegram_message(chat_id, "❌ Compress. fallita (2° tentativo).")
            except Exception: pass
            try:
                if os.path.exists(out_tmp):
                    os.remove(out_tmp)
            except Exception:
                pass
            return {"status": "error", "path": str(src), "msg": "encode2_failed"}

        logger.info("[encode] OK rc=%s, size=%s bytes", rc2, out2)

        if out2 <= safe_limit_bytes:
            try:
                dest_path = src.with_suffix(REENCODE_OUTPUT_EXT)
                os.replace(out_tmp, dest_path)
                if dest_path != src:
                    try:
                        os.remove(src)
                    except Exception:
                        pass

                try:
                    eff_n_audio = (len(audio_map) if audio_map is not None else ctx.n_audio)
                    update_mkv_metadata_final(dest_path, src, eff_n_audio)
                    logger.info("[final] mkvpropedit: track statistics tags rigenerati + segment title/default audio impostati.")
                except Exception:
                    pass

                final_size = os.path.getsize(dest_path)

                try:
                    eff_total_kbps_2 = int((final_size * 8) / max(1.0, ctx.duration_s) / 1000)
                    margin_pct_2 = (1.0 - (final_size / safe_limit_bytes)) * 100.0
                    logger.info(
                        "[final] try#2 OK: size=%s (%d B) ≤ limit=%s (%d B), margin=%.2f%%, "
                        "eff_total=%d kb/s vs plan_total=%s, v=%s, vf_h=%s, audio_streams=%d, dest=%s",
                        _fmt_bytes(final_size), final_size,
                        _fmt_bytes(safe_limit_bytes), safe_limit_bytes, margin_pct_2,
                        eff_total_kbps_2, plan2.safe_total_kbps, plan2.v_kbps, plan2.vf_height,
                        (len(audio_map) if filter_enabled else ctx.n_audio),
                        str(dest_path)
                    )

                    size_factor = (orig_size / final_size) if final_size > 0 else 0.0
                    size_redux_pct = 100.0 * (1.0 - (final_size / orig_size))
                    tot_redux_pct  = 100.0 * (1.0 - (eff_total_kbps_2 / max(1, ctx.source_total_kbps_est)))
                    v_redux_pct    = 100.0 * (1.0 - (plan2.v_kbps / max(1, ctx.source_video_kbps_est)))

                    logger.info(
                        "[final] delta#2: total≈%d→%d kb/s (Δ=%.2f%%), v≈%d→%d kb/s (plan, Δ=%.2f%%), "
                        "size %s→%s (x%.2f, Δ=%.2f%%), vf_h=%s, a_streams=%d",
                        ctx.source_total_kbps_est, eff_total_kbps_2, tot_redux_pct,
                        ctx.source_video_kbps_est, plan2.v_kbps, v_redux_pct,
                        _fmt_bytes(orig_size), _fmt_bytes(final_size), size_factor, size_redux_pct,
                        plan2.vf_height, (len(audio_map) if filter_enabled else ctx.n_audio)
                    )

                except Exception:
                    pass

            except Exception:
                try: os.remove(out_tmp)
                except Exception: pass
                final_size = out2

            try:
                send_telegram_message(chat_id, f"✅ Compressione ok al 2° tentativo: {_fmt_bytes(final_size)} (limite {_fmt_bytes(safe_limit_bytes)})")
            except Exception:
                pass

            # return {"status": "encoded_ok", "path": file_path, "size": final_size,
            return {"status": "encoded_ok", "path": dest_path, "size": final_size,
                    "plan": {"vf_height": plan2.vf_height, "v_kbps": plan2.v_kbps,
                             "audio_kbps": plan2.target_audio_kbps_total,
                             "safe_total_kbps": plan2.safe_total_kbps,
                             "src_video_kbps_est": getattr(plan2, "source_video_kbps_est", None)}}

        # Ancora troppo grande → lasciare la decisione di split al chiamante
        try:
            if os.path.exists(out_tmp):
                os.remove(out_tmp)
        except Exception:
            pass
        try:
            send_telegram_message(chat_id, f"ℹ️ Output ancora > limite ({_fmt_bytes(safe_limit_bytes)}).")
        except Exception:
            pass

        logger.info(
            "[final] try#2 still too big: size=%s (%d B) > limit=%s (%d B) by %.2f%% — split demandato al chiamante.",
            _fmt_bytes(out2), out2, _fmt_bytes(safe_limit_bytes), safe_limit_bytes,
            ((out2 - safe_limit_bytes) / safe_limit_bytes) * 100.0
        )

        return {"status": "too_big", "path": str(src), "size": out2, "msg": "overshoot_after_retry"}

    except Exception as e:
        logger.exception("[check_file] eccezione: %s", e)
        try: send_telegram_message(chat_id, f"❌ Errore check_file: {e}")
        except Exception: pass
        return {"status": "error", "path": None, "msg": str(e)}