package com.puntoz.pro;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.text.TextUtils;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import java.net.URLEncoder;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.ArrayList;
import android.widget.ScrollView;
import android.widget.ImageView;
import android.widget.TextView;
import android.view.Gravity;
import android.graphics.drawable.GradientDrawable;

import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.exoplayer.DefaultLoadControl;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.DefaultRenderersFactory;
import androidx.media3.exoplayer.hls.DefaultHlsExtractorFactory;
import androidx.media3.exoplayer.hls.HlsMediaSource;
import androidx.media3.exoplayer.smoothstreaming.SsMediaSource;
import androidx.media3.exoplayer.rtsp.RtspMediaSource;
import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory;
import androidx.media3.ui.AspectRatioFrameLayout;
import androidx.media3.ui.PlayerView;

import org.json.JSONArray;
import org.json.JSONObject;

@UnstableApi
public class MainActivity extends Activity {

    private FrameLayout root;
    private WebView webView;
    private FrameLayout playerBox;
    private PlayerView playerView;
    private ExoPlayer player;

    private boolean previewActive = false;
    private boolean fullscreen = false;
    private boolean playerCompletoAbierto = false;
    private String currentUrl = "";
    private String currentPayload = "";
    private int currentIndex = -1;
    private LinearLayout osdBox;
    private TextView osdTitle;
    private TextView osdNow;
    private TextView osdTime;
    private TextView osdNext;
    private TextView osdNextTime;
    private TextView osdRemain;
    private ImageView osdLogo;
    private ImageView overlayLogo;
    private WebView overlayLogoGif;
    private FrameLayout adBox;
    private ImageView adImage;
    private TextView adText;
    private WebView adGifWeb;
    private View osdProgressDone;
    private View osdProgressLeft;
    private Runnable osdHide;
    private LinearLayout guideMenu;
    private LinearLayout catMenu;
    private LinearLayout nativeHome;
    private int menuState = 0;
    private long lastFullscreenEnterMs = 0L;
    private int focusedChannelIndex = 0;
    private int playingChannelIndex = 0;
    private String playingSid = "";
    private String playingUrl = "";
    private int focusedCategoryIndex = 0;
    private int focusedSectionIndex = 1;
    private int catActiveColumn = 0;
    private ArrayList<SectionItem> sectionItems = new ArrayList<>();
    private ArrayList<ChannelItem> channelItems = new ArrayList<>();
    private ArrayList<CategoryItem> categoryItems = new ArrayList<>();
    private HashMap<String, ArrayList<ChannelItem>> canalesCache = new HashMap<>();
    private HashMap<String, ArrayList<CategoryItem>> seccionesCache = new HashMap<>();
    private boolean precargaInicialHecha = false;
    private HashMap<String, Bitmap> logoCache = new HashMap<>();
    private HashMap<String, Boolean> logoLoading = new HashMap<>();
    private String catName = "Canales";
    private long blockEnterUntilMs = 0;
    private Runnable cargarCategoriaPendiente = null;
    private boolean cargandoSoloListaCategoria = false;
    private boolean prefetchCategoriasActivo = false;
    private String androidSectionActual = "";
    private String androidFolderActual = "";

    private static final String PANEL_URL =
            "http://216.152.155.250/PUNTOZ_PANEL/preview.php?apk_tv=1&epg_refresh=1";

    @SuppressLint({"SetJavaScriptEnabled"})
    @Override
    protected void onCreate(Bundle b) {
        super.onCreate(b);

        sectionItems.add(new SectionItem("search", "Buscar"));
        sectionItems.add(new SectionItem("tv", "TV"));
        sectionItems.add(new SectionItem("movies", "Películas"));
        sectionItems.add(new SectionItem("series", "Series"));
        sectionItems.add(new SectionItem("sports", "Deportes"));
        sectionItems.add(new SectionItem("countries", "Países"));
        sectionItems.add(new SectionItem("247", "24/7"));
        sectionItems.add(new SectionItem("adult", "Adultos"));
        sectionItems.add(new SectionItem("favorites", "Favoritos"));
        sectionItems.add(new SectionItem("recordings", "Grabación"));
        sectionItems.add(new SectionItem("refresh", "Actualizar"));
        sectionItems.add(new SectionItem("settings", "Configuración"));

        root = new FrameLayout(this);

        webView = new WebView(this);
        root.addView(webView, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        ));

        playerBox = new FrameLayout(this);
        playerBox.setBackgroundColor(Color.BLACK);
        playerBox.setVisibility(View.GONE);
        playerBox.setFocusable(false);
        playerBox.setFocusableInTouchMode(false);

        playerView = new PlayerView(this);
        playerView.setUseController(false);
        playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
        playerBox.addView(playerView, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        ));

        crearNativeHomeBase();
        root.addView(nativeHome, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        ));
        nativeHome.setVisibility(View.GONE);

        root.addView(playerBox, new FrameLayout.LayoutParams(1, 1));

        overlayLogo = new ImageView(this);
        overlayLogo.setScaleType(ImageView.ScaleType.FIT_CENTER);
        overlayLogo.setVisibility(View.GONE);
        playerBox.addView(overlayLogo, new FrameLayout.LayoutParams(120, 120));

        overlayLogoGif = new WebView(this);
        overlayLogoGif.setBackgroundColor(Color.TRANSPARENT);
        overlayLogoGif.setVisibility(View.GONE);
        try {
            overlayLogoGif.getSettings().setJavaScriptEnabled(false);
            overlayLogoGif.getSettings().setLoadWithOverviewMode(true);
            overlayLogoGif.getSettings().setUseWideViewPort(true);
        } catch(Exception ignored) {}
        playerBox.addView(overlayLogoGif, new FrameLayout.LayoutParams(120, 120));

        adBox = new FrameLayout(this);
        adBox.setVisibility(View.GONE);

        adImage = new ImageView(this);
        adImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
        adImage.setVisibility(View.GONE);
        adBox.addView(adImage, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        ));

        adText = new TextView(this);
        adText.setGravity(Gravity.CENTER);
        adText.setVisibility(View.GONE);
        adBox.addView(adText, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        ));

        adGifWeb = new WebView(this);
        adGifWeb.setBackgroundColor(Color.TRANSPARENT);
        adGifWeb.setVisibility(View.GONE);
        try {
            adGifWeb.getSettings().setJavaScriptEnabled(false);
            adGifWeb.getSettings().setLoadWithOverviewMode(true);
            adGifWeb.getSettings().setUseWideViewPort(true);
        } catch(Exception ignored) {}
        adBox.addView(adGifWeb, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        ));

        playerBox.addView(adBox, new FrameLayout.LayoutParams(320, 160));
        crearOSD();
        crearMenusFullscreen();
        setContentView(root);

        WebSettings s = webView.getSettings();
        s.setJavaScriptEnabled(true);
        s.setDomStorageEnabled(true);
        s.setMediaPlaybackRequiresUserGesture(false);
        s.setLoadWithOverviewMode(true);
        s.setUseWideViewPort(true);
        s.setSupportZoom(false);
        webView.setInitialScale(75);

        webView.setWebChromeClient(new WebChromeClient());
        webView.addJavascriptInterface(new AndroidBridge(), "PuntozAndroid");

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url != null && url.endsWith(".apk")) {
                    Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    startActivity(i);
                    return true;
                }
                try {
                    if (url != null && url.contains("/PUNTOZ_PANEL/") && !url.contains("apk_tv=1")) {
                        url = url + (url.contains("?") ? "&" : "?") + "apk_tv=1";
                    }
                } catch(Exception ignored) {}

                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                detenerVideoHtml();
                if (!fullscreen) ubicarPreviewNativo();

                if (!fullscreen && !precargaInicialHecha) {
                    precargaInicialHecha = true;
                    cargarCategoriasLigero("tv", "", true);
                    root.postDelayed(() -> precargarCategoriasCercanas(), 1200);
                }
            }
        });

        crearPlayer();

        // PZ_FAST_ANDROID: no borrar cache WebView al iniciar
        // PZ_FAST_ANDROID: no borrar historial WebView al iniciar
        // PZ_FAST_ANDROID: no borrar form data WebView al iniciar
        webView.loadUrl(PANEL_URL);
    }

    private static class EpgItem {
        String title = "";
        String desc = "";
        long start = 0;
        long stop = 0;
    }

    private static class ChannelItem {
        String url="", name="Canal", logo="", sid="", now="", next="", st="", en="", ns="", ne="", overlay_enabled="0", overlay_logo="", overlay_x="0", overlay_y="0", overlay_size="90", overlay_opacity="100";
        java.util.ArrayList<EpgItem> programs = new java.util.ArrayList<>();
    }

    private static class CategoryItem {
        String id="", name="";
        String type="category";
        String section="";
        String folder="";
        String firstCat="";
    }

    private static class SectionItem {
        String id="", name="";
        SectionItem(String id, String name){
            this.id = id;
            this.name = name;
        }
    }

    private TextView tvMini(String txt, int size, int color) {
        TextView t = new TextView(this);
        t.setText(txt);
        t.setTextColor(color);
        t.setTextSize(size);
        t.setSingleLine(true);
        t.setMaxLines(1);
        t.setEllipsize(android.text.TextUtils.TruncateAt.END);
        t.setPadding(16,8,16,8);
        return t;
    }

    private GradientDrawable fondoMenu(int alpha) {
        GradientDrawable bg = new GradientDrawable();
        bg.setColor(Color.argb(alpha, 5, 10, 14));
        bg.setStroke(2, Color.argb(95, 255, 255, 255));
        bg.setCornerRadius(18);
        return bg;
    }

    private TextView itemMenu(String txt, boolean foco, int size) {
        TextView t = tvMini(txt, size, Color.WHITE);
        if (foco) {
            GradientDrawable bg = new GradientDrawable();
            bg.setColor(Color.argb(105, 150, 190, 70));
            bg.setStroke(2, Color.argb(230, 235, 245, 120));
            bg.setCornerRadius(10);
            t.setBackground(bg);
            t.setTypeface(null, android.graphics.Typeface.BOLD);
        } else {
            t.setBackground(null);
        }
        return t;
    }

    private TextView itemActual(String txt, int size) {
        TextView t = tvMini(txt, size, Color.YELLOW);
        t.setBackground(null);
        t.setTypeface(null, android.graphics.Typeface.BOLD);
        return t;
    }

    private TextView osdText(String text, int size, int color) {
        TextView t = new TextView(this);
        t.setText(text);
        t.setTextColor(color);
        t.setTextSize(size);
        t.setSingleLine(true);
        t.setMaxLines(1);
        t.setEllipsize(android.text.TextUtils.TruncateAt.END);
        t.setIncludeFontPadding(false);
        return t;
    }

    private TextView osdChip(String txt) {
        TextView t = osdText(txt, 13, Color.LTGRAY);
        t.setGravity(Gravity.CENTER);
        GradientDrawable bg = new GradientDrawable();
        bg.setColor(Color.argb(35, 0, 0, 0));
        bg.setStroke(2, Color.argb(160, 180, 180, 180));
        bg.setCornerRadius(8);
        t.setBackground(bg);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(68, 30);
        lp.setMargins(8, 0, 8, 0);
        t.setLayoutParams(lp);
        return t;
    }

    private void crearOSD() {
        osdBox = new LinearLayout(this);
        osdBox.setOrientation(LinearLayout.HORIZONTAL);
        osdBox.setGravity(Gravity.CENTER_VERTICAL);
        osdBox.setPadding(28, 16, 28, 16);
        osdBox.setVisibility(View.GONE);

        GradientDrawable bg = new GradientDrawable();
        bg.setColor(Color.argb(82, 0, 0, 0));
        bg.setStroke(2, Color.argb(80, 255, 255, 255));
        bg.setCornerRadius(16);
        osdBox.setBackground(bg);

        osdLogo = new ImageView(this);
        osdLogo.setScaleType(ImageView.ScaleType.FIT_CENTER);
        LinearLayout.LayoutParams logoLp = new LinearLayout.LayoutParams(220, 150);
        logoLp.setMargins(0, 0, 28, 0);
        osdBox.addView(osdLogo, logoLp);

        LinearLayout info = new LinearLayout(this);
        info.setOrientation(LinearLayout.VERTICAL);
        info.setPadding(0, 18, 0, 0);

        osdNow = osdText("", 13, Color.WHITE);
        osdTime = osdText("", 11, Color.WHITE);

        LinearLayout progressRow = new LinearLayout(this);
        progressRow.setOrientation(LinearLayout.HORIZONTAL);
        progressRow.setGravity(Gravity.CENTER_VERTICAL);

        LinearLayout bar = new LinearLayout(this);
        bar.setOrientation(LinearLayout.HORIZONTAL);
        GradientDrawable barBg = new GradientDrawable();
        barBg.setColor(Color.argb(120, 160, 170, 180));
        barBg.setCornerRadius(6);
        bar.setBackground(barBg);

        osdProgressDone = new View(this);
        osdProgressDone.setBackgroundColor(Color.rgb(255, 220, 0));
        bar.addView(osdProgressDone, new LinearLayout.LayoutParams(0, 7, 1));

        osdProgressLeft = new View(this);
        osdProgressLeft.setBackgroundColor(Color.TRANSPARENT);
        bar.addView(osdProgressLeft, new LinearLayout.LayoutParams(0, 7, 99));

        LinearLayout.LayoutParams barLp = new LinearLayout.LayoutParams(560, 7);
        barLp.setMargins(0, 1, 14, 1);
        progressRow.addView(bar, barLp);

        osdRemain = osdText("", 11, Color.WHITE);
        progressRow.addView(osdRemain);

        osdNext = osdText("", 13, Color.rgb(0, 210, 255));
        osdNextTime = osdText("", 12, Color.WHITE);

        info.addView(osdNow);
        info.addView(osdTime);
        info.addView(progressRow);
        info.addView(osdNext);
        info.addView(osdNextTime);

        osdBox.addView(info, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1));

        View divider = new View(this);
        divider.setBackgroundColor(Color.argb(130, 180, 180, 180));
        LinearLayout.LayoutParams divLp = new LinearLayout.LayoutParams(2, 128);
        divLp.setMargins(35, 0, 35, 0);
        osdBox.addView(divider, divLp);

        LinearLayout right = new LinearLayout(this);
        right.setOrientation(LinearLayout.HORIZONTAL);
        right.setGravity(Gravity.CENTER);
        right.addView(osdChip("FHD"));
        right.addView(osdChip("16:9"));
        right.addView(osdChip("STEREO"));

        osdBox.addView(right, new LinearLayout.LayoutParams(280, LinearLayout.LayoutParams.WRAP_CONTENT));

        FrameLayout.LayoutParams osdParams = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT
        );
        osdParams.gravity = Gravity.BOTTOM;
        osdParams.setMargins(70, 0, 70, 42);

        root.addView(osdBox, osdParams);
    }

    private String osdHora(String raw) {
        try {
            long v = Long.parseLong(raw.trim());
            if (v > 9999999999L) v = v / 1000L;
            return new java.text.SimpleDateFormat("hh:mm a", new java.util.Locale("es", "CL"))
                    .format(new java.util.Date(v * 1000L));
        } catch(Exception e) {
            return "";
        }
    }

    private int osdProgreso(String ini, String fin) {
        try {
            long s = Long.parseLong(ini.trim());
            long e = Long.parseLong(fin.trim());
            long n = System.currentTimeMillis() / 1000L;
            if (s <= 0 || e <= 0 || e <= s) return 0;
            if (n <= s) return 0;
            if (n >= e) return 100;
            return (int)Math.max(0, Math.min(100, ((n - s) * 100L) / (e - s)));
        } catch(Exception e) {
            return 0;
        }
    }

    private String osdMinutosRestantes(String fin) {
        try {
            long e = Long.parseLong(fin.trim());
            long n = System.currentTimeMillis() / 1000L;
            if (e <= 0 || e <= n) return "";
            long m = (e - n) / 60L;
            return m > 0 ? (m + " min") : "";
        } catch(Exception e) {
            return "";
        }
    }

    private void ocultarPublicidad() {
        try {
            if (adBox != null) adBox.setVisibility(View.GONE);
            if (adImage != null) adImage.setVisibility(View.GONE);
            if (adText != null) adText.setVisibility(View.GONE);
            if (adGifWeb != null) {
                adGifWeb.setVisibility(View.GONE);
                adGifWeb.loadUrl("about:blank");
            }
        } catch(Exception ignored) {}
    }

    private void mostrarPublicidadPorSid(String sid) {
        try {
            if (adBox == null) return;
            if (!fullscreen) {
                ocultarPublicidad();
                return;
            }
            if (sid == null || sid.trim().isEmpty()) {
                ocultarPublicidad();
                return;
            }

            final String sidFinal = sid.trim();

            new Thread(() -> {
                try {
                    URL u = new URL("http://216.152.155.250/PUNTOZ_PANEL/ads_api.php?sid=" + Uri.encode(sidFinal));
                    HttpURLConnection c = (HttpURLConnection)u.openConnection();
                    c.setConnectTimeout(5000);
                    c.setReadTimeout(8000);

                    InputStream is = c.getInputStream();
                    byte[] buf = new byte[8192];
                    StringBuilder sb = new StringBuilder();
                    int n;
                    while((n = is.read(buf)) > 0) sb.append(new String(buf, 0, n, "UTF-8"));
                    is.close();

                    JSONObject jo = new JSONObject(sb.toString());

                    boolean enabled = jo.optBoolean("enabled", false);
                    String type = jo.optString("type", "text");
                    String message = jo.optString("message", "");
                    String media = jo.optString("media", "");
                    String color = jo.optString("color", "#FFFFFF");
                    String bg = jo.optString("bg_color", "#000000");
                    int txtSize = jo.optInt("size", 28);
                    int opacity = jo.optInt("opacity", 90);
                    double xPct = jo.optDouble("x_pct", 7);
                    double yPct = jo.optDouble("y_pct", 7);
                    double wPct = jo.optDouble("w_pct", 35);

                    if (!enabled) {
                        runOnUiThread(() -> ocultarPublicidad());
                        return;
                    }

                    final String fType = type;
                    final String fMessage = message;
                    final String fMedia = media;
                    final String fColor = color;
                    final String fBg = bg;
                    final int fTxtSize = txtSize;
                    final int fOpacity = Math.max(0, Math.min(100, opacity));
                    final double fxPct = xPct, fyPct = yPct, fwPct = wPct;

                    Bitmap bmp = null;
                    if ((fType.equals("image") || fType.equals("png") || fType.equals("webp")) && fMedia.startsWith("http")) {
                        try { bmp = BitmapFactory.decodeStream(new URL(fMedia).openStream()); } catch(Exception ignored) {}
                    }
                    final Bitmap fbmp = bmp;

                    runOnUiThread(() -> {
                        try {
                            if (!fullscreen) {
                                ocultarPublicidad();
                                return;
                            }

                            int bw = Math.max(1, playerBox.getWidth());
                            int bh = Math.max(1, playerBox.getHeight());

                            int finalW = Math.max(80, Math.round((float)(bw * fwPct / 100.0)));
                            int finalH;
                            if (fType.equals("image") || fType.equals("png") || fType.equals("webp") || fType.equals("gif")) {
                                finalH = finalW;
                            } else {
                                finalH = Math.max(40, Math.round(finalW * 0.45f));
                            }
                            int finalX = Math.round((float)(bw * fxPct / 100.0));
                            int finalY = Math.round((float)(bh * fyPct / 100.0));

                            if (finalX + finalW > bw) finalX = Math.max(0, bw - finalW);
                            if (finalY + finalH > bh) finalY = Math.max(0, bh - finalH);

                            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(finalW, finalH);
                            lp.gravity = Gravity.TOP | Gravity.LEFT;
                            lp.leftMargin = finalX;
                            lp.topMargin = finalY;
                            adBox.setLayoutParams(lp);
                            adBox.setAlpha(fOpacity / 100f);

                            adImage.setVisibility(View.GONE);
                            adText.setVisibility(View.GONE);
                            adGifWeb.setVisibility(View.GONE);

                            if (fType.equals("gif") && fMedia.startsWith("http")) {
                                String html = "<html><body style='margin:0;background:transparent;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='" + fMedia + "' style='max-width:100%;max-height:100%;object-fit:contain;'/></body></html>";
                                adGifWeb.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
                                adGifWeb.setVisibility(View.VISIBLE);
                            } else if ((fType.equals("image") || fType.equals("png") || fType.equals("webp")) && fbmp != null) {
                                adImage.setImageBitmap(fbmp);
                                adImage.setVisibility(View.VISIBLE);
                            } else {
                                adText.setText(fMessage == null || fMessage.trim().isEmpty() ? "" : fMessage);
                                adText.setTextColor(Color.parseColor(fColor));
                                adText.setTextSize(fTxtSize);
                                GradientDrawable bgd = new GradientDrawable();
                                bgd.setColor(Color.parseColor(fBg));
                                bgd.setCornerRadius(14);
                                adText.setBackground(bgd);
                                adText.setVisibility(View.VISIBLE);
                            }

                            adBox.setVisibility(View.VISIBLE);
                            adBox.bringToFront();
                            if (overlayLogo != null) overlayLogo.bringToFront();

                        } catch(Exception ignored) {
                            ocultarPublicidad();
                        }
                    });

                } catch(Exception e) {
                    runOnUiThread(() -> ocultarPublicidad());
                }
            }).start();

        } catch(Exception ignored) {}
    }

    private void mostrarOverlayLogoPorSid(String sid) {
        try {
            if (overlayLogo == null) return;
            if (!fullscreen) {
                overlayLogo.setVisibility(View.GONE);
                try { if (overlayLogoGif != null) { overlayLogoGif.setVisibility(View.GONE); overlayLogoGif.loadUrl("about:blank"); } } catch(Exception ignored) {}
                return;
            }
            if (sid == null || sid.trim().isEmpty()) {
                overlayLogo.setVisibility(View.GONE);
                return;
            }

            final String sidFinal = sid.trim();

            new Thread(() -> {
                try {
                    URL u = new URL("http://216.152.155.250/PUNTOZ_PANEL/overlay_api.php?sid=" + Uri.encode(sidFinal));
                    HttpURLConnection c = (HttpURLConnection)u.openConnection();
                    c.setConnectTimeout(5000);
                    c.setReadTimeout(8000);

                    InputStream is = c.getInputStream();
                    byte[] buf = new byte[8192];
                    StringBuilder sb = new StringBuilder();
                    int n;
                    while((n = is.read(buf)) > 0) sb.append(new String(buf, 0, n, "UTF-8"));
                    is.close();

                    JSONObject jo = new JSONObject(sb.toString());

                    boolean enabled = jo.optBoolean("enabled", false);
                    String logo = jo.optString("logo", "");
                    int x = jo.optInt("x", 0);
                    int y = jo.optInt("y", 0);
                    int size = jo.optInt("size", 90);
                    int opacity = jo.optInt("opacity", 100);
                    double xPct = jo.optDouble("x_pct", 0);
                    double yPct = jo.optDouble("y_pct", 0);
                    double sizePct = jo.optDouble("size_pct", 0);

                    if (!enabled || logo == null || !logo.startsWith("http")) {
                        runOnUiThread(() -> {
                            try { overlayLogo.setVisibility(View.GONE); } catch(Exception ignored) {}
                        });
                        return;
                    }

                    size = Math.max(30, Math.min(400, size));
                    opacity = Math.max(0, Math.min(100, opacity));

                    final boolean isGif = logo.toLowerCase().contains(".gif");
                    Bitmap bmp = null;
                    if(!isGif){
                        bmp = BitmapFactory.decodeStream(new URL(logo).openStream());
                    }
                    final Bitmap fbmp = bmp;
                    final boolean fIsGif = isGif;
                    final int fx = x, fy = y, fsize = size, fopacity = opacity;
                    final double fxPct = xPct, fyPct = yPct, fsizePct = sizePct;

                    runOnUiThread(() -> {
                        try {
                            int finalSize = fsize;
                            int finalX = fx;
                            int finalY = fy;

                            if (fullscreen && fxPct > 0 && fyPct >= 0 && fsizePct > 0) {
                                int bw = Math.max(1, playerBox.getWidth());
                                int bh = Math.max(1, playerBox.getHeight());

                                finalSize = Math.max(30, Math.round((float)(bw * fsizePct / 100.0)));
                                finalX = Math.round((float)(bw * fxPct / 100.0));
                                finalY = Math.round((float)(bh * fyPct / 100.0));

                                if (finalX + finalSize > bw) {
                                    finalX = Math.max(0, bw - finalSize);
                                }
                            } else if (fullscreen) {
                                finalSize = Math.max(30, Math.round(fsize * 2.2f));
                                finalX = Math.round(fx * 2.0f);
                                finalY = Math.round(fy * 2.0f);
                            }

                            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(finalSize, finalSize);
                            lp.gravity = Gravity.TOP | Gravity.LEFT;
                            lp.leftMargin = Math.max(0, finalX);
                            lp.topMargin = Math.max(0, finalY);

                            if (fIsGif) {
                                overlayLogo.setVisibility(View.GONE);

                                if (overlayLogoGif != null) {
                                    overlayLogoGif.setLayoutParams(lp);
                                    overlayLogoGif.setAlpha(fopacity / 100f);
                                    String html = "<html><body style='margin:0;background:transparent;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='" + logo + "' style='max-width:100%;max-height:100%;object-fit:contain;'/></body></html>";
                                    overlayLogoGif.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
                                    overlayLogoGif.setVisibility(View.VISIBLE);
                                    overlayLogoGif.bringToFront();
                                }
                            } else {
                                try { if (overlayLogoGif != null) { overlayLogoGif.setVisibility(View.GONE); overlayLogoGif.loadUrl("about:blank"); } } catch(Exception ignored) {}
                                overlayLogo.setLayoutParams(lp);
                                overlayLogo.setAlpha(fopacity / 100f);
                                overlayLogo.setImageBitmap(fbmp);
                                overlayLogo.setVisibility(View.VISIBLE);
                                overlayLogo.bringToFront();
                            }
                        } catch(Exception ignored) {}
                    });

                } catch(Exception e) {
                    runOnUiThread(() -> {
                        try { overlayLogo.setVisibility(View.GONE); } catch(Exception ignored) {}
                    });
                }
            }).start();

        } catch(Exception ignored) {}
    }

    private void mostrarOverlayLogo(String payload) {
        try {
            if (overlayLogo == null) return;
            if (payload == null || payload.trim().isEmpty()) {
                overlayLogo.setVisibility(View.GONE);
                return;
            }

            String[] parts = payload.split("###PZ###", -1);

            String enabled = parts.length > 12 ? Uri.decode(parts[12]) : "0";
            String logo = parts.length > 13 ? Uri.decode(parts[13]) : "";
            int x = parts.length > 14 ? Integer.parseInt(Uri.decode(parts[14])) : 0;
            int y = parts.length > 15 ? Integer.parseInt(Uri.decode(parts[15])) : 0;
            int size = parts.length > 16 ? Integer.parseInt(Uri.decode(parts[16])) : 90;
            int opacity = parts.length > 17 ? Integer.parseInt(Uri.decode(parts[17])) : 100;

            if (!"1".equals(enabled) || logo == null || !logo.startsWith("http")) {
                overlayLogo.setVisibility(View.GONE);
                return;
            }

            size = Math.max(30, Math.min(400, size));
            opacity = Math.max(0, Math.min(100, opacity));

            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(size, size);
            lp.gravity = Gravity.TOP | Gravity.LEFT;
            lp.leftMargin = Math.max(0, x);
            lp.topMargin = Math.max(0, y);
            overlayLogo.setLayoutParams(lp);
            overlayLogo.setAlpha(opacity / 100f);
            overlayLogo.setVisibility(View.VISIBLE);
            overlayLogo.bringToFront();

            final String expectedLogo = logo;
            new Thread(() -> {
                try {
                    Bitmap bmp = BitmapFactory.decodeStream(new URL(expectedLogo).openStream());
                    runOnUiThread(() -> {
                        try {
                            overlayLogo.setImageBitmap(bmp);
                            overlayLogo.setVisibility(View.VISIBLE);
                            overlayLogo.bringToFront();
                        } catch(Exception ignored) {}
                    });
                } catch(Exception ignored) {}
            }).start();

        } catch(Exception e) {
            try { overlayLogo.setVisibility(View.GONE); } catch(Exception ignored) {}
        }
    }

    private void cargarLogoOSD(String logoUrl) {
        if (osdLogo == null) return;
        if (logoUrl == null || !logoUrl.startsWith("http")) return;

        Bitmap cached = logoCache.get(logoUrl);
        if (cached != null) {
            try { osdLogo.setImageBitmap(cached); } catch(Exception ignored) {}
            return;
        }

        final String expectedLogo = logoUrl;

        new Thread(() -> {
            try {
                java.net.URLConnection con = new URL(expectedLogo).openConnection();
                con.setConnectTimeout(3000);
                con.setReadTimeout(5000);
                InputStream in = con.getInputStream();
                Bitmap bmp = BitmapFactory.decodeStream(in);
                try { in.close(); } catch(Exception ignored) {}

                if (bmp != null) {
                    logoCache.put(expectedLogo, bmp);
                    runOnUiThread(() -> {
                        try { osdLogo.setImageBitmap(bmp); } catch(Exception ignored) {}
                    });
                }
            } catch(Exception ignored) {}
        }).start();
    }

    private void mostrarOSDDesdePayload(String payload) {
        if (payload == null || payload.trim().isEmpty() || osdBox == null) return;
        String[] parts = payload.split("###PZ###", -1);

        String logo = parts.length > 2 ? Uri.decode(parts[2]) : "";
        String now = parts.length > 3 ? Uri.decode(parts[3]) : "No information";
        String next = parts.length > 4 ? Uri.decode(parts[4]) : "No information";
        String st = parts.length > 5 ? Uri.decode(parts[5]) : "";
        String en = parts.length > 6 ? Uri.decode(parts[6]) : "";
        String ns = parts.length > 7 ? Uri.decode(parts[7]) : "";
        String ne = parts.length > 8 ? Uri.decode(parts[8]) : "";

        osdNow.setText(android.text.Html.fromHtml("<font color=\"#FFD400\">Ahora:</font> <font color=\"#FFFFFF\">" + now + "</font>"));
        osdTime.setText(osdHora(st) + (osdHora(st).length() > 0 || osdHora(en).length() > 0 ? " – " : "") + osdHora(en));
        osdNext.setText(android.text.Html.fromHtml("<font color=\"#00D8FF\">Sigue:</font> <font color=\"#FFFFFF\">" + next + "</font>"));
        osdNextTime.setText(osdHora(ns) + (osdHora(ns).length() > 0 || osdHora(ne).length() > 0 ? " – " : "") + osdHora(ne));
        osdRemain.setText(osdMinutosRestantes(en));

        int progress = osdProgreso(st, en);
        try {
            osdProgressDone.setLayoutParams(new LinearLayout.LayoutParams(0, 7, Math.max(1, progress)));
            osdProgressLeft.setLayoutParams(new LinearLayout.LayoutParams(0, 7, Math.max(1, 100 - progress)));
        } catch(Exception ignored) {}

        cargarLogoOSD(logo);

        osdBox.setVisibility(View.VISIBLE);
        playerBox.bringToFront();
        osdBox.bringToFront();

        if (osdHide != null) root.removeCallbacks(osdHide);
        osdHide = () -> osdBox.setVisibility(View.GONE);
        root.postDelayed(osdHide, 10000);
    }

    private void parsePayloadChannels(String payload) {
        try {
            String[] parts = payload.split("###PZ###", -1);
            String sidActual = parts.length > 9 ? Uri.decode(parts[9]) : "";
            String json = parts.length > 10 ? Uri.decode(parts[10]) : "";
            String incomingCatName = parts.length > 11 ? Uri.decode(parts[11]) : "";
            if (incomingCatName != null) incomingCatName = incomingCatName.trim();
            if (incomingCatName == null || incomingCatName.isEmpty() || incomingCatName.equalsIgnoreCase("All channels")) {
                if (catName == null || catName.trim().isEmpty() || catName.equalsIgnoreCase("All channels")) catName = "Canales";
            } else {
                catName = incomingCatName;
            }

            org.json.JSONArray arr = new org.json.JSONArray(json);
            channelItems.clear();

            for(int i=0;i<arr.length();i++){
                org.json.JSONObject o = arr.getJSONObject(i);
                ChannelItem ch = new ChannelItem();
                ch.url=o.optString("url","");
                ch.name=o.optString("name","Canal");
                ch.logo=o.optString("logo","");
                ch.sid=o.optString("sid","");
                ch.now=o.optString("now","");
                ch.next=o.optString("next","");
                ch.st=o.optString("st","");
                ch.en=o.optString("en","");
                ch.ns=o.optString("ns","");
                ch.ne=o.optString("ne","");
                ch.overlay_enabled=o.optString("overlay_enabled","0");
                ch.overlay_logo=o.optString("overlay_logo","");
                ch.overlay_x=o.optString("overlay_x","0");
                ch.overlay_y=o.optString("overlay_y","0");
                ch.overlay_size=o.optString("overlay_size","90");
                ch.overlay_opacity=o.optString("overlay_opacity","100");

                try {
                    org.json.JSONArray progs = o.optJSONArray("programs");
                    if (progs != null) {
                        for (int pi = 0; pi < progs.length(); pi++) {
                            org.json.JSONObject po = progs.getJSONObject(pi);
                            EpgItem ep = new EpgItem();
                            ep.title = po.optString("title", po.optString("name", ""));
                            ep.desc = po.optString("desc", po.optString("description", ""));
                            ep.start = po.optLong("start", po.optLong("st", 0));
                            ep.stop = po.optLong("stop", po.optLong("end", po.optLong("en", 0)));
                            ch.programs.add(ep);
                        }
                    }
                } catch(Exception ignored) {}

                if(ch.url.startsWith("http")) channelItems.add(ch);
                if(ch.sid.equals(sidActual)){
                    focusedChannelIndex=i;
                    playingChannelIndex=i;
                    playingSid = ch.sid;
                    playingUrl = ch.url;
                    currentIndex=i;
                }
            }
        } catch(Exception ignored) {}
    }

    private void crearMenusFullscreen() {
        guideMenu = new LinearLayout(this);
        guideMenu.setOrientation(LinearLayout.HORIZONTAL);
        guideMenu.setPadding(18,18,18,18);
        guideMenu.setVisibility(View.GONE);
        guideMenu.setBackground(fondoMenu(78));
        root.addView(guideMenu, menuLpFull());

        catMenu = new LinearLayout(this);
        catMenu.setOrientation(LinearLayout.HORIZONTAL);
        catMenu.setPadding(18,18,18,18);
        catMenu.setVisibility(View.GONE);
        catMenu.setBackground(fondoMenu(78));
        FrameLayout.LayoutParams cp = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        );
        cp.gravity = Gravity.LEFT;
        cp.setMargins(0,0,0,0);
        root.addView(catMenu, cp);
    }

    private void crearNativeHomeBase() {
        nativeHome = new LinearLayout(this);
        nativeHome.setOrientation(LinearLayout.HORIZONTAL);
        nativeHome.setPadding(18, 18, 18, 18);
        nativeHome.setBackground(fondoMenu(82));
        nativeHome.setVisibility(View.GONE);
    }

    private FrameLayout.LayoutParams menuLpFull() {
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        );
        lp.gravity = Gravity.LEFT;
        lp.setMargins(24,32,24,34);
        return lp;
    }

    private void cargarLogoEn(ImageView img, String logoUrl) {
        if (img == null || logoUrl == null || !logoUrl.startsWith("http")) return;

        Bitmap cached = logoCache.get(logoUrl);
        if (cached != null) {
            img.setImageBitmap(cached);
            return;
        }

        if(logoLoading.containsKey(logoUrl)){
            return;
        }

        logoLoading.put(logoUrl, true);

        new Thread(() -> {
            try {
                InputStream in = new URL(logoUrl).openStream();
                Bitmap bmp = BitmapFactory.decodeStream(in);
                logoCache.put(logoUrl, bmp);
                runOnUiThread(() -> {
                    try { img.setImageBitmap(bmp); } catch(Exception ignored) {}
                });
            } catch(Exception ignored) {}
            try { logoLoading.remove(logoUrl); } catch(Exception ignored) {}
        }).start();
    }

    private LinearLayout barraProgresoCanal(String st, String en) {
        LinearLayout bar = new LinearLayout(this);
        bar.setOrientation(LinearLayout.HORIZONTAL);

        int pr = osdProgreso(st == null ? "" : st, en == null ? "" : en);

        View done = new View(this);
        done.setBackgroundColor(Color.argb(210, 190, 190, 190));
        bar.addView(done, new LinearLayout.LayoutParams(0, 6, Math.max(1, pr)));

        View left = new View(this);
        left.setBackgroundColor(Color.argb(90, 90, 90, 90));
        bar.addView(left, new LinearLayout.LayoutParams(0, 6, Math.max(1, 100 - pr)));

        LinearLayout.LayoutParams blp = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, 6
        );
        blp.setMargins(0, 6, 0, 0);
        bar.setLayoutParams(blp);

        return bar;
    }

    private LinearLayout canalItemVisual(ChannelItem ch, boolean foco) {
        LinearLayout row = new LinearLayout(this);
        row.setOrientation(LinearLayout.HORIZONTAL);
        row.setGravity(Gravity.CENTER_VERTICAL);
        row.setPadding(12, 8, 12, 8);

        GradientDrawable bg = new GradientDrawable();
        bg.setCornerRadius(12);

        if (foco) {
            bg.setColor(Color.argb(125, 170, 220, 0));
            bg.setStroke(2, Color.argb(240, 255, 235, 0));
        } else {
            bg.setColor(Color.argb(22, 0, 25, 45));
            bg.setStroke(1, Color.argb(38, 0, 210, 255));
        }

        row.setBackground(bg);

        ImageView logo = new ImageView(this);
        logo.setScaleType(ImageView.ScaleType.FIT_CENTER);
        LinearLayout.LayoutParams lpLogo = new LinearLayout.LayoutParams(130, 86);
        lpLogo.setMargins(0, 0, 16, 0);
        row.addView(logo, lpLogo);
        cargarLogoEn(logo, ch.logo);

        LinearLayout texts = new LinearLayout(this);
        texts.setOrientation(LinearLayout.VERTICAL);

        TextView name = tvMini(ch.name == null ? "Canal" : ch.name, 14, Color.WHITE);
        if (foco) name.setTypeface(null, android.graphics.Typeface.BOLD);
        name.setSingleLine(true);
        name.setEllipsize(TextUtils.TruncateAt.END);

        String prog = (ch.now == null || ch.now.trim().isEmpty()) ? "Programación en Actualización" : ch.now;
        TextView now = tvMini(prog, 12, Color.LTGRAY);
        now.setSingleLine(true);
        now.setEllipsize(TextUtils.TruncateAt.END);

        texts.addView(name, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
        ));
        texts.addView(now, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
        ));
        texts.addView(barraProgresoCanal(ch.st, ch.en));

        row.addView(texts, new LinearLayout.LayoutParams(
                0, LinearLayout.LayoutParams.WRAP_CONTENT, 1
        ));

        LinearLayout.LayoutParams rlp = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        );
        rlp.setMargins(0, 4, 0, 4);
        row.setLayoutParams(rlp);

        return row;
    }

    private void reconstruirGuia() {
        if (guideMenu == null) return;
        guideMenu.removeAllViews();

        LinearLayout colCanales = new LinearLayout(this);
        colCanales.setOrientation(LinearLayout.VERTICAL);
        colCanales.addView(itemMenu(catVisible(), false, 18));

        int max=10;
        int start=Math.max(0, focusedChannelIndex-(max/2));
        if(start+max>channelItems.size()) start=Math.max(0, channelItems.size()-max);

        for(int i=start;i<channelItems.size() && i<start+max;i++){
            ChannelItem ch=channelItems.get(i);
            boolean foco=i==focusedChannelIndex;
            colCanales.addView(canalItemVisual(ch, foco));
        }

        ChannelItem ch = canalEnFoco();

        LinearLayout colProg = new LinearLayout(this);
        colProg.setOrientation(LinearLayout.VERTICAL);
        colProg.setPadding(20,18,20,18);

        LinearLayout colDetalleWrap = new LinearLayout(this);
        colDetalleWrap.setOrientation(LinearLayout.VERTICAL);
        colDetalleWrap.setPadding(18, 34, 18, 34);

        LinearLayout colDetalle = new LinearLayout(this);
        colDetalle.setOrientation(LinearLayout.VERTICAL);
        colDetalle.setPadding(24, 22, 24, 22);
        colDetalle.setBackground(fondoMenu(92));

        if(ch != null){

            LinearLayout headProg = new LinearLayout(this);
            headProg.setOrientation(LinearLayout.HORIZONTAL);
            headProg.setGravity(Gravity.CENTER_VERTICAL);
            headProg.setPadding(0, 0, 0, 8);

            ImageView logoProg = new ImageView(this);
            logoProg.setScaleType(ImageView.ScaleType.FIT_CENTER);
            cargarLogoEn(logoProg, ch.logo);

            LinearLayout.LayoutParams lpLogo = new LinearLayout.LayoutParams(88, 72);
            lpLogo.setMargins(0, 0, 12, 0);
            headProg.addView(logoProg, lpLogo);

            LinearLayout headText = new LinearLayout(this);
            headText.setOrientation(LinearLayout.VERTICAL);

            TextView headName = tvMini(ch.name == null ? "Canal" : ch.name, 16, Color.WHITE);
            headName.setTypeface(null, android.graphics.Typeface.BOLD);
            headName.setSingleLine(true);
            headName.setEllipsize(TextUtils.TruncateAt.END);

            TextView headCat = tvMini(catVisible(), 12, Color.LTGRAY);
            headCat.setSingleLine(true);
            headCat.setEllipsize(TextUtils.TruncateAt.END);

            headText.addView(headName);
            headText.addView(headCat);

            headProg.addView(headText, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1));

            colProg.addView(headProg);

            if(ch.programs != null && ch.programs.size() > 0){
                long nowTs = System.currentTimeMillis() / 1000L;
                int maxProg = Math.min(20, ch.programs.size());

                for(int pi=0; pi<maxProg; pi++){
                    EpgItem ep = ch.programs.get(pi);
                    boolean actual = nowTs >= ep.start && nowTs < ep.stop;
                    String titulo = (ep.title == null || ep.title.trim().isEmpty()) ? "Programación en Actualización" : ep.title;
                    if (actual) colProg.addView(itemActual(hora(String.valueOf(ep.start)) + "  " + titulo, 13));
                    else colProg.addView(itemMenu(hora(String.valueOf(ep.start)) + "  " + titulo, false, 13));
                }
            } else {
                colProg.addView(itemActual(
                        hora(ch.st) + "  " + safe(ch.now),
                        13
                ));

                colProg.addView(itemMenu(
                        hora(ch.ns) + "  " + safe(ch.next),
                        false,
                        13
                ));
            }

            TextView detTitle = tvMini(safe(ch.now), 15, Color.WHITE);
            detTitle.setTypeface(null, android.graphics.Typeface.BOLD);
            detTitle.setSingleLine(false);
            detTitle.setMaxLines(2);
            detTitle.setEllipsize(TextUtils.TruncateAt.END);
            colDetalle.addView(detTitle);

            colDetalle.addView(itemMenu(
                    hora(ch.st)+" - "+hora(ch.en),
                    false,
                    13
            ));

            colDetalle.addView(barraProgresoCanal(
                    ch.st,
                    ch.en
            ));

            colDetalle.addView(itemMenu(
                    "Sigue:",
                    false,
                    13
            ));

            colDetalle.addView(itemMenu(
                    safe(ch.next),
                    false,
                    13
            ));
        }

        guideMenu.addView(colCanales,new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,33));

        View sep1 = new View(this);
        sep1.setBackgroundColor(Color.argb(55,0,210,255));
        guideMenu.addView(sep1,new LinearLayout.LayoutParams(10,LinearLayout.LayoutParams.MATCH_PARENT));

        guideMenu.addView(colProg,new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,29));

        View sep2 = new View(this);
        sep2.setBackgroundColor(Color.argb(55,0,210,255));
        guideMenu.addView(sep2,new LinearLayout.LayoutParams(10,LinearLayout.LayoutParams.MATCH_PARENT));

        colDetalleWrap.addView(colDetalle, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        ));

        guideMenu.addView(colDetalleWrap,new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,38));
    }

    private void reconstruirCategorias() {
        if (catMenu == null) return;
        catMenu.removeAllViews();

        LinearLayout colCats = new LinearLayout(this);
        colCats.setOrientation(LinearLayout.VERTICAL);
        TextView tituloCats = tvMini("CATEGORIAS", 18, Color.WHITE);
        tituloCats.setTypeface(null, android.graphics.Typeface.BOLD);
        colCats.addView(tituloCats);

        if(categoryItems.size()==0){
            colCats.addView(itemMenu("Cargando categorías...", false, 16));
        } else {
            int max=16;
            int start=Math.max(0, focusedCategoryIndex-(max/2));
            if(start+max>categoryItems.size()) start=Math.max(0, categoryItems.size()-max);
            for(int i=start;i<categoryItems.size() && i<start+max;i++){
                colCats.addView(itemMenu(categoryItems.get(i).name, i==focusedCategoryIndex && catActiveColumn==1, 16));
            }
        }

        LinearLayout colCh = new LinearLayout(this);
        colCh.setOrientation(LinearLayout.VERTICAL);
        colCh.addView(itemMenu(catVisible(), catActiveColumn==2, 18));
        int max=10;
        int start=Math.max(0, focusedChannelIndex-(max/2));
        if(start+max>channelItems.size()) start=Math.max(0, channelItems.size()-max);
        for(int i=start;i<channelItems.size() && i<start+max;i++){
            ChannelItem ch=channelItems.get(i);
            colCh.addView(canalItemVisual(ch, i==focusedChannelIndex && catActiveColumn==2));
        }

        catMenu.addView(colCats, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 38));

        View sepB = new View(this);
        sepB.setBackgroundColor(Color.argb(70,0,210,255));
        catMenu.addView(sepB, new LinearLayout.LayoutParams(8, LinearLayout.LayoutParams.MATCH_PARENT));

        catMenu.addView(colCh, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 62));
    }

    private String catVisible(){
        android.util.Log.d("PUNTOZ_CAT","catName="+catName);
        if(catName == null || catName.trim().isEmpty() || catName.equalsIgnoreCase("All channels")){
            return "Canales";
        }
        return catName;
    }

    private String safe(String x){ return (x==null || x.trim().isEmpty()) ? "Programación en Actualización" : x; }

    private String hora(String raw){
        try{
            long v=Long.parseLong(raw.trim());
            java.text.SimpleDateFormat df=new java.text.SimpleDateFormat("HH:mm", new java.util.Locale("es","CL"));
            return df.format(new java.util.Date(v*1000L));
        }catch(Exception e){ return ""; }
    }

    private ChannelItem canalEnFoco(){
        if(channelItems==null || channelItems.size()==0) return null;
        if(focusedChannelIndex<0) focusedChannelIndex=0;
        if(focusedChannelIndex>=channelItems.size()) focusedChannelIndex=channelItems.size()-1;
        return channelItems.get(focusedChannelIndex);
    }

    private LinearLayout canalItemRapido(ChannelItem ch, int distancia) {
        LinearLayout row = new LinearLayout(this);
        row.setOrientation(LinearLayout.HORIZONTAL);
        row.setGravity(Gravity.CENTER);
        row.setPadding(0, 0, 0, 0);
        row.setBackgroundColor(Color.TRANSPARENT);

        int abs = Math.abs(distancia);

        float alpha;
        int altoFila;
        int logoW;
        int logoH;

        if (abs == 0) {
            alpha = 1.00f;
            altoFila = 178;
            logoW = 285;
            logoH = 132;
        } else if (abs == 1) {
            alpha = 0.82f;
            altoFila = 112;
            logoW = 170;
            logoH = 100;
        } else if (abs == 2) {
            alpha = 0.58f;
            altoFila = 96;
            logoW = 128;
            logoH = 76;
        } else if (abs == 3) {
            alpha = 0.36f;
            altoFila = 84;
            logoW = 98;
            logoH = 58;
        } else {
            alpha = 0.22f;
            altoFila = 72;
            logoW = 76;
            logoH = 44;
        }

        FrameLayout stage = new FrameLayout(this);
        stage.setBackgroundColor(Color.TRANSPARENT);

        if (distancia == 0) {
            View mancha = new View(this) {
                android.graphics.Paint paint = new android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG);

                @Override
                protected void onDraw(android.graphics.Canvas canvas) {
                    super.onDraw(canvas);

                    float w = getWidth();
                    float h = getHeight();

                    android.graphics.RadialGradient grad = new android.graphics.RadialGradient(
                            w * 0.50f,
                            h * 0.50f,
                            w * 0.52f,
                            new int[] {
                                    Color.argb(88, 155, 45, 255),
                                    Color.argb(48, 145, 35, 255),
                                    Color.argb(18, 125, 25, 210),
                                    Color.TRANSPARENT
                            },
                            new float[] {0.00f, 0.38f, 0.68f, 1.00f},
                            android.graphics.Shader.TileMode.CLAMP
                    );

                    paint.setShader(grad);
                    paint.setStyle(android.graphics.Paint.Style.FILL);

                    canvas.drawOval(
                            w * 0.08f,
                            h * 0.18f,
                            w * 0.92f,
                            h * 0.82f,
                            paint
                    );

                    paint.setShader(null);
                }
            };

            mancha.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

            stage.addView(mancha, new FrameLayout.LayoutParams(
                    430,
                    178,
                    Gravity.CENTER
            ));
        }

        ImageView logo = new ImageView(this);
        logo.setScaleType(ImageView.ScaleType.FIT_CENTER);
        logo.setAlpha(alpha);

        FrameLayout.LayoutParams lpLogo = new FrameLayout.LayoutParams(
                logoW,
                logoH,
                Gravity.CENTER
        );

        stage.addView(logo, lpLogo);
        cargarLogoEn(logo, ch.logo);

        LinearLayout.LayoutParams stageLp = new LinearLayout.LayoutParams(
                distancia == 0 ? 450 : 280,
                altoFila
        );

        row.addView(stage, stageLp);

        LinearLayout.LayoutParams rlp = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                altoFila
        );
        row.setLayoutParams(rlp);

        return row;
    }

    private void reconstruirCanalesRapido() {
        if (guideMenu == null) return;

        guideMenu.removeAllViews();
        guideMenu.setOrientation(LinearLayout.VERTICAL);
        guideMenu.setGravity(Gravity.CENTER);
        guideMenu.setPadding(0, 0, 0, 0);
        guideMenu.setBackgroundColor(Color.TRANSPARENT);

        if (channelItems == null || channelItems.size() == 0) return;

        int total = channelItems.size();

        TextView flechaArriba = tvMini("▲", 18, Color.argb(210, 255, 255, 255));
        flechaArriba.setGravity(Gravity.CENTER);
        flechaArriba.setPadding(0, 0, 0, 10);
        guideMenu.addView(flechaArriba, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                42
        ));

        for (int off = -5; off <= 5; off++) {
            int idx = (focusedChannelIndex + off + total) % total;
            ChannelItem ch = channelItems.get(idx);
            guideMenu.addView(canalItemRapido(ch, off));
        }

        TextView flechaAbajo = tvMini("▼", 18, Color.argb(210, 255, 255, 255));
        flechaAbajo.setGravity(Gravity.CENTER);
        flechaAbajo.setPadding(0, 10, 0, 0);
        guideMenu.addView(flechaAbajo, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                42
        ));
    }

    private void mostrarCanalesRapidoFullscreen() {
        menuState = 3;

        if(playingSid != null && !playingSid.isEmpty()) {
            for(int i=0;i<channelItems.size();i++){
                if(playingSid.equals(channelItems.get(i).sid)){
                    focusedChannelIndex = i;
                    break;
                }
            }
        } else if (currentIndex >= 0 && currentIndex < channelItems.size()) {
            focusedChannelIndex = currentIndex;
        }

        reconstruirCanalesRapido();

        if (osdBox != null) osdBox.setVisibility(View.GONE);
        if (catMenu != null) catMenu.setVisibility(View.GONE);

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(430, FrameLayout.LayoutParams.MATCH_PARENT);
        lp.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
        lp.setMargins(18, 0, 0, 0);
        guideMenu.setLayoutParams(lp);

        guideMenu.setVisibility(View.VISIBLE);
        guideMenu.bringToFront();
    }

    private void mostrarGuiaMenu(){
        menuState=1;
        if(playingSid != null && !playingSid.isEmpty()) {
            for(int i=0;i<channelItems.size();i++){
                if(playingSid.equals(channelItems.get(i).sid)){
                    focusedChannelIndex = i;
                    break;
                }
            }
        }
        reconstruirGuia();
        if(osdBox!=null) osdBox.setVisibility(View.GONE);
        if(catMenu!=null) catMenu.setVisibility(View.GONE);
        guideMenu.setVisibility(View.VISIBLE);
        guideMenu.bringToFront();
    }

    private void mostrarCategoriasMenu(){
        menuState=2;
        catActiveColumn=1;
        cargarCategoriasLigero();
        reconstruirCategorias();
        if(osdBox!=null) osdBox.setVisibility(View.GONE);
        if(guideMenu!=null) guideMenu.setVisibility(View.GONE);
        catMenu.setVisibility(View.VISIBLE);
        catMenu.bringToFront();
    }

    private void cerrarMenus(){
        menuState=0;
        if(guideMenu!=null) {
            guideMenu.setVisibility(View.GONE);
            guideMenu.setOrientation(LinearLayout.HORIZONTAL);
            guideMenu.setLayoutParams(menuLpFull());
        }
        if(catMenu!=null) catMenu.setVisibility(View.GONE);
    }

    private void seleccionarSeccionNativa(){
        if(sectionItems.size()==0) return;
        SectionItem si = sectionItems.get(focusedSectionIndex);
        if(si == null) return;

        String id = si.id == null ? "" : si.id;

        if("refresh".equals(id)){
            categoryItems.clear();
            channelItems.clear();
            focusedCategoryIndex = 0;
            focusedChannelIndex = 0;
            catActiveColumn = 1;
            cargarCategoriasLigero(androidSectionActual, androidFolderActual, true);
            reconstruirCategorias();
            return;
        }

        if("settings".equals(id) || "search".equals(id) || "favorites".equals(id) || "recordings".equals(id)){
            catName = si.name;
            reconstruirCategorias();
            return;
        }

        androidSectionActual = id;
        androidFolderActual = "";
        categoryItems.clear();
        channelItems.clear();
        focusedCategoryIndex = 0;
        focusedChannelIndex = 0;
        catActiveColumn = 1;
        catName = si.name;

        cargarCategoriasLigero(id, "", true);
        reconstruirCategorias();
    }

    private void moverCanalFoco(int delta){
        if(channelItems.size()==0) return;
        focusedChannelIndex=(focusedChannelIndex+delta+channelItems.size())%channelItems.size();
        if(menuState==1) reconstruirGuia();
        if(menuState==2) reconstruirCategorias();
        if(menuState==3) reconstruirCanalesRapido();
    }

    private void moverCategoriaFoco(int delta){
        if(menuState==2 && catActiveColumn==0){
            if(sectionItems.size()==0) return;
            focusedSectionIndex=(focusedSectionIndex+delta+sectionItems.size())%sectionItems.size();
            reconstruirCategorias();
            return;
        }

        if(categoryItems.size()==0) return;
        focusedCategoryIndex=(focusedCategoryIndex+delta+categoryItems.size())%categoryItems.size();

        reconstruirCategorias();
        precargarCategoriasCercanas();

        if(menuState==2 && catActiveColumn==1){
            if(cargarCategoriaPendiente != null){
                root.removeCallbacks(cargarCategoriaPendiente);
                cargarCategoriaPendiente = null;
            }
            cargarCategoriaSeleccionada();
        }
    }

    private void reproducirCanalFoco(){
        if(cargandoSoloListaCategoria) return;
        ChannelItem ch=canalEnFoco();
        if(ch==null || !ch.url.startsWith("http")) return;

        currentUrl=ch.url;
        playingChannelIndex=focusedChannelIndex;
        playingSid = ch.sid;
        playingUrl = ch.url;

        try{
            org.json.JSONArray arr=new org.json.JSONArray();
            for(ChannelItem c: channelItems){
                org.json.JSONObject o=new org.json.JSONObject();
                o.put("url",c.url); o.put("name",c.name); o.put("logo",c.logo); o.put("sid",c.sid);
                o.put("now",c.now); o.put("next",c.next); o.put("st",c.st); o.put("en",c.en); o.put("ns",c.ns); o.put("ne",c.ne); o.put("overlay_enabled",c.overlay_enabled); o.put("overlay_logo",c.overlay_logo); o.put("overlay_x",c.overlay_x); o.put("overlay_y",c.overlay_y); o.put("overlay_size",c.overlay_size); o.put("overlay_opacity",c.overlay_opacity);
                arr.put(o);
            }
            org.json.JSONObject o=new org.json.JSONObject();
            o.put("url",ch.url);
            o.put("name",ch.name);
            o.put("logo",ch.logo);
            o.put("sid",ch.sid);
            o.put("now",ch.now);
            o.put("next",ch.next);
            o.put("st",ch.st);
            o.put("en",ch.en);
            o.put("ns",ch.ns);
            o.put("ne",ch.ne);
            o.put("overlay_enabled",ch.overlay_enabled);
            o.put("overlay_logo",ch.overlay_logo);
            o.put("overlay_x",ch.overlay_x);
            o.put("overlay_y",ch.overlay_y);
            o.put("overlay_size",ch.overlay_size);
            o.put("overlay_opacity",ch.overlay_opacity);
            currentPayload=payloadDesdeCanal(o, arr, catName);
            parsePayloadChannels(currentPayload);
        }catch(Exception ignored){}

        try { if (overlayLogo != null) overlayLogo.setVisibility(View.GONE); } catch(Exception ignored) {}
        try { if (overlayLogoGif != null) { overlayLogoGif.setVisibility(View.GONE); overlayLogoGif.loadUrl("about:blank"); } } catch(Exception ignored) {}
        try { ocultarPublicidad(); } catch(Exception ignored) {}

        detenerVideoHtml();
        playUrl(ch.url);
        cerrarMenus();

        /* PZ_FAST_PLAY:
         * No recargar WebView al reproducir canal.
         * Evita lentitud, reset visual y posible doble audio.
         */

        mostrarOSDDesdePayload(currentPayload);

        try {
            if (fullscreen) {
                mostrarOverlayLogoPorSid(ch.sid);
                mostrarPublicidadPorSid(ch.sid);
            } else {
                if (overlayLogo != null) overlayLogo.setVisibility(View.GONE);
                if (overlayLogoGif != null) { overlayLogoGif.setVisibility(View.GONE); overlayLogoGif.loadUrl("about:blank"); }
                ocultarPublicidad();
            }
        } catch(Exception ignored) {}
    }

    private void cargarCategoriasLigero(){
        cargarCategoriasLigero(androidSectionActual, androidFolderActual, false);
    }

    private void cargarCategoriasLigero(String section, String folder, boolean force){
        if(!force && categoryItems.size()>0) return;

        androidSectionActual = section == null ? "" : section;
        androidFolderActual = folder == null ? "" : folder;

        String cacheKey = androidSectionActual + "||" + androidFolderActual;

        if(seccionesCache.containsKey(cacheKey)){
            categoryItems = new ArrayList<>(seccionesCache.get(cacheKey));
            runOnUiThread(this::reconstruirCategorias);
            return;
        }

        new Thread(() -> {
            try{
                String qs = "android_menu=1";
                if(androidSectionActual != null && !androidSectionActual.trim().isEmpty()){
                    qs += "&section=" + URLEncoder.encode(androidSectionActual, "UTF-8");
                }
                if(androidFolderActual != null && !androidFolderActual.trim().isEmpty()){
                    qs += "&folder=" + URLEncoder.encode(androidFolderActual, "UTF-8");
                }

                URL u=new URL("http://216.152.155.250/PUNTOZ_PANEL/tv_clean.php?" + qs);
                HttpURLConnection c=(HttpURLConnection)u.openConnection();
                c.setConnectTimeout(5000); c.setReadTimeout(8000);
                InputStream is=c.getInputStream();
                byte[] buf=new byte[32768]; StringBuilder sb=new StringBuilder(); int n;
                while((n=is.read(buf))>0) sb.append(new String(buf,0,n,"UTF-8"));
                is.close();
                org.json.JSONObject json=new org.json.JSONObject(sb.toString());
                org.json.JSONArray cats=json.optJSONArray("categories");
                ArrayList<CategoryItem> tmp=new ArrayList<>();
                if(cats!=null){
                    for(int i=0;i<cats.length();i++){
                        org.json.JSONObject o=cats.getJSONObject(i);
                        CategoryItem ci=new CategoryItem();
                        ci.id=o.optString("id","");
                        ci.name=o.optString("name","Sin nombre");
                        ci.type=o.optString("type","category");
                        ci.section=o.optString("section","");
                        ci.folder=o.optString("folder","");
                        ci.firstCat=o.optString("first_cat","");
                        if(ci.id.length()>0) tmp.add(ci);
                    }
                }
                String cacheKey2 = androidSectionActual + "||" + androidFolderActual;
                seccionesCache.put(cacheKey2, new ArrayList<>(tmp));

                runOnUiThread(() -> {
                    categoryItems=tmp;
                    reconstruirCategorias();
                });
            }catch(Exception ignored){}
        }).start();
    }

    private void precargarCategoriasCercanas(){
        if(prefetchCategoriasActivo) return;
        if(categoryItems == null || categoryItems.size() == 0) return;

        prefetchCategoriasActivo = true;

        new Thread(() -> {
            try{
                int total = categoryItems.size();
                int start = Math.max(0, focusedCategoryIndex - 3);
                int end = Math.min(total - 1, focusedCategoryIndex + 8);

                for(int i=start; i<=end; i++){
                    CategoryItem ci = categoryItems.get(i);
                    if(ci == null || ci.id == null || ci.id.trim().isEmpty()) continue;
                    if(canalesCache.containsKey(ci.id)) continue;

                    try{
                        URL u=new URL("http://216.152.155.250/PUNTOZ_PANEL/tv_clean.php?android_menu=1&cat="+URLEncoder.encode(ci.id,"UTF-8"));
                        HttpURLConnection c=(HttpURLConnection)u.openConnection();
                        c.setConnectTimeout(3500);
                        c.setReadTimeout(9000);
                        InputStream is=c.getInputStream();
                        byte[] buf=new byte[32768];
                        StringBuilder sb=new StringBuilder();
                        int n;
                        while((n=is.read(buf))>0) sb.append(new String(buf,0,n,"UTF-8"));
                        is.close();

                        org.json.JSONObject json=new org.json.JSONObject(sb.toString());
                        org.json.JSONArray chans=json.optJSONArray("channels");
                        ArrayList<ChannelItem> tmp=new ArrayList<>();

                        if(chans!=null){
                            for(int x=0;x<chans.length();x++){
                                org.json.JSONObject o=chans.getJSONObject(x);
                                ChannelItem ch=new ChannelItem();
                                ch.url=o.optString("url","");
                                ch.name=o.optString("name","Canal");
                                ch.logo=o.optString("logo","");
                                ch.sid=o.optString("sid","");
                                ch.now=o.optString("now","");
                                ch.next=o.optString("next","");
                                ch.st=o.optString("st","");
                                ch.en=o.optString("en","");
                                ch.ns=o.optString("ns","");
                                ch.ne=o.optString("ne","");
                                ch.overlay_enabled=o.optString("overlay_enabled","0");
                                ch.overlay_logo=o.optString("overlay_logo","");
                                ch.overlay_x=o.optString("overlay_x","0");
                                ch.overlay_y=o.optString("overlay_y","0");
                                ch.overlay_size=o.optString("overlay_size","90");
                                ch.overlay_opacity=o.optString("overlay_opacity","100");
                                if(ch.url.startsWith("http")) tmp.add(ch);
                            }
                        }

                        canalesCache.put(ci.id, tmp);
                    }catch(Exception ignored){}
                }
            }catch(Exception ignored){}
            prefetchCategoriasActivo = false;
        }).start();
    }

    private void cargarCategoriaSeleccionada(){
        if(categoryItems.size()==0) return;
        CategoryItem ci=categoryItems.get(focusedCategoryIndex);
        catName=ci.name;

        ArrayList<ChannelItem> cached = canalesCache.get(ci.id);
        if(cached != null){
            int keepCol = catActiveColumn;
            cargandoSoloListaCategoria = true;
            channelItems = new ArrayList<>(cached);
            focusedChannelIndex = 0;
            catActiveColumn = keepCol;
            reconstruirCategorias();
            cargandoSoloListaCategoria = false;
            return;
        }

        new Thread(() -> {
            try{
                URL u=new URL("http://216.152.155.250/PUNTOZ_PANEL/tv_clean.php?android_menu=1&cat="+URLEncoder.encode(ci.id,"UTF-8"));
                HttpURLConnection c=(HttpURLConnection)u.openConnection();
                c.setConnectTimeout(5000); c.setReadTimeout(12000);
                InputStream is=c.getInputStream();
                byte[] buf=new byte[32768]; StringBuilder sb=new StringBuilder(); int n;
                while((n=is.read(buf))>0) sb.append(new String(buf,0,n,"UTF-8"));
                is.close();
                org.json.JSONObject json=new org.json.JSONObject(sb.toString());
                catName = json.optString("cat_name", ci.name);
                org.json.JSONArray chans=json.optJSONArray("channels");
                ArrayList<ChannelItem> tmp=new ArrayList<>();
                if(chans!=null){
                    for(int i=0;i<chans.length();i++){
                        org.json.JSONObject o=chans.getJSONObject(i);
                        ChannelItem ch=new ChannelItem();
                        ch.url=o.optString("url","");
                        ch.name=o.optString("name","Canal");
                        ch.logo=o.optString("logo","");
                        ch.sid=o.optString("sid","");
                        ch.now=o.optString("now","");
                        ch.next=o.optString("next","");
                        ch.st=o.optString("st","");
                        ch.en=o.optString("en","");
                        ch.ns=o.optString("ns","");
                        ch.ne=o.optString("ne","");
                        if(ch.url.startsWith("http")) tmp.add(ch);
                    }
                }
                canalesCache.put(ci.id, new ArrayList<>(tmp));

                runOnUiThread(() -> {
                    int keepCol = catActiveColumn;
                    cargandoSoloListaCategoria = true;
                    channelItems=tmp;
                    focusedChannelIndex=0;
                    catActiveColumn=keepCol;
                    reconstruirCategorias();
                    cargandoSoloListaCategoria = false;
                });
            }catch(Exception ignored){}
        }).start();
    }

    private void crearPlayer() {
        DefaultTrackSelector selector = new DefaultTrackSelector(this);
        selector.setParameters(selector.buildUponParameters()
                .setPreferredAudioLanguage("es")
                .setPreferredTextLanguage("es")
                .setForceHighestSupportedBitrate(false)
                .setForceLowestBitrate(false)
                .setTunnelingEnabled(false)
                .setAllowVideoMixedMimeTypeAdaptiveness(true)
                .setAllowAudioMixedMimeTypeAdaptiveness(true)
                .setAllowVideoNonSeamlessAdaptiveness(true)
        );

        DefaultLoadControl loadControl = new DefaultLoadControl.Builder()
                .setBufferDurationsMs(50000, 50000, 1000, 2000)
                .build();

        DefaultRenderersFactory renderersFactory =
                new DefaultRenderersFactory(this)
                        .setEnableDecoderFallback(true);

        player = new ExoPlayer.Builder(this, renderersFactory)
                .setTrackSelector(selector)
                .setLoadControl(loadControl)
                .build();

        playerView.setPlayer(player);

        player.addListener(new Player.Listener() {
            @Override
            public void onPlayerError(PlaybackException error) {
                try {
                    player.seekToDefaultPosition();
                    player.prepare();
                    player.play();
                } catch (Exception ignored) {}
            }

            @Override
            public void onPlaybackStateChanged(int state) {
                if (state == Player.STATE_IDLE && currentUrl != null && !currentUrl.isEmpty()) {
                    try {
                        player.prepare();
                        player.play();
                    } catch (Exception ignored) {}
                }
            }
        });
    }

    private void detenerVideoHtml() {
        webView.evaluateJavascript(
                "(function(){try{" +
                "var v=document.getElementById('mainVideo');" +
                "if(v){v.pause();v.removeAttribute('src');v.load();v.style.display='none';}" +
                "window.quickPlay=function(row){try{" +
                "document.querySelectorAll('.row').forEach(function(r){r.classList.remove('active');});" +
                "if(row){row.classList.add('active'); if(typeof updateTopFromRow==='function') updateTopFromRow(row,true);}" +
                "}catch(e){} return true;};" +
                "}catch(e){}})();", null);
    }

    private void ubicarPreviewNativo() {
        webView.evaluateJavascript(
                "(function(){var b=document.querySelector('.videoBox');" +
                "if(!b)return '';" +
                "var r=b.getBoundingClientRect();" +
                "return r.left+','+r.top+','+r.width+','+r.height;})()",
                value -> {
                    try {
                        if (value == null) return;
                        value = value.replace("\"", "").replace("\\\"", "");
                        if (value.trim().isEmpty()) return;

                        String[] p = value.split(",");
                        if (p.length < 4) return;

                        float scale = webView.getScale();
                        int x = Math.round(Float.parseFloat(p[0]) * scale);
                        int y = Math.round(Float.parseFloat(p[1]) * scale);
                        int w = Math.round(Float.parseFloat(p[2]) * scale);
                        int h = Math.round(Float.parseFloat(p[3]) * scale);

                        if (w < 100 || h < 80) return;

                        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
                        lp.leftMargin = x;
                        lp.topMargin = y;
                        playerBox.setLayoutParams(lp);
                    } catch (Exception ignored) {}
                }
        );
    }

    private void playPreview(String payload) {
        String[] parts = payload.split("###PZ###", -1);
        String url = parts.length > 0 ? Uri.decode(parts[0]) : "";
        if (!url.startsWith("http")) return;

        currentPayload = payload;
        parsePayloadChannels(payload);
        try {
            String[] pp = payload.split("###PZ###", -1);
            String sidActual = pp.length > 9 ? Uri.decode(pp[9]) : "";
            String json = pp.length > 10 ? Uri.decode(pp[10]) : "";
            JSONArray arr = new JSONArray(json);
            currentIndex = -1;
            for(int i=0;i<arr.length();i++){
                JSONObject o = arr.getJSONObject(i);
                if(sidActual.equals(o.optString("sid",""))){
                    currentIndex = i;
                    break;
                }
            }
        } catch(Exception ignored) {}

        detenerVideoHtml();
        ubicarPreviewNativo();

        playerBox.setVisibility(View.VISIBLE);
        playerBox.bringToFront();
        webView.requestFocus();

        if (!url.equals(currentUrl)) {
            currentUrl = url;
            playUrl(url);
        } else {
            try { if (player != null) player.play(); } catch (Exception ignored) {}
        }

        previewActive = true;
    }

    private void playUrl(String playUrl) {
        DefaultHttpDataSource.Factory httpFactory = new DefaultHttpDataSource.Factory()
                .setAllowCrossProtocolRedirects(true)
                .setConnectTimeoutMs(12000)
                .setReadTimeoutMs(12000)
                .setUserAgent("VLC/3.0.20 LibVLC/3.0.20");

        MediaItem item = new MediaItem.Builder().setUri(Uri.parse(playUrl)).build();
        String lower = playUrl.toLowerCase();

        if (lower.startsWith("rtsp://")) {
            player.setMediaSource(new RtspMediaSource.Factory().createMediaSource(item));
        } else if (lower.contains(".mpd")) {
            player.setMediaSource(new DashMediaSource.Factory(httpFactory).createMediaSource(item));
        } else if (lower.contains(".ism") || lower.contains("manifest")) {
            player.setMediaSource(new SsMediaSource.Factory(httpFactory).createMediaSource(item));
        } else if (lower.contains(".m3u8")) {
            int tsFlags =
                    DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES |
                    DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS;

            DefaultHlsExtractorFactory hlsExtractorFactory =
                    new DefaultHlsExtractorFactory(tsFlags, true);

            player.setMediaSource(new HlsMediaSource.Factory(httpFactory)
                    .setExtractorFactory(hlsExtractorFactory)
                    .setAllowChunklessPreparation(true)
                    .createMediaSource(item));
        } else {
            player.setMediaSource(new ProgressiveMediaSource.Factory(httpFactory).createMediaSource(item));
        }

        player.prepare();
        player.play();
    }

    private void entrarFullscreenMismoPlayer() {
        if (!previewActive) return;

        fullscreen = true;
        playerBox.bringToFront();

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
        );
        lp.leftMargin = 0;
        lp.topMargin = 0;
        playerBox.setLayoutParams(lp);
        playerBox.setVisibility(View.VISIBLE);

        if (osdBox != null) {
            osdBox.bringToFront();
            osdBox.setVisibility(View.GONE);
        }

        try { if (player != null) player.play(); } catch (Exception ignored) {}

        try {
            if (overlayLogo != null) {
                FrameLayout.LayoutParams overlayLp = new FrameLayout.LayoutParams(260, 110);
                overlayLp.gravity = Gravity.TOP | Gravity.LEFT;
                overlayLp.leftMargin = 60;
                overlayLp.topMargin = 60;
                overlayLogo.setLayoutParams(overlayLp);
                overlayLogo.setVisibility(View.VISIBLE);
                overlayLogo.bringToFront();
            }
        } catch(Exception ignored) {}

        try {
            String[] pp = currentPayload == null ? new String[0] : currentPayload.split("###PZ###", -1);
            String sidActual = pp.length > 9 ? Uri.decode(pp[9]) : "";
            mostrarOverlayLogoPorSid(sidActual);

            mostrarPublicidadPorSid(sidActual);
        } catch(Exception ignored) {}
    }

    private void salirFullscreenMismoPlayer() {
        fullscreen = false;
        try { if (overlayLogo != null) overlayLogo.setVisibility(View.GONE); } catch(Exception ignored) {}
        try { if (overlayLogoGif != null) { overlayLogoGif.setVisibility(View.GONE); overlayLogoGif.loadUrl("about:blank"); } } catch(Exception ignored) {}
        ocultarPublicidad();
        ubicarPreviewNativo();
        playerBox.setVisibility(View.VISIBLE);
        playerBox.bringToFront();
        webView.requestFocus();
    }

    private void abrirPlayerCompleto(String payload) {
        /*
         * OPCION B:
         * No abrir PlayerActivity.
         * Usar el mismo ExoPlayer del preview y agrandarlo.
         */
        entrarFullscreenMismoPlayer();
    }

    public class AndroidBridge {
        @JavascriptInterface
        public void abrirFullscreen(String url) {
            entrarFullscreenMismoPlayer();
        }

        @JavascriptInterface
        public void relayoutPreview() {
            runOnUiThread(() -> {
                try {
                    if (!fullscreen) {
                        root.postDelayed(() -> ubicarPreviewNativo(), 80);
                        root.postDelayed(() -> ubicarPreviewNativo(), 220);
                        root.postDelayed(() -> ubicarPreviewNativo(), 500);
                    }
                } catch(Exception ignored) {}
            });
        }
    }

    private void manejarEnter() {
        webView.evaluateJavascript(
                "(function(){"+
                "var f=document.querySelector('.focus');"+
                "var r=null;"+
                "if(f&&f.classList.contains('videoBox')) r=document.querySelector('.row.active');"+
                "else if(f&&f.classList.contains('row')) r=f;"+
                "else r=document.querySelector('.row.active');"+
                "if(!r){var ae=document.activeElement;if(ae&&ae.classList&&ae.classList.contains('cat')){return 'CAT';}if(ae){ae.click();return 'CLICK';}return '';}"+
                "var u=r.getAttribute('data-play')||'';"+
                "var n=r.getAttribute('data-name')||'Canal';"+
                "var l=r.getAttribute('data-logo')||'';"+
                "var sid=r.getAttribute('data-stream-id')||'';"+
                "var a=r.getAttribute('data-now')||'No information';"+
                "var nx=r.getAttribute('data-next')||'No information';"+
                "var ns=r.getAttribute('data-next-start')||'';"+
                "var ne=r.getAttribute('data-next-end')||'';"+
                "var st=r.getAttribute('data-start')||'';"+
                "var en=r.getAttribute('data-end')||'';"+
                "var cat=(document.body&&document.body.getAttribute('data-cat-name'))||r.getAttribute('data-cat-name')||'Canales';"+
                "var rows=Array.from(document.querySelectorAll('.row36,.row')).filter(function(x){return x.getAttribute('data-play');});"+
                "var list=rows.map(function(x,i){return {"+
                "url:x.getAttribute('data-play')||'',"+
                "name:x.getAttribute('data-name')||'Canal',"+
                "logo:x.getAttribute('data-logo')||'',"+
                "sid:x.getAttribute('data-stream-id')||'',"+
                "now:x.getAttribute('data-now')||'',"+
                "next:x.getAttribute('data-next')||'',"+
                "st:x.getAttribute('data-start')||'',"+
                "en:x.getAttribute('data-end')||'',"+
                "ns:x.getAttribute('data-next-start')||'',"+
                "ne:x.getAttribute('data-next-end')||'',"+
                "idx:i"+
                "};});"+
                "var cj=JSON.stringify(list);"+
                "var p=encodeURIComponent(u)+'###PZ###'+encodeURIComponent(n)+'###PZ###'+encodeURIComponent(l)+'###PZ###'+encodeURIComponent(a)+'###PZ###'+encodeURIComponent(nx)+'###PZ###'+encodeURIComponent(st)+'###PZ###'+encodeURIComponent(en)+'###PZ###'+encodeURIComponent(ns)+'###PZ###'+encodeURIComponent(ne)+'###PZ###'+encodeURIComponent(sid)+'###PZ###'+encodeURIComponent(cj)+'###PZ###'+encodeURIComponent(cat);"+
                "if(f&&f.classList.contains('videoBox')) return 'VIDEO:'+p;"+
                "if(f&&f.classList.contains('row')) return 'ROW:'+p;"+
                "var ae2=document.activeElement;if(ae2&&ae2.classList&&ae2.classList.contains('cat')){return 'CAT';}if(ae2){ae2.click();return 'CLICK';}"+
                "return '';"+
                "})()",
                value -> {
                    if (value == null) return;
                    value = value.replace("\"", "").replace("\\\"", "");

                    if (value.startsWith("CAT")) {
                        webView.evaluateJavascript(
                            "(function(){try{var c=document.querySelector('.catList a.cat.focus')||document.querySelector('.cats a.cat.focus')||document.activeElement;var h=c&&c.getAttribute?c.getAttribute('href'):'';h=String(h||'').replace(/&amp;/g,'&');if(h&&h.indexOf('folder=')>=0){window.location.href=h+(h.indexOf('?')>=0?'&':'?')+'pz_auto_layout2=1';return 'FOLDER';}var go=function(){var ev=new KeyboardEvent('keydown',{key:'ArrowRight',bubbles:true,cancelable:true});document.dispatchEvent(ev);};if(h&&window.pzLoadCatSinReload){var done=false;var fn=function(){if(done)return;done=true;try{document.removeEventListener('pzCatLoaded',fn);}catch(e){}go();};document.addEventListener('pzCatLoaded',fn);window.pzLoadCatSinReload(h);setTimeout(fn,900);return 'LOADCAT';}go();return 'RIGHT';}catch(e){return 'ERR';}})();",
                            null
                        );
                        return;
                    }

                    if (value.startsWith("VIDEO:")) {
                        if (previewActive) abrirPlayerCompleto(currentPayload);
                        return;
                    }

                    if (value.startsWith("ROW:")) {
                        String payload = value.substring(4);
                        String[] parts = payload.split("###PZ###", -1);
                        String url = parts.length > 0 ? Uri.decode(parts[0]) : "";

                        if (previewActive && url.equals(currentUrl)) {
                            abrirPlayerCompleto(currentPayload);
                        } else {
                            playPreview(payload);
                        }
                    }
                }
        );
    }

    @Override
    protected void onResume() {
        super.onResume();
        detenerVideoHtml();
        if (!fullscreen) ubicarPreviewNativo();
    }

    private String payloadDesdeCanal(JSONObject o, JSONArray arr, String cat) {
        String u = o.optString("url","");
        String n = o.optString("name","Canal");
        String l = o.optString("logo","");
        String sid = o.optString("sid","");
        String a = o.optString("now","No information");
        String nx = o.optString("next","No information");
        String st = o.optString("st","");
        String en = o.optString("en","");
        String ns = o.optString("ns","");
        String ne = o.optString("ne","");
        String ovEnabled = o.optString("overlay_enabled","0");
        String ovLogo = o.optString("overlay_logo","");
        String ovX = o.optString("overlay_x","0");
        String ovY = o.optString("overlay_y","0");
        String ovSize = o.optString("overlay_size","90");
        String ovOpacity = o.optString("overlay_opacity","100");

        return Uri.encode(u)+"###PZ###"+
                Uri.encode(n)+"###PZ###"+
                Uri.encode(l)+"###PZ###"+
                Uri.encode(a)+"###PZ###"+
                Uri.encode(nx)+"###PZ###"+
                Uri.encode(st)+"###PZ###"+
                Uri.encode(en)+"###PZ###"+
                Uri.encode(ns)+"###PZ###"+
                Uri.encode(ne)+"###PZ###"+
                Uri.encode(sid)+"###PZ###"+
                Uri.encode(arr.toString())+"###PZ###"+
                Uri.encode(cat == null ? "Canales" : cat)+"###PZ###"+
                Uri.encode(ovEnabled)+"###PZ###"+
                Uri.encode(ovLogo)+"###PZ###"+
                Uri.encode(ovX)+"###PZ###"+
                Uri.encode(ovY)+"###PZ###"+
                Uri.encode(ovSize)+"###PZ###"+
                Uri.encode(ovOpacity);
    }

    private boolean cambiarCanalFullscreen(int key) {
        if (!fullscreen) return false;
        boolean esCanalArribaAbajo =
                key == KeyEvent.KEYCODE_DPAD_UP ||
                key == KeyEvent.KEYCODE_DPAD_DOWN ||
                key == KeyEvent.KEYCODE_CHANNEL_UP ||
                key == KeyEvent.KEYCODE_CHANNEL_DOWN ||
                key == KeyEvent.KEYCODE_PLUS ||
                key == KeyEvent.KEYCODE_MINUS;
        if (!esCanalArribaAbajo) return false;
        if (currentPayload == null || currentPayload.trim().isEmpty()) return true;

        try {
            String[] parts = currentPayload.split("###PZ###", -1);
            String json = parts.length > 10 ? Uri.decode(parts[10]) : "";
            String cat = parts.length > 11 ? Uri.decode(parts[11]) : "Canales";

            JSONArray arr = new JSONArray(json);
            if (arr.length() == 0) return true;

            if (currentIndex < 0 || currentIndex >= arr.length()) currentIndex = 0;

            int delta = (key == KeyEvent.KEYCODE_DPAD_UP ||
                         key == KeyEvent.KEYCODE_CHANNEL_UP ||
                         key == KeyEvent.KEYCODE_PLUS) ? 1 : -1;
            int next = (currentIndex + delta + arr.length()) % arr.length();

            JSONObject o = arr.getJSONObject(next);
            String payload = payloadDesdeCanal(o, arr, cat);

            currentIndex = next;
            currentPayload = payload;

            String[] pp = payload.split("###PZ###", -1);
            String url = pp.length > 0 ? Uri.decode(pp[0]) : "";

            if (url.startsWith("http")) {
                currentUrl = url;

                try { if (overlayLogo != null) overlayLogo.setVisibility(View.GONE); } catch(Exception ignored) {}
                try { if (overlayLogoGif != null) { overlayLogoGif.setVisibility(View.GONE); overlayLogoGif.loadUrl("about:blank"); } } catch(Exception ignored) {}
                try { ocultarPublicidad(); } catch(Exception ignored) {}

                playUrl(url);
                mostrarOSDDesdePayload(currentPayload);
                mostrarOverlayLogoPorSid(o.optString("sid",""));
                mostrarPublicidadPorSid(o.optString("sid",""));
            }
        } catch(Exception ignored) {}

        return true;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            int key = event.getKeyCode();

            if (fullscreen) {
                if(menuState==0 && cambiarCanalFullscreen(key)) return true;

                if (key == KeyEvent.KEYCODE_DPAD_LEFT) {
                    if(menuState==0) mostrarGuiaMenu();
                    else if(menuState==1){ mostrarCategoriasMenu(); precargarCategoriasCercanas(); }
                    else if(menuState==2){
                        if(catActiveColumn==2){ catActiveColumn=1; reconstruirCategorias(); }
                        else if(catActiveColumn==1){ reconstruirCategorias(); }
                    }
                    return true;
                }

                if (key == KeyEvent.KEYCODE_DPAD_RIGHT) {
                    if(menuState==2){
                        mostrarGuiaMenu();
                        return true;
                    }
                    return true;
                }

                if (key == KeyEvent.KEYCODE_DPAD_UP) {
                    if(menuState==1 || menuState==3 || (menuState==2 && catActiveColumn==2)){ moverCanalFoco(-1); return true; }
                    if(menuState==2 && catActiveColumn==1){ moverCategoriaFoco(-1); return true; }
                }

                if (key == KeyEvent.KEYCODE_DPAD_DOWN) {
                    if(menuState==1 || menuState==3 || (menuState==2 && catActiveColumn==2)){ moverCanalFoco(1); return true; }
                    if(menuState==2 && catActiveColumn==1){ moverCategoriaFoco(1); return true; }
                }

                if (key == KeyEvent.KEYCODE_DPAD_CENTER || key == KeyEvent.KEYCODE_ENTER) {

                    if(System.currentTimeMillis() < blockEnterUntilMs) return true;
                    if(menuState!=0){
                        if(menuState==2 && catActiveColumn==0){
                            seleccionarSeccionNativa();
                            return true;
                        }

                        if(menuState==2 && catActiveColumn==1){
                            if(cargarCategoriaPendiente != null){
                                root.removeCallbacks(cargarCategoriaPendiente);
                                cargarCategoriaPendiente = null;
                            }

                            if(categoryItems.size() > 0){
                                CategoryItem ci = categoryItems.get(focusedCategoryIndex);

                                if("folder".equalsIgnoreCase(ci.type)){
                                    String sec = ci.section == null ? "" : ci.section;
                                    String fol = ci.folder == null ? "" : ci.folder;

                                    categoryItems.clear();
                                    channelItems.clear();
                                    focusedCategoryIndex = 0;
                                    focusedChannelIndex = 0;
                                    catActiveColumn = 1;
                                    catName = ci.name;

                                    cargarCategoriasLigero(sec, fol, true);
                                    reconstruirCategorias();
                                    return true;
                                }

                                ArrayList<ChannelItem> cached = canalesCache.get(ci.id);
                                if(cached != null){
                                    channelItems = new ArrayList<>(cached);
                                    focusedChannelIndex = 0;
                                    catActiveColumn = 2;
                                    reconstruirCategorias();
                                    return true;
                                }
                            }

                            catActiveColumn=2;
                            cargarCategoriaSeleccionada();
                            return true;
                        }
                        reproducirCanalFoco();
                        return true;
                    }

                    long nowEnter = System.currentTimeMillis();
                    if (lastFullscreenEnterMs > 0 && (nowEnter - lastFullscreenEnterMs) < 650) {
                        lastFullscreenEnterMs = 0L;
                        mostrarCanalesRapidoFullscreen();
                        return true;
                    }

                    lastFullscreenEnterMs = nowEnter;
                    mostrarOSDDesdePayload(currentPayload);
                    return true;
                }

                if (key == KeyEvent.KEYCODE_BACK || key == KeyEvent.KEYCODE_ESCAPE) {
                    if(menuState!=0){ cerrarMenus(); return true; }
                    if(osdBox!=null && osdBox.getVisibility()==View.VISIBLE){ osdBox.setVisibility(View.GONE); return true; }
                    salirFullscreenMismoPlayer();
                    return true;
                }
            }

            if (key == KeyEvent.KEYCODE_BACK || key == KeyEvent.KEYCODE_ESCAPE) {
                webView.evaluateJavascript(
                    "(function(){try{if(document.body&&document.body.classList&&document.body.classList.contains('pzLayout2')){var ev=new KeyboardEvent('keydown',{key:'ArrowLeft',bubbles:true,cancelable:true});document.dispatchEvent(ev);return 'HANDLED_LAYOUT2';}return 'NO_LAYOUT2';}catch(e){return 'ERR';}})();",
                    value -> {}
                );
                return true;
            }

            if (key == KeyEvent.KEYCODE_DPAD_CENTER || key == KeyEvent.KEYCODE_ENTER) {
                manejarEnter();
                return true;
            }
        }

        return super.dispatchKeyEvent(event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            if (player != null) {
                player.release();
                player = null;
            }
        } catch (Exception ignored) {}
    }
}
