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.widget.TextView;
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.view.Gravity;
import android.graphics.drawable.GradientDrawable;

import androidx.media3.common.MediaItem;
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.hls.DefaultHlsExtractorFactory;
import androidx.media3.exoplayer.hls.HlsMediaSource;
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 View osdProgressDone;
    private View osdProgressLeft;
    private Runnable osdHide;
    private LinearLayout guideMenu;
    private LinearLayout catMenu;
    private int menuState = 0;
    private int focusedChannelIndex = 0;
    private int playingChannelIndex = 0;
    private int focusedCategoryIndex = 0;
    private int catActiveColumn = 0;
    private ArrayList<ChannelItem> channelItems = new ArrayList<>();
    private ArrayList<CategoryItem> categoryItems = new ArrayList<>();
    private HashMap<String, ArrayList<ChannelItem>> canalesCache = new HashMap<>();
    private HashMap<String, Bitmap> logoCache = new HashMap<>();
    private String catName = "Canales";
    private long blockEnterUntilMs = 0;

    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);

        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
        ));

        root.addView(playerBox, new FrameLayout.LayoutParams(1, 1));
        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;
                }
                view.loadUrl(url);
                return true;
            }

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

        crearPlayer();

        webView.clearCache(true);
        webView.clearHistory();
        webView.clearFormData();
        webView.loadUrl(PANEL_URL + "&v=" + System.currentTimeMillis());
    }

    private static class ChannelItem {
        String url="", name="Canal", logo="", sid="", now="", next="", st="", en="", ns="", ne="";
    }

    private static class CategoryItem {
        String id="", 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(120, 255, 220, 0));
            bg.setStroke(2, Color.argb(230, 255, 220, 0));
            bg.setCornerRadius(12);
            t.setBackground(bg);
        }
        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 cargarLogoOSD(String logoUrl) {
        if (logoUrl == null || !logoUrl.startsWith("http") || osdLogo == null) return;
        new Thread(() -> {
            try {
                Bitmap bmp = BitmapFactory.decodeStream(new URL(logoUrl).openStream());
                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("Ahora: " + now);
        osdTime.setText(osdHora(st) + (osdHora(st).length() > 0 || osdHora(en).length() > 0 ? " – " : "") + osdHora(en));
        osdNext.setText("Sigue: " + next);
        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]) : "";
            catName = parts.length > 11 ? Uri.decode(parts[11]) : "Canales";

            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","");
                if(ch.url.startsWith("http")) channelItems.add(ch);
                if(ch.sid.equals(sidActual)){
                    focusedChannelIndex=i;
                    playingChannelIndex=i;
                    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(1120, FrameLayout.LayoutParams.MATCH_PARENT);
        cp.gravity = Gravity.LEFT;
        cp.setMargins(24,32,0,34);
        root.addView(catMenu, cp);
    }

    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;
        }

        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) {}
        }).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);
        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(catName, true, 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));
        }

        LinearLayout colInfo = new LinearLayout(this);
        colInfo.setOrientation(LinearLayout.VERTICAL);
        colInfo.setPadding(30,18,30,18);

        ChannelItem ch = canalEnFoco();
        if(ch != null){
            colInfo.addView(itemMenu("Ahora: " + safe(ch.now), true, 18));
            colInfo.addView(itemMenu(hora(ch.st)+" - "+hora(ch.en), false, 14));
            colInfo.addView(itemMenu("Sigue: " + safe(ch.next), false, 16));
            colInfo.addView(itemMenu(hora(ch.ns)+" - "+hora(ch.ne), false, 14));
        }

        guideMenu.addView(colCanales, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 40));
        View sep = new View(this);
        sep.setBackgroundColor(Color.argb(70,0,210,255));
        guideMenu.addView(sep, new LinearLayout.LayoutParams(8, LinearLayout.LayoutParams.MATCH_PARENT));
        guideMenu.addView(colInfo, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 60));
    }

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

        LinearLayout colCats = new LinearLayout(this);
        colCats.setOrientation(LinearLayout.VERTICAL);
        colCats.addView(itemMenu("CATEGORIAS", catActiveColumn==0, 18));

        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((i==focusedCategoryIndex && catActiveColumn==0?"▶ ":"   ")+categoryItems.get(i).name, i==focusedCategoryIndex && catActiveColumn==0, 16));
            }
        }

        LinearLayout colCh = new LinearLayout(this);
        colCh.setOrientation(LinearLayout.VERTICAL);
        colCh.addView(itemMenu(catName, catActiveColumn==1, 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==1));
        }

        catMenu.addView(colCats, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 42));
        View sep = new View(this);
        sep.setBackgroundColor(Color.argb(70,0,210,255));
        catMenu.addView(sep, new LinearLayout.LayoutParams(8, LinearLayout.LayoutParams.MATCH_PARENT));
        catMenu.addView(colCh, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 58));
    }

    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 void mostrarGuiaMenu(){
        menuState=1;
        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=0;
        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);
        if(catMenu!=null) catMenu.setVisibility(View.GONE);
    }

    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();
    }

    private void moverCategoriaFoco(int delta){
        if(categoryItems.size()==0) return;
        focusedCategoryIndex=(focusedCategoryIndex+delta+categoryItems.size())%categoryItems.size();
        reconstruirCategorias();
    }

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

        currentUrl=ch.url;
        playingChannelIndex=focusedChannelIndex;

        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);
                arr.put(o);
            }
            org.json.JSONObject o=new org.json.JSONObject();
            o.put("url",ch.url);
            currentPayload=payloadDesdeCanal(o, arr, catName);
        }catch(Exception ignored){}

        playUrl(ch.url);
        cerrarMenus();
        mostrarOSDDesdePayload(currentPayload);
    }

    private void cargarCategoriasLigero(){
        if(categoryItems.size()>0) return;
        new Thread(() -> {
            try{
                URL u=new URL("http://216.152.155.250/PUNTOZ_PANEL/tv_clean.php?android_menu=1");
                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");
                        if(ci.id.length()>0) tmp.add(ci);
                    }
                }
                runOnUiThread(() -> { categoryItems=tmp; reconstruirCategorias(); });
            }catch(Exception ignored){}
        }).start();
    }

    private void cargarCategoriaSeleccionada(){
        if(categoryItems.size()==0) return;
        CategoryItem ci=categoryItems.get(focusedCategoryIndex);
        catName=ci.name;
        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());
                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);
                    }
                }
                runOnUiThread(() -> {
                    channelItems=tmp;
                    focusedChannelIndex=0;
                    catActiveColumn=1;
                    reconstruirCategorias();
                });
            }catch(Exception ignored){}
        }).start();
    }

    private void crearPlayer() {
        DefaultTrackSelector selector = new DefaultTrackSelector(this);
        selector.setParameters(selector.buildUponParameters()
                .setPreferredAudioLanguage("es")
                .setPreferredTextLanguage("es")
        );

        DefaultLoadControl loadControl = new DefaultLoadControl.Builder()
                .setBufferDurationsMs(1000, 5000, 250, 500)
                .build();

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

        playerView.setPlayer(player);
    }

    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.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) {}
    }

    private void salirFullscreenMismoPlayer() {
        fullscreen = false;
        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();
        }
    }

    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){if(document.activeElement){document.activeElement.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;"+
                "if(document.activeElement){document.activeElement.click();return 'CLICK';}"+
                "return '';"+
                "})()",
                value -> {
                    if (value == null) return;
                    value = value.replace("\"", "").replace("\\\"", "");

                    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","");

        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);
    }

    private boolean cambiarCanalFullscreen(int key) {
        if (!fullscreen) return false;
        if (key != KeyEvent.KEYCODE_DPAD_UP && key != KeyEvent.KEYCODE_DPAD_DOWN) 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) ? 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;
                playUrl(url);
                mostrarOSDDesdePayload(currentPayload);
            }
        } catch(Exception ignored) {}

        return true;
    }

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

            if (fullscreen) {
                if (cambiarCanalFullscreen(key)) return true;

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

                if (key == KeyEvent.KEYCODE_DPAD_RIGHT) {
                    if(menuState==2){
                        if(catActiveColumn==0){
                            cargarCategoriaSeleccionada();
                            catActiveColumn=1;
                            blockEnterUntilMs=System.currentTimeMillis()+500;
                        }
                    }
                    return true;
                }

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

                if (key == KeyEvent.KEYCODE_DPAD_DOWN) {
                    if(menuState==1 || (menuState==2 && catActiveColumn==1)){ moverCanalFoco(1); return true; }
                    if(menuState==2 && catActiveColumn==0){ 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){ cargarCategoriaSeleccionada(); catActiveColumn=1; return true; }
                        reproducirCanalFoco();
                        return true;
                    }
                    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_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) {}
    }
}
