package online.undergroundapps.appstore;

import android.app.Activity;
import android.app.Dialog;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

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

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MainActivity extends Activity {
    private static final String STORE_UPDATE_URL = "https://install.undergroundapps.online/updates/appstore/latest.json";
    private static final int BG = Color.rgb(5, 7, 10);
    private static final int PANEL = Color.rgb(14, 18, 24);
    private static final int PANEL_2 = Color.rgb(19, 25, 34);
    private static final int BORDER = Color.rgb(42, 50, 62);
    private static final int TEXT = Color.rgb(246, 247, 249);
    private static final int MUTED = Color.rgb(174, 181, 192);
    private static final int GREEN = Color.rgb(143, 255, 61);
    private static final int PURPLE = Color.rgb(150, 86, 255);
    private static final int BLUE = Color.rgb(54, 164, 255);

    private enum Screen { HOME, APPS, MESSAGES, INSTRUCTIONS, SETTINGS }

    private FrameLayout contentHost;
    private TextView navHome, navApps, navMessages, navInstructions, navSettings, refreshButton, lastUpdatedText;
    private JSONObject catalog;
    private List<AppEntry> apps = new ArrayList<>();
    private Screen screen = Screen.HOME;
    private boolean refreshing = false;
    private final Map<Long, String> downloadTargets = new HashMap<>();
    private final Map<Long, String> downloadHashes = new HashMap<>();
    private final Map<String, Bitmap> remoteIcons = new ConcurrentHashMap<>();
    private BroadcastReceiver downloadReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        keepImmersive();
        loadCatalog();
        buildShell();
        registerDownloadReceiver();
        showScreen(Screen.HOME);
        navHome.post(() -> navHome.requestFocus());
        navHome.postDelayed(this::refreshCatalogSilent, 900);
    }

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

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) keepImmersive();
    }

    private void keepImmersive() {
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        if (Build.VERSION.SDK_INT >= 19) {
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
                    View.SYSTEM_UI_FLAG_FULLSCREEN |
                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        }
    }

    private void loadCatalog() {
        try {
            catalog = CatalogStore.load(this);
            apps = CatalogStore.apps(catalog);
        } catch (Exception e) {
            try {
                catalog = CatalogStore.loadBundled(this);
                apps = CatalogStore.apps(catalog);
            } catch (Exception fatal) {
                catalog = new JSONObject();
                apps = new ArrayList<>();
            }
        }
    }

    private void buildShell() {
        LinearLayout root = new LinearLayout(this);
        root.setOrientation(LinearLayout.VERTICAL);
        root.setBackgroundColor(BG);
        root.setPadding(dp(14), dp(10), dp(14), dp(10));

        LinearLayout header = new LinearLayout(this);
        header.setOrientation(LinearLayout.HORIZONTAL);
        header.setGravity(Gravity.CENTER_VERTICAL);
        header.setPadding(dp(10), dp(6), dp(10), dp(6));
        header.setBackground(roundRect(Color.argb(236, 11, 15, 21), Color.rgb(45, 54, 68), 1, 18));
        root.addView(header, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(76)));

        LinearLayout brand = new LinearLayout(this);
        brand.setOrientation(LinearLayout.HORIZONTAL);
        brand.setGravity(Gravity.CENTER_VERTICAL);
        ImageView logo = new ImageView(this);
        logo.setImageResource(getResources().getIdentifier("underground_logo", "drawable", getPackageName()));
        logo.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        brand.addView(logo, new LinearLayout.LayoutParams(dp(58), dp(58)));
        LinearLayout brandText = vertical();
        brandText.setPadding(dp(8), 0, 0, 0);
        brandText.addView(label("UNDERGROUND", 17, TEXT, true));
        brandText.addView(label("A P P   S T O R E", 9, MUTED, true));
        brand.addView(brandText);
        header.addView(brand, new LinearLayout.LayoutParams(dp(230), ViewGroup.LayoutParams.MATCH_PARENT));

        LinearLayout nav = new LinearLayout(this);
        nav.setOrientation(LinearLayout.HORIZONTAL);
        nav.setGravity(Gravity.CENTER_VERTICAL);
        nav.setPadding(dp(3), dp(3), dp(3), dp(3));
        nav.setBackground(roundRect(Color.argb(170, 24, 30, 40), Color.rgb(55, 65, 80), 1, 14));

        navHome = navButton("Home");
        navApps = navButton("Apps");
        navMessages = navButton("Messages");
        navInstructions = navButton("Instructions");
        navSettings = navButton("Settings");
        nav.addView(navHome); nav.addView(navApps); nav.addView(navMessages); nav.addView(navInstructions); nav.addView(navSettings);
        navHome.setOnClickListener(v -> showScreen(Screen.HOME));
        navApps.setOnClickListener(v -> showScreen(Screen.APPS));
        navMessages.setOnClickListener(v -> showScreen(Screen.MESSAGES));
        navInstructions.setOnClickListener(v -> showScreen(Screen.INSTRUCTIONS));
        navSettings.setOnClickListener(v -> showScreen(Screen.SETTINGS));
        header.addView(nav, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(52)));

        View spacer = new View(this);
        header.addView(spacer, new LinearLayout.LayoutParams(0, 1, 1f));

        LinearLayout status = vertical();
        status.setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
        refreshButton = actionButton("↻  REFRESH", false);
        refreshButton.setOnClickListener(v -> refreshCatalog());
        status.addView(refreshButton, new LinearLayout.LayoutParams(dp(148), dp(40)));
        lastUpdatedText = label(lastUpdatedLabel(), 9, MUTED, false);
        lastUpdatedText.setGravity(Gravity.END);
        LinearLayout.LayoutParams lpLast = new LinearLayout.LayoutParams(dp(180), dp(20));
        lpLast.topMargin = dp(2);
        status.addView(lastUpdatedText, lpLast);
        header.addView(status);

        contentHost = new FrameLayout(this);
        LinearLayout.LayoutParams hostParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f);
        hostParams.topMargin = dp(8);
        root.addView(contentHost, hostParams);
        setContentView(root);
        updateMessagesBadge();
    }

    private TextView navButton(String text) {
        TextView v = label(text, 14, TEXT, true);
        v.setGravity(Gravity.CENTER);
        v.setFocusable(true);
        v.setClickable(true);
        v.setPadding(dp(12), 0, dp(12), 0);
        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(48));
        p.setMargins(dp(2), 0, dp(2), 0);
        v.setLayoutParams(p);
        v.setOnFocusChangeListener((view, focused) -> styleNav((TextView) view, focused, view == activeNav()));
        return v;
    }

    private TextView activeNav() {
        if (screen == Screen.APPS) return navApps;
        if (screen == Screen.MESSAGES) return navMessages;
        if (screen == Screen.INSTRUCTIONS) return navInstructions;
        if (screen == Screen.SETTINGS) return navSettings;
        return navHome;
    }

    private void updateNavStyles() {
        styleNav(navHome, navHome.hasFocus(), screen == Screen.HOME);
        styleNav(navApps, navApps.hasFocus(), screen == Screen.APPS);
        styleNav(navMessages, navMessages.hasFocus(), screen == Screen.MESSAGES);
        styleNav(navInstructions, navInstructions.hasFocus(), screen == Screen.INSTRUCTIONS);
        styleNav(navSettings, navSettings.hasFocus(), screen == Screen.SETTINGS);
        updateMessagesBadge();
    }

    private void styleNav(TextView v, boolean focused, boolean selected) {
        if (selected || focused) {
            v.setTextColor(selected ? Color.BLACK : TEXT);
            v.setBackground(roundRect(selected ? GREEN : PANEL_2, focused && !selected ? GREEN : GREEN, focused ? 2 : 1, 12));
        } else {
            v.setTextColor(MUTED);
            v.setBackground(roundRect(Color.TRANSPARENT, Color.TRANSPARENT, 0, 12));
        }
    }

    private void showScreen(Screen next) {
        screen = next;
        updateNavStyles();
        contentHost.removeAllViews();
        if (next == Screen.APPS) renderApps();
        else if (next == Screen.MESSAGES) renderMessages();
        else if (next == Screen.INSTRUCTIONS) renderInstructions();
        else if (next == Screen.SETTINGS) renderSettings();
        else renderHome();
    }

    private void renderHome() {
        ScrollView scroll = new ScrollView(this);
        scroll.setFillViewport(true);
        scroll.setClipToPadding(false);
        LinearLayout body = vertical();
        body.setPadding(dp(6), dp(4), dp(6), dp(18));
        scroll.addView(body);

        LinearLayout intro = new LinearLayout(this);
        intro.setOrientation(LinearLayout.HORIZONTAL);
        intro.setGravity(Gravity.CENTER_VERTICAL);
        intro.setPadding(dp(20), dp(12), dp(20), dp(12));
        intro.setBackground(glassPanel(PURPLE));
        LinearLayout introText = vertical();
        introText.addView(label("YOUR APPS. YOUR DEVICE. YOUR FREEDOM.", 22, TEXT, true));
        introText.addView(label("Underground-approved apps, messages, and setup help — built for TV remotes.", 12, MUTED, false), full(dp(28), 0, dp(4), 0, 0));
        intro.addView(introText, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
        TextView online = chip(apps.size() + " APPS  •  SERVER POWERED");
        intro.addView(online, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(34)));
        body.addView(intro, full(dp(82), 0, 0, 0, 0));

        body.addView(sectionTitle("FEATURED APPS"), full(dp(42), 0, dp(8), 0, 0));
        body.addView(appRail(), full(dp(270), 0, 0, 0, 0));

        LinearLayout panels = new LinearLayout(this);
        panels.setOrientation(LinearLayout.HORIZONTAL);
        panels.setGravity(Gravity.TOP);
        LinearLayout messages = homeMessagesPanel();
        LinearLayout instructions = homeInstructionsPanel();
        LinearLayout links = homeQuickLinksPanel();
        LinearLayout.LayoutParams pp = new LinearLayout.LayoutParams(0, dp(188), 1f);
        pp.setMargins(0, dp(8), dp(8), 0);
        panels.addView(messages, pp);
        LinearLayout.LayoutParams pp2 = new LinearLayout.LayoutParams(0, dp(188), 1.16f);
        pp2.setMargins(0, dp(8), dp(8), 0);
        panels.addView(instructions, pp2);
        LinearLayout.LayoutParams pp3 = new LinearLayout.LayoutParams(0, dp(188), .84f);
        pp3.setMargins(0, dp(8), 0, 0);
        panels.addView(links, pp3);
        body.addView(panels, full(dp(200), 0, 0, 0, 0));
        contentHost.addView(scroll);
    }

    private View hero(AppEntry app) {
        LinearLayout hero = new LinearLayout(this);
        hero.setOrientation(LinearLayout.HORIZONTAL);
        hero.setGravity(Gravity.CENTER_VERTICAL);
        hero.setPadding(dp(28), dp(20), dp(24), dp(20));
        hero.setBackground(gradientPanel());

        LinearLayout left = vertical();
        TextView kicker = label("●  FEATURED APP", 11, GREEN, true);
        left.addView(kicker);
        TextView title = label(app.name, 34, TEXT, true);
        LinearLayout.LayoutParams t = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        t.topMargin = dp(6);
        left.addView(title, t);
        TextView desc = label(app.description, 15, MUTED, false);
        LinearLayout.LayoutParams d = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        d.topMargin = dp(7);
        left.addView(desc, d);

        LinearLayout chips = new LinearLayout(this);
        chips.setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout.LayoutParams cp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(34));
        cp.setMargins(0, dp(12), dp(8), 0);
        TextView cat = chip(app.category); chips.addView(cat, cp);
        if (!app.fileSize.isEmpty()) { TextView sz = chip(app.fileSize); chips.addView(sz, cp); }
        TextView device = chip("Android TV • Fire TV • Google TV"); chips.addView(device, cp);
        left.addView(chips);

        TextView details = actionButton("VIEW APP DETAILS  ›", true);
        details.setOnClickListener(v -> showAppDialog(app));
        LinearLayout.LayoutParams bp = new LinearLayout.LayoutParams(dp(230), dp(50));
        bp.topMargin = dp(14);
        left.addView(details, bp);
        hero.addView(left, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));

        ImageView image = new ImageView(this);
        image.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        setAppIcon(image, app);
        LinearLayout.LayoutParams ip = new LinearLayout.LayoutParams(dp(220), dp(220));
        ip.leftMargin = dp(20);
        hero.addView(image, ip);
        return hero;
    }

    private HorizontalScrollView appRail() {
        HorizontalScrollView scroller = new HorizontalScrollView(this);
        scroller.setHorizontalScrollBarEnabled(false);
        scroller.setFillViewport(false);
        LinearLayout row = new LinearLayout(this);
        row.setOrientation(LinearLayout.HORIZONTAL);
        row.setGravity(Gravity.CENTER_VERTICAL);
        row.setPadding(dp(2), dp(4), dp(20), dp(8));
        for (AppEntry app : apps) {
            View card = appCard(app);
            LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(dp(188), dp(245));
            p.setMargins(0, 0, dp(12), 0);
            row.addView(card, p);
        }
        scroller.addView(row, new HorizontalScrollView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
        return scroller;
    }

    private View appCard(AppEntry app) {
        LinearLayout card = vertical();
        card.setGravity(Gravity.CENTER_HORIZONTAL);
        card.setPadding(dp(10), dp(10), dp(10), dp(10));
        card.setFocusable(true);
        card.setClickable(true);
        applyFocusPanel(card, PANEL_2, GREEN, 16);

        ImageView icon = new ImageView(this);
        icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
        setAppIcon(icon, app);
        card.addView(icon, new LinearLayout.LayoutParams(dp(126), dp(126)));

        TextView name = label(app.shortName, 15, TEXT, true);
        name.setGravity(Gravity.CENTER);
        name.setMaxLines(2);
        LinearLayout.LayoutParams np = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(46));
        np.topMargin = dp(7);
        card.addView(name, np);

        String meta = "";
        if (!app.versionName.isEmpty()) meta += "v" + app.versionName;
        if (!app.fileSize.isEmpty()) meta += (meta.isEmpty() ? "" : "  |  ") + app.fileSize;
        TextView metadata = label(meta, 10, MUTED, false);
        metadata.setGravity(Gravity.CENTER);
        card.addView(metadata, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(24)));

        TextView status = label(actionLabel(app), 12, app.update ? Color.BLACK : TEXT, true);
        status.setGravity(Gravity.CENTER);
        status.setBackground(roundRect(app.update ? GREEN : Color.rgb(38, 46, 58), app.update ? GREEN : BORDER, 1, 9));
        LinearLayout.LayoutParams sp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(38));
        sp.topMargin = dp(4);
        card.addView(status, sp);

        card.setOnClickListener(v -> showAppDialog(app));
        return card;
    }

    private void renderApps() {
        ScrollView scroll = new ScrollView(this);
        scroll.setFillViewport(true);
        LinearLayout body = vertical();
        body.setPadding(dp(8), dp(14), dp(8), dp(24));
        body.addView(sectionTitle("ALL UNDERGROUND APPS (" + apps.size() + ")"));
        TextView sub = label("Apps download only when you choose Install — the App Store itself stays lightweight.", 13, MUTED, false);
        body.addView(sub, full(dp(34), 0, dp(2), 0, 0));
        body.addView(appRail(), full(dp(280), 0, dp(8), 0, 0));
        TextView hint = label("DPAD: LEFT / RIGHT selects an app • OK opens details • BACK returns Home", 12, MUTED, true);
        hint.setGravity(Gravity.CENTER_VERTICAL);
        hint.setPadding(dp(16), 0, dp(16), 0);
        hint.setBackground(roundRect(PANEL, BORDER, 1, 12));
        body.addView(hint, full(dp(52), 0, dp(12), 0, 0));
        scroll.addView(body);
        contentHost.addView(scroll);
    }

    private void renderMessages() {
        ScrollView scroll = new ScrollView(this);
        scroll.setFillViewport(true);
        LinearLayout body = vertical();
        body.setPadding(dp(8), dp(14), dp(8), dp(28));
        body.addView(sectionTitle("MESSAGES FROM UNDERGROUND"));
        body.addView(label("Announcements, new releases, service notices, and important App Store information from the Underground team.", 13, MUTED, false), full(dp(38), 0, dp(2), 0, 0));
        JSONArray messages = messagesArray();
        if (messages.length() == 0) {
            LinearLayout empty = infoCard();
            empty.addView(label("NO NEW MESSAGES", 18, TEXT, true));
            empty.addView(label("You're caught up. Use Refresh in the top HUD to check again.", 13, MUTED, false), full(dp(34), 0, dp(6), 0, 0));
            body.addView(empty, full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(12), 0, 0));
        } else {
            for (int i = 0; i < messages.length(); i++) {
                JSONObject n = messages.optJSONObject(i);
                if (n == null) continue;
                LinearLayout card = infoCard();
                String date = n.optString("date", "Underground");
                card.addView(label(date.toUpperCase(Locale.US), 10, GREEN, true));
                card.addView(label(n.optString("title", "Underground Message"), 20, TEXT, true), full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(5), 0, 0));
                card.addView(label(n.optString("body", ""), 14, MUTED, false), full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(6), 0, 0));
                body.addView(card, full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(10), 0, 0));
            }
        }
        scroll.addView(body);
        contentHost.addView(scroll);
    }

    private void renderInstructions() {
        ScrollView scroll = new ScrollView(this);
        scroll.setFillViewport(true);
        LinearLayout body = vertical();
        body.setPadding(dp(8), dp(14), dp(8), dp(28));
        body.addView(sectionTitle("HOW TO INSTALL APPS"));
        body.addView(label("Do this once on each device before your first Underground install.", 13, MUTED, false), full(dp(34), 0, dp(2), 0, 0));

        LinearLayout permission = infoCard();
        permission.setBackground(glassPanel(BLUE));
        permission.addView(label("1  •  ALLOW UNDERGROUND APP STORE", 18, TEXT, true));
        permission.addView(label("Android must allow this App Store to install downloaded APKs. The wording can be Allow from this source, Install unknown apps, or Apps from Unknown Sources depending on the device.", 13, MUTED, false), full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(7), 0, 0));
        TextView open = actionButton("OPEN INSTALL PERMISSION", true);
        open.setOnClickListener(v -> openInstallerPermission());
        LinearLayout.LayoutParams op = new LinearLayout.LayoutParams(dp(240), dp(46)); op.topMargin = dp(12);
        permission.addView(open, op);
        body.addView(permission, full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(10), 0, 0));

        JSONArray instructions = catalog.optJSONArray("instructions");
        if (instructions != null) {
            for (int i = 0; i < instructions.length(); i++) {
                JSONObject o = instructions.optJSONObject(i);
                if (o == null) continue;
                LinearLayout card = infoCard();
                card.addView(label(o.optString("title", "Instructions"), 18, TEXT, true));
                JSONArray steps = o.optJSONArray("steps");
                if (steps != null) {
                    for (int x = 0; x < steps.length(); x++) {
                        TextView step = label((x + 1) + ".  " + steps.optString(x), 13, MUTED, false);
                        LinearLayout.LayoutParams sp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                        sp.topMargin = dp(7);
                        card.addView(step, sp);
                    }
                }
                body.addView(card, full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(10), 0, 0));
            }
        }
        scroll.addView(body);
        contentHost.addView(scroll);
    }

    private LinearLayout infoCard() {
        LinearLayout card = vertical();
        card.setPadding(dp(18), dp(14), dp(18), dp(14));
        card.setFocusable(true);
        card.setClickable(true);
        applyFocusPanel(card, PANEL, PURPLE, 14);
        card.setOnClickListener(v -> {});
        return card;
    }

    private void renderSettings() {
        ScrollView scroll = new ScrollView(this);
        scroll.setFillViewport(true);
        LinearLayout body = vertical();
        body.setPadding(dp(8), dp(14), dp(8), dp(28));
        body.addView(sectionTitle("SETTINGS"));

        TextView check = settingsButton("↻  Check for New Apps & Messages", "Refresh apps, versions, download links, and Underground admin messages.");
        check.setOnClickListener(v -> refreshCatalog());
        body.addView(check, full(dp(66), 0, dp(10), 0, 0));

        TextView storeUpdate = settingsButton("↑  Check for App Store Update", "Check for a newer version of The Underground App Store itself.");
        storeUpdate.setOnClickListener(v -> checkStoreUpdate());
        body.addView(storeUpdate, full(dp(66), 0, dp(10), 0, 0));

        TextView permission = settingsButton("▣  Install Permission / Unknown Sources", "Open Android's Allow from this source / Install unknown apps screen.");
        permission.setOnClickListener(v -> openInstallerPermission());
        body.addView(permission, full(dp(66), 0, dp(10), 0, 0));

        TextView bundled = settingsButton("↺  Restore Built-In Catalog", "Discard the cached server catalog and restore the built-in server-powered six-app catalog.");
        bundled.setOnClickListener(v -> {
            CatalogStore.clearRemote(this);
            loadCatalog();
            lastUpdatedText.setText(lastUpdatedLabel());
            Toast.makeText(this, "Built-in Underground catalog restored", Toast.LENGTH_SHORT).show();
            showScreen(Screen.SETTINGS);
        });
        body.addView(bundled, full(dp(66), 0, dp(10), 0, 0));

        LinearLayout about = infoCard();
        about.addView(label("THE UNDERGROUND APP STORE", 20, TEXT, true));
        about.addView(label("Version " + BuildConfig.VERSION_NAME + "  •  Lightweight server-powered release", 13, MUTED, false), full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(6), 0, 0));
        about.addView(label("Apps are downloaded on demand from install.undergroundapps.online. Built for DPAD remotes and touch on Android TV, Google TV, Fire TV, ONN, Mi Box, Shield, and compatible Android devices.", 13, MUTED, false), full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(4), 0, 0));
        body.addView(about, full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(14), 0, 0));

        scroll.addView(body);
        contentHost.addView(scroll);
    }

    private TextView settingsButton(String title, String description) {
        TextView v = label(title + "\n" + description, 15, TEXT, true);
        v.setGravity(Gravity.CENTER_VERTICAL);
        v.setPadding(dp(18), dp(8), dp(18), dp(8));
        v.setFocusable(true);
        v.setClickable(true);
        applyFocusButton(v, false);
        return v;
    }

    private void showAppDialog(AppEntry app) {
        final Dialog dialog = new Dialog(this);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.getWindow();

        LinearLayout panel = new LinearLayout(this);
        panel.setOrientation(LinearLayout.HORIZONTAL);
        panel.setGravity(Gravity.CENTER_VERTICAL);
        panel.setPadding(dp(24), dp(22), dp(24), dp(22));
        panel.setBackground(roundRect(Color.rgb(10, 14, 20), GREEN, 2, 18));

        ImageView icon = new ImageView(this);
        icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
        setAppIcon(icon, app);
        panel.addView(icon, new LinearLayout.LayoutParams(dp(190), dp(190)));

        LinearLayout text = vertical();
        text.setPadding(dp(24), 0, 0, 0);
        TextView title = label(app.name, 26, TEXT, true);
        text.addView(title);
        String meta = app.category;
        if (!app.versionName.isEmpty()) meta += "  •  v" + app.versionName;
        if (!app.fileSize.isEmpty()) meta += "  •  " + app.fileSize;
        text.addView(label(meta, 12, GREEN, true), full(dp(28), 0, dp(5), 0, 0));
        text.addView(label(app.description, 14, MUTED, false), full(ViewGroup.LayoutParams.WRAP_CONTENT, 0, dp(8), 0, 0));
        text.addView(label("Android TV • Google TV • Fire TV • Android", 12, MUTED, false), full(dp(30), 0, dp(8), 0, 0));

        LinearLayout actions = new LinearLayout(this);
        actions.setOrientation(LinearLayout.HORIZONTAL);
        TextView install = actionButton(dialogActionLabel(app), true);
        TextView close = actionButton("CLOSE", false);
        install.setOnClickListener(v -> { dialog.dismiss(); installApp(app); });
        close.setOnClickListener(v -> dialog.dismiss());
        LinearLayout.LayoutParams ab = new LinearLayout.LayoutParams(dp(190), dp(48));
        ab.rightMargin = dp(10);
        actions.addView(install, ab);
        actions.addView(close, new LinearLayout.LayoutParams(dp(120), dp(48)));
        text.addView(actions, full(dp(56), 0, dp(14), 0, 0));
        panel.addView(text, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));

        dialog.setContentView(panel);
        Window w = dialog.getWindow();
        if (w != null) {
            w.setBackgroundDrawableResource(android.R.color.transparent);
            w.setDimAmount(0.72f);
            w.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            w.setLayout((int)(getResources().getDisplayMetrics().widthPixels * 0.78f), ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        dialog.setOnShowListener(d -> {
            Window win = dialog.getWindow();
            if (win != null) win.setLayout((int)(getResources().getDisplayMetrics().widthPixels * 0.78f), ViewGroup.LayoutParams.WRAP_CONTENT);
            install.requestFocus();
        });
        dialog.show();
    }

    private String actionLabel(AppEntry app) {
        if (app.update) return "UPDATE";
        return isInstalled(app.packageName) ? "INSTALLED ✓" : "INSTALL";
    }

    private String dialogActionLabel(AppEntry app) {
        if (app.update) return "UPDATE APP";
        return isInstalled(app.packageName) ? "REINSTALL" : "INSTALL";
    }

    private boolean isInstalled(String packageName) {
        if (packageName == null || packageName.isEmpty()) return false;
        try {
            if (Build.VERSION.SDK_INT >= 33) {
                getPackageManager().getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
            } else {
                //noinspection deprecation
                getPackageManager().getPackageInfo(packageName, 0);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private void installApp(AppEntry app) {
        if (!ensureInstallerPermission()) return;
        if (app.apkUrl != null && app.apkUrl.startsWith("https://")) {
            downloadRemoteApk(app);
        } else {
            Toast.makeText(this, "This app does not have a server download configured yet.", Toast.LENGTH_LONG).show();
        }
    }

    private boolean ensureInstallerPermission() {
        if (Build.VERSION.SDK_INT >= 26 && !getPackageManager().canRequestPackageInstalls()) {
            Toast.makeText(this, "Enable Allow from this source, then return and choose the app again.", Toast.LENGTH_LONG).show();
            openInstallerPermission();
            return false;
        }
        return true;
    }

    private void openInstallerPermission() {
        try {
            if (Build.VERSION.SDK_INT >= 26) {
                Intent i = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName()));
                startActivity(i);
            } else {
                startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
            }
        } catch (Exception e) {
            try { startActivity(new Intent(Settings.ACTION_SETTINGS)); }
            catch (Exception ignored) { Toast.makeText(this, "Open device Settings and allow unknown app installs.", Toast.LENGTH_LONG).show(); }
        }
    }

    private File installRoot() {
        File base = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
        File dir = new File(base, "underground-installers");
        if (!dir.exists()) dir.mkdirs();
        return dir;
    }

    private void prepareBundledApk(AppEntry app) {
        Toast.makeText(this, "Preparing " + app.shortName + "…", Toast.LENGTH_SHORT).show();
        new Thread(() -> {
            try {
                File out = new File(installRoot(), app.id + "-bundled.apk");
                if (!out.exists() || out.length() == 0) {
                    try (InputStream in = getAssets().open(app.assetApk); FileOutputStream fos = new FileOutputStream(out)) {
                        byte[] buf = new byte[128 * 1024];
                        int n;
                        while ((n = in.read(buf)) > 0) fos.write(buf, 0, n);
                    }
                }
                if (app.sha256 != null && !app.sha256.isEmpty()) {
                    String actual = sha256(out);
                    if (!app.sha256.equalsIgnoreCase(actual)) {
                        out.delete();
                        throw new Exception("APK checksum did not match");
                    }
                }
                runOnUiThread(() -> launchInstaller(out));
            } catch (Exception e) {
                runOnUiThread(() -> Toast.makeText(this, "Could not prepare APK: " + e.getMessage(), Toast.LENGTH_LONG).show());
            }
        }, "ug-apk-copy").start();
    }

    private String sha256(File file) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        try (InputStream in = new java.io.FileInputStream(file)) {
            byte[] b = new byte[128 * 1024];
            int n;
            while ((n = in.read(b)) > 0) md.update(b, 0, n);
        }
        StringBuilder s = new StringBuilder();
        for (byte b : md.digest()) s.append(String.format(Locale.US, "%02x", b));
        return s.toString();
    }

    private void launchInstaller(File apk) {
        try {
            Uri uri = ApkProvider.uriFor(apk);
            Intent i = new Intent(Intent.ACTION_VIEW);
            i.setDataAndType(uri, "application/vnd.android.package-archive");
            i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            startActivity(i);
        } catch (Exception e) {
            Toast.makeText(this, "No Android package installer was found.", Toast.LENGTH_LONG).show();
        }
    }

    private void downloadRemoteApk(AppEntry app) {
        try {
            DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            if (dm == null) throw new Exception("Download Manager is unavailable");
            String fileName = app.id.replaceAll("[^A-Za-z0-9._-]", "_") + "-" + System.currentTimeMillis() + ".apk";
            File target = new File(installRoot(), fileName);
            if (target.exists()) target.delete();

            DownloadManager.Request r = new DownloadManager.Request(Uri.parse(app.apkUrl));
            r.setTitle("Underground • " + app.shortName);
            r.setDescription("Downloading app package…");
            r.setMimeType("application/vnd.android.package-archive");
            r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            r.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "underground-installers/" + fileName);
            long id = dm.enqueue(r);
            downloadTargets.put(id, fileName);
            if (app.sha256 != null && !app.sha256.isEmpty()) downloadHashes.put(id, app.sha256);
            Toast.makeText(this, "Downloading " + app.shortName + "…", Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Toast.makeText(this, "Download failed to start: " + e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    private void registerDownloadReceiver() {
        downloadReceiver = new BroadcastReceiver() {
            @Override public void onReceive(Context context, Intent intent) {
                long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
                String fileName = downloadTargets.remove(id);
                String expectedHash = downloadHashes.remove(id);
                if (fileName == null) return;
                File apk = new File(installRoot(), fileName);
                if (!apk.exists() || apk.length() <= 0) {
                    Toast.makeText(MainActivity.this, "Download did not complete successfully.", Toast.LENGTH_LONG).show();
                    return;
                }
                if (expectedHash != null && !expectedHash.isEmpty()) {
                    new Thread(() -> {
                        try {
                            String actual = sha256(apk);
                            if (!expectedHash.equalsIgnoreCase(actual)) {
                                apk.delete();
                                runOnUiThread(() -> Toast.makeText(MainActivity.this, "Download verification failed. Please refresh the App Store and try again.", Toast.LENGTH_LONG).show());
                                return;
                            }
                            runOnUiThread(() -> launchInstaller(apk));
                        } catch (Exception e) {
                            runOnUiThread(() -> Toast.makeText(MainActivity.this, "Could not verify downloaded APK.", Toast.LENGTH_LONG).show());
                        }
                    }, "ug-apk-verify").start();
                } else launchInstaller(apk);
            }
        };
        IntentFilter f = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        if (Build.VERSION.SDK_INT >= 33) registerReceiver(downloadReceiver, f, Context.RECEIVER_EXPORTED);
        else {
            //noinspection deprecation
            registerReceiver(downloadReceiver, f);
        }
    }

    private void checkStoreUpdate() {
        Toast.makeText(this, "Checking for App Store update…", Toast.LENGTH_SHORT).show();
        new Thread(() -> {
            HttpURLConnection c = null;
            try {
                URL url = new URL(STORE_UPDATE_URL + "?t=" + System.currentTimeMillis());
                c = (HttpURLConnection) url.openConnection();
                c.setConnectTimeout(8000);
                c.setReadTimeout(10000);
                c.setUseCaches(false);
                c.setRequestProperty("Cache-Control", "no-cache");
                c.setRequestProperty("User-Agent", "UndergroundAppStore/" + BuildConfig.VERSION_NAME);
                int code = c.getResponseCode();
                if (code < 200 || code >= 300) throw new Exception("server returned " + code);
                JSONObject update = new JSONObject(CatalogStore.readFully(c.getInputStream()));
                int remoteCode = update.optInt("versionCode", 0);
                String remoteName = update.optString("versionName", "");
                String apkUrl = update.optString("apkUrl", "");
                if (remoteCode > BuildConfig.VERSION_CODE && apkUrl.startsWith("https://")) {
                    runOnUiThread(() -> showStoreUpdateDialog(remoteName, apkUrl));
                } else {
                    runOnUiThread(() -> Toast.makeText(this, "The Underground App Store is up to date.", Toast.LENGTH_LONG).show());
                }
            } catch (Exception e) {
                String message = e.getMessage() == null ? "server unavailable" : e.getMessage();
                runOnUiThread(() -> Toast.makeText(this, "Could not check for App Store update: " + message, Toast.LENGTH_LONG).show());
            } finally {
                if (c != null) c.disconnect();
            }
        }, "ug-store-update-check").start();
    }

    private void showStoreUpdateDialog(String versionName, String apkUrl) {
        final Dialog dialog = new Dialog(this);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        LinearLayout panel = vertical();
        panel.setPadding(dp(24), dp(22), dp(24), dp(22));
        panel.setBackground(roundRect(Color.rgb(10, 14, 20), GREEN, 2, 18));
        panel.addView(label("APP STORE UPDATE AVAILABLE", 22, TEXT, true));
        panel.addView(label("Version " + (versionName.isEmpty() ? "new" : versionName) + " is ready to install.", 14, MUTED, false), full(dp(42), 0, dp(8), 0, 0));
        LinearLayout actions = new LinearLayout(this);
        actions.setOrientation(LinearLayout.HORIZONTAL);
        TextView update = actionButton("UPDATE STORE", true);
        TextView later = actionButton("LATER", false);
        update.setOnClickListener(v -> { dialog.dismiss(); downloadStoreUpdate(apkUrl); });
        later.setOnClickListener(v -> dialog.dismiss());
        LinearLayout.LayoutParams up = new LinearLayout.LayoutParams(dp(190), dp(48));
        up.rightMargin = dp(10);
        actions.addView(update, up);
        actions.addView(later, new LinearLayout.LayoutParams(dp(120), dp(48)));
        panel.addView(actions, full(dp(58), 0, dp(14), 0, 0));
        dialog.setContentView(panel);
        dialog.setOnShowListener(d -> update.requestFocus());
        dialog.show();
        Window w = dialog.getWindow();
        if (w != null) {
            w.setBackgroundDrawableResource(android.R.color.transparent);
            w.setDimAmount(0.72f);
            w.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            w.setLayout((int)(getResources().getDisplayMetrics().widthPixels * 0.55f), ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }

    private void downloadStoreUpdate(String apkUrl) {
        if (!ensureInstallerPermission()) return;
        try {
            DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            if (dm == null) throw new Exception("Download Manager is unavailable");
            String fileName = "underground-app-store-update-" + System.currentTimeMillis() + ".apk";
            File target = new File(installRoot(), fileName);
            if (target.exists()) target.delete();
            DownloadManager.Request r = new DownloadManager.Request(Uri.parse(apkUrl));
            r.setTitle("The Underground App Store Update");
            r.setDescription("Downloading App Store update…");
            r.setMimeType("application/vnd.android.package-archive");
            r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            r.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "underground-installers/" + fileName);
            long id = dm.enqueue(r);
            downloadTargets.put(id, fileName);
            Toast.makeText(this, "Downloading App Store update…", Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Toast.makeText(this, "Update download failed to start: " + e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    private void refreshCatalogSilent() {
        new Thread(() -> {
            HttpURLConnection c = null;
            try {
                URL url = new URL(CatalogStore.REMOTE_URL + "?t=" + System.currentTimeMillis());
                c = (HttpURLConnection) url.openConnection();
                c.setConnectTimeout(6000);
                c.setReadTimeout(8000);
                c.setUseCaches(false);
                c.setRequestProperty("Cache-Control", "no-cache");
                c.setRequestProperty("User-Agent", "UndergroundAppStore/" + BuildConfig.VERSION_NAME);
                int code = c.getResponseCode();
                if (code < 200 || code >= 300) return;
                CatalogStore.saveRemote(this, CatalogStore.readFully(c.getInputStream()));
                runOnUiThread(() -> {
                    loadCatalog();
                    lastUpdatedText.setText(lastUpdatedLabel());
                    updateMessagesBadge();
                    if (screen == Screen.HOME) showScreen(Screen.HOME);
                });
            } catch (Exception ignored) {
            } finally { if (c != null) c.disconnect(); }
        }, "ug-catalog-auto-refresh").start();
    }

    private void refreshCatalog() {
        if (refreshing) return;
        refreshing = true;
        refreshButton.setText("↻  CHECKING…");
        refreshButton.setEnabled(false);
        final int before = apps.size();
        new Thread(() -> {
            HttpURLConnection c = null;
            try {
                URL url = new URL(CatalogStore.REMOTE_URL + "?t=" + System.currentTimeMillis());
                c = (HttpURLConnection) url.openConnection();
                c.setConnectTimeout(8000);
                c.setReadTimeout(10000);
                c.setUseCaches(false);
                c.setRequestProperty("Cache-Control", "no-cache");
                c.setRequestProperty("User-Agent", "UndergroundAppStore/" + BuildConfig.VERSION_NAME);
                int code = c.getResponseCode();
                if (code < 200 || code >= 300) throw new Exception("server returned " + code);
                String json = CatalogStore.readFully(c.getInputStream());
                CatalogStore.saveRemote(this, json);
                runOnUiThread(() -> {
                    loadCatalog();
                    finishRefresh(before, null);
                });
            } catch (Exception e) {
                String message = e.getMessage() == null ? "server unavailable" : e.getMessage();
                runOnUiThread(() -> finishRefresh(before, message));
            } finally {
                if (c != null) c.disconnect();
            }
        }, "ug-catalog-refresh").start();
    }

    private void finishRefresh(int before, String error) {
        refreshing = false;
        refreshButton.setEnabled(true);
        refreshButton.setText("↻  CHECK FOR NEW APPS");
        lastUpdatedText.setText(lastUpdatedLabel());
        if (error == null) {
            int added = Math.max(0, apps.size() - before);
            Toast.makeText(this, added > 0 ? (added + " new app" + (added == 1 ? "" : "s") + " added") : "Underground catalog is up to date", Toast.LENGTH_LONG).show();
            showScreen(screen);
        } else {
            Toast.makeText(this, "No new server catalog found — bundled/cached apps are unchanged. (" + error + ")", Toast.LENGTH_LONG).show();
        }
    }

    private String lastUpdatedLabel() {
        long when = CatalogStore.lastUpdated(this);
        if (when <= 0) return "Built-in catalog";
        return "Updated " + new SimpleDateFormat("MMM d • h:mm a", Locale.US).format(new Date(when));
    }

    private AppEntry featuredApp() {
        String id = catalog.optString("featuredId", "wave2");
        for (AppEntry a : apps) if (id.equals(a.id)) return a;
        for (AppEntry a : apps) if (a.featured) return a;
        return apps.isEmpty() ? null : apps.get(0);
    }

    private void setAppIcon(ImageView image, AppEntry app) {
        int id = app.drawableName == null || app.drawableName.isEmpty() ? 0 : getResources().getIdentifier(app.drawableName, "drawable", getPackageName());
        if (id == 0) id = getResources().getIdentifier("underground_logo", "drawable", getPackageName());
        image.setImageResource(id);
        if (app.iconUrl != null && app.iconUrl.startsWith("https://")) loadRemoteIcon(image, app.iconUrl);
    }

    private void loadRemoteIcon(ImageView image, String url) {
        Bitmap cached = remoteIcons.get(url);
        if (cached != null) { image.setImageBitmap(cached); return; }
        new Thread(() -> {
            HttpURLConnection c = null;
            try {
                c = (HttpURLConnection) new URL(url).openConnection();
                c.setConnectTimeout(7000); c.setReadTimeout(9000); c.setUseCaches(true);
                try (InputStream in = c.getInputStream()) {
                    Bitmap b = BitmapFactory.decodeStream(in);
                    if (b != null) {
                        remoteIcons.put(url, b);
                        runOnUiThread(() -> image.setImageBitmap(b));
                    }
                }
            } catch (Exception ignored) {
            } finally { if (c != null) c.disconnect(); }
        }, "ug-icon").start();
    }

    @Override
    public void onBackPressed() {
        if (screen != Screen.HOME) {
            showScreen(Screen.HOME);
            navHome.requestFocus();
        } else super.onBackPressed();
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
            showScreen(Screen.MESSAGES);
            return true;
        }
        return super.dispatchKeyEvent(event);
    }

    private JSONArray messagesArray() {
        JSONArray a = catalog.optJSONArray("messages");
        if (a == null) a = catalog.optJSONArray("news");
        return a == null ? new JSONArray() : a;
    }

    private void updateMessagesBadge() {
        if (navMessages == null) return;
        int count = messagesArray().length();
        navMessages.setText(count > 0 ? "Messages (" + count + ")" : "Messages");
    }

    private LinearLayout homeMessagesPanel() {
        LinearLayout panel = infoCard();
        panel.setBackground(glassPanel(PURPLE));
        panel.addView(label("MESSAGES FROM UNDERGROUND", 16, TEXT, true));
        JSONArray m = messagesArray();
        if (m.length() > 0) {
            JSONObject n = m.optJSONObject(0);
            if (n != null) {
                panel.addView(label(n.optString("title", "Latest Underground message"), 14, GREEN, true), full(dp(32), 0, dp(8), 0, 0));
                TextView b = label(n.optString("body", ""), 11, MUTED, false); b.setMaxLines(3);
                panel.addView(b, full(dp(58), 0, 0, 0, 0));
            }
        } else panel.addView(label("No new messages.", 12, MUTED, false), full(dp(44), 0, dp(8), 0, 0));
        panel.addView(label("Press OK to read all messages  ›", 11, TEXT, true), full(dp(30), 0, dp(6), 0, 0));
        panel.setOnClickListener(v -> showScreen(Screen.MESSAGES));
        return panel;
    }

    private LinearLayout homeInstructionsPanel() {
        LinearLayout panel = infoCard();
        panel.setBackground(glassPanel(BLUE));
        panel.addView(label("HOW TO INSTALL APPS", 16, TEXT, true));
        panel.addView(label("FIRST-TIME SETUP", 10, BLUE, true), full(dp(24), 0, dp(7), 0, 0));
        panel.addView(label("1. Open install permission\n2. Allow Underground App Store\n3. Return here and choose Install", 12, MUTED, false), full(dp(78), 0, 0, 0, 0));
        panel.addView(label("Press OK for the full guide  ›", 11, TEXT, true), full(dp(28), 0, dp(5), 0, 0));
        panel.setOnClickListener(v -> showScreen(Screen.INSTRUCTIONS));
        return panel;
    }

    private LinearLayout homeQuickLinksPanel() {
        LinearLayout panel = infoCard();
        panel.setBackground(glassPanel(GREEN));
        panel.addView(label("QUICK LINKS", 16, TEXT, true));
        panel.addView(label("Check for new apps\nApp Store update\nInstall permission\nAbout Underground", 12, MUTED, false), full(dp(96), 0, dp(8), 0, 0));
        panel.addView(label("Press OK for Settings  ›", 11, TEXT, true), full(dp(28), 0, dp(4), 0, 0));
        panel.setOnClickListener(v -> showScreen(Screen.SETTINGS));
        return panel;
    }

    private GradientDrawable glassPanel(int accent) {
        GradientDrawable g = new GradientDrawable(GradientDrawable.Orientation.TL_BR,
                new int[]{Color.argb(238, 17, 22, 31), Color.argb(228, 11, 15, 23)});
        g.setCornerRadius(dp(18));
        g.setStroke(dp(1), Color.argb(190, Color.red(accent), Color.green(accent), Color.blue(accent)));
        return g;
    }

    private LinearLayout vertical() {
        LinearLayout v = new LinearLayout(this);
        v.setOrientation(LinearLayout.VERTICAL);
        return v;
    }

    private TextView label(String text, int sp, int color, boolean bold) {
        TextView v = new TextView(this);
        v.setText(text);
        v.setTextSize(sp);
        v.setTextColor(color);
        v.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
        v.setIncludeFontPadding(false);
        if (bold) v.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
        return v;
    }

    private TextView sectionTitle(String text) {
        TextView v = label("▌ " + text, 20, TEXT, true);
        return v;
    }

    private TextView chip(String text) {
        TextView v = label(text, 11, TEXT, true);
        v.setGravity(Gravity.CENTER);
        v.setPadding(dp(12), 0, dp(12), 0);
        v.setBackground(roundRect(Color.rgb(24, 30, 40), BORDER, 1, 15));
        return v;
    }

    private TextView actionButton(String text, boolean primary) {
        TextView v = label(text, 13, primary ? Color.BLACK : TEXT, true);
        v.setGravity(Gravity.CENTER);
        v.setFocusable(true);
        v.setClickable(true);
        applyFocusButton(v, primary);
        return v;
    }

    private void applyFocusButton(TextView v, boolean primary) {
        v.setBackground(roundRect(primary ? GREEN : PANEL_2, primary ? GREEN : BORDER, primary ? 2 : 1, 11));
        v.setOnFocusChangeListener((view, focused) -> {
            TextView t = (TextView) view;
            t.animate().scaleX(focused ? 1.045f : 1f).scaleY(focused ? 1.045f : 1f).setDuration(120).start();
            if (focused) {
                t.setTextColor(primary ? Color.BLACK : TEXT);
                t.setBackground(roundRect(primary ? GREEN : PANEL_2, GREEN, 3, 11));
            } else {
                t.setTextColor(primary ? Color.BLACK : TEXT);
                t.setBackground(roundRect(primary ? GREEN : PANEL_2, primary ? GREEN : BORDER, primary ? 2 : 1, 11));
            }
        });
    }

    private void applyFocusPanel(View v, int normalColor, int focusBorder, int radius) {
        v.setBackground(roundRect(normalColor, BORDER, 1, radius));
        v.setOnFocusChangeListener((view, focused) -> {
            view.animate().scaleX(focused ? 1.035f : 1f).scaleY(focused ? 1.035f : 1f).setDuration(120).start();
            view.setBackground(roundRect(normalColor, focused ? focusBorder : BORDER, focused ? 3 : 1, radius));
        });
    }

    private GradientDrawable gradientPanel() {
        GradientDrawable g = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,
                new int[]{Color.rgb(13, 14, 25), Color.rgb(11, 15, 24), Color.rgb(9, 18, 18)});
        g.setCornerRadius(dp(18));
        g.setStroke(dp(1), Color.rgb(49, 55, 70));
        return g;
    }

    private GradientDrawable roundRect(int fill, int stroke, int strokeDp, int radiusDp) {
        GradientDrawable g = new GradientDrawable();
        g.setColor(fill);
        g.setCornerRadius(dp(radiusDp));
        if (strokeDp > 0) g.setStroke(dp(strokeDp), stroke);
        return g;
    }

    private LinearLayout.LayoutParams full(int height, int left, int top, int right, int bottom) {
        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
        p.setMargins(dp(left), dp(top), dp(right), dp(bottom));
        return p;
    }

    private int dp(int value) {
        return Math.round(value * getResources().getDisplayMetrics().density);
    }
}
