package com.foxx.androidcast;

/*********************************************************************
 * DeveloperSettingsActivity.java
 * Created at: Sun 17 May 2026 22:49:49 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class DeveloperSettingsActivity
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (7 commits, 129 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	e8bf7419bf18028236c0e55eba2aefb055a7b9d5dd91b0fe32b9c4d185aaa1cb
**********************************************************************/
import android.content.Context;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.text.TextUtils;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.foxx.androidcast.commercial.CommercialFeatures;
import com.foxx.androidcast.commercial.InAppPurchaseCoordinator;
import com.foxx.androidcast.commercial.PlayStoreIntegration;
import com.foxx.androidcast.display.ExternalDisplayCapturePolicy;
import com.foxx.androidcast.display.WiredDisplayMonitor;
import com.foxx.androidcast.media.CodecPriorityCatalog;
import com.foxx.androidcast.ota.OtaUpdateChecker;
import com.foxx.androidcast.ota.OtaUpdateCoordinator;
import com.foxx.androidcast.receiver.av.ReceiverAudioPreset;
import com.foxx.androidcast.receiver.av.ReceiverAvRuntime;
import com.foxx.androidcast.receiver.av.ReceiverVideoPreset;
import com.foxx.androidcast.sender.SenderResolutionUiMode;
import com.foxx.androidcast.dev.DevAdbConnectInfo;
import com.foxx.androidcast.dev.DevAdbWifiKeeper;
import com.foxx.androidcast.dev.DevDiagnosticsController;
import com.foxx.androidcast.dev.DevNetworkSelfTestController;
import com.foxx.androidcast.network.diag.CastDiagPing;
import com.foxx.androidcast.remoteaccess.RemoteAccessCoordinator;
import com.foxx.androidcast.remoteaccess.RemoteAccessMode;
import com.foxx.androidcast.remoteaccess.RemoteAccessVpnRouting;
import com.foxx.androidcast.stats.ui.SessionStatsAnalyzerActivity;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.textfield.TextInputEditText;

/** Hidden developer settings (opened via app-info area easter egg). */
public class DeveloperSettingsActivity extends AppCompatActivity {
    private static final int REQUEST_VPN_PREPARE = 9107;

    private DevDiagnosticsController diagnosticsController;
    private DevNetworkSelfTestController networkSelfTestController;
    private android.view.View settingsPanel;
    private android.view.View diagnosticsPanel;
    private android.view.View networkSelfTestPanel;
    private Spinner videoPresetSpinner;
    private Spinner audioPresetSpinner;
    private Spinner remoteAccessSpinner;
    private Spinner remoteAccessVpnRouteSpinner;
    private Spinner remoteAccessVpnAppSpinner;
    private android.view.View remoteAccessVpnRouteLabel;
    private android.view.View remoteAccessVpnAppLabel;
    private RemoteAccessMode pendingRemoteAccessMode;
    private boolean suppressRemoteAccessSpinnerEvents;
    private SeekBar intensitySeek;
    private TextView intensityValue;

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(CastLocaleHelper.attach(newBase));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        CastThemeHelper.applyTheme(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_developer_settings);
        setTitle(R.string.developer_settings_title);

        settingsPanel = findViewById(R.id.dev_panel_settings);
        diagnosticsPanel = findViewById(R.id.dev_panel_diagnostics);
        networkSelfTestPanel = findViewById(R.id.dev_panel_network_selftest);
        diagnosticsController = new DevDiagnosticsController(this);
        networkSelfTestController = new DevNetworkSelfTestController(this);
        bindDeveloperTabs();

        CheckBox debugOverlay = findViewById(R.id.check_receiver_debug_overlay);
        CheckBox codecScores = findViewById(R.id.check_show_codec_priority_scores);
        CheckBox sessionStats = findViewById(R.id.check_grab_session_stats);
        CheckBox streamDump = findViewById(R.id.check_dev_stream_dump);
        CheckBox adaptiveDisplay = findViewById(R.id.check_receiver_adaptive_display);
        CheckBox backendNtpSync = findViewById(R.id.check_dev_backend_ntp_sync);

        refreshFromPreferences(debugOverlay, codecScores, sessionStats, streamDump, adaptiveDisplay, backendNtpSync);

        findViewById(R.id.btn_open_session_stats).setOnClickListener(v ->
                startActivity(new android.content.Intent(this, SessionStatsAnalyzerActivity.class)));

        findViewById(R.id.btn_reload_codecs_json).setOnClickListener(v -> {
            String summary = CodecPriorityCatalog.reload(this);
            Toast.makeText(this, summary, Toast.LENGTH_LONG).show();
        });

        TextView installedVersion = findViewById(R.id.text_ota_installed_version);
        TextInputEditText manifestUrl = findViewById(R.id.edit_ota_manifest_url);
        CheckBox otaOnLaunch = findViewById(R.id.check_ota_on_launch);
        Button checkOta = findViewById(R.id.btn_check_ota);

        refreshOtaUi(installedVersion, manifestUrl, otaOnLaunch);

        otaOnLaunch.setOnCheckedChangeListener((buttonView, isChecked) ->
                AppPreferences.setOtaCheckOnLaunch(this, isChecked));

        checkOta.setOnClickListener(v -> {
            String url = manifestUrl.getText() != null ? manifestUrl.getText().toString().trim() : "";
            AppPreferences.setOtaChannelUrl(this, url);
            OtaUpdateCoordinator.requestManualCheck(this);
        });

        bindAvPresets();
        bindRemoteAccessSection();
        bindDiagPingSection();
        bindSenderResolutionUiModeSection();
        bindWiredDisplaySection();
        bindAdbWifiSection();
        bindCommercialSection();
    }

    @Override
    protected void onDestroy() {
        if (diagnosticsController != null) {
            diagnosticsController.onDestroy();
        }
        if (networkSelfTestController != null) {
            networkSelfTestController.onDestroy();
        }
        super.onDestroy();
    }

    @Override
    protected void onPause() {
        saveAvPresets();
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        refreshFromPreferences(
                findViewById(R.id.check_receiver_debug_overlay),
                findViewById(R.id.check_show_codec_priority_scores),
                findViewById(R.id.check_grab_session_stats),
                findViewById(R.id.check_dev_stream_dump),
                findViewById(R.id.check_receiver_adaptive_display),
                findViewById(R.id.check_dev_backend_ntp_sync));
        bindDiagPingSection();
        refreshOtaUi(
                findViewById(R.id.text_ota_installed_version),
                findViewById(R.id.edit_ota_manifest_url),
                findViewById(R.id.check_ota_on_launch));
        bindAvPresets();
        bindRemoteAccessSection();
        bindSenderResolutionUiModeSection();
        bindWiredDisplaySection();
        bindAdbWifiSection();
        bindCommercialSection();
    }

    private void bindAdbWifiSection() {
        if (!BuildConfig.DEBUG) {
            android.view.View section = findViewById(R.id.check_dev_adb_wifi_keeper);
            if (section != null) {
                android.view.View parent = (android.view.View) section.getParent();
                // Hide adb wifi block in release UI if layout is shared.
                section.setVisibility(android.view.View.GONE);
            }
            return;
        }
        CheckBox keeper = findViewById(R.id.check_dev_adb_wifi_keeper);
        TextView status = findViewById(R.id.text_dev_adb_wifi_status);
        Button refresh = findViewById(R.id.btn_dev_adb_wifi_refresh);
        Button copy = findViewById(R.id.btn_dev_adb_wifi_copy);
        if (keeper == null) {
            return;
        }
        bindDeveloperCheckbox(keeper, AppPreferences.isDevAdbWifiKeeperEnabled(this), (ctx, on) -> {
            AppPreferences.setDevAdbWifiKeeperEnabled(ctx, on);
            DevAdbWifiKeeper.onPreferenceChanged(ctx, on);
            refreshAdbWifiStatus(status);
        });
        if (refresh != null) {
            refresh.setOnClickListener(v -> {
                DevAdbWifiKeeper.refreshNow(this, "dev-settings");
                status.postDelayed(() -> refreshAdbWifiStatus(status), 400L);
            });
        }
        if (copy != null) {
            copy.setOnClickListener(v -> {
                DevAdbWifiKeeper.copyPrimaryConnectLine(this);
                Toast.makeText(this, R.string.dev_adb_wifi_copied, Toast.LENGTH_SHORT).show();
            });
        }
        refreshAdbWifiStatus(status);
    }

    private void refreshAdbWifiStatus(TextView status) {
        if (status == null || !BuildConfig.DEBUG) {
            return;
        }
        DevAdbConnectInfo info = DevAdbWifiKeeper.getLastInfo(this);
        String line = info.primaryConnectLine();
        String headline = line.isEmpty()
                ? getString(R.string.dev_adb_wifi_status_waiting)
                : getString(R.string.dev_adb_wifi_status_line, line);
        String detail = getString(
                R.string.dev_adb_wifi_status_detail,
                info.secureSettingsGranted ? "yes" : "no",
                info.adbWifiEnabled ? "on" : "off",
                info.connect.isEmpty() ? "no endpoints yet" : TextUtils.join(", ", info.connect));
        status.setText(headline + "\n" + detail);
    }

    private void bindRemoteAccessSection() {
        remoteAccessSpinner = findViewById(R.id.spinner_dev_remote_access_mode);
        if (remoteAccessSpinner == null) {
            return;
        }
        RemoteAccessMode[] modes = RemoteAccessMode.values();
        String[] labels = new String[]{
                getString(R.string.dev_remote_access_disabled),
                getString(R.string.dev_remote_access_wireguard),
                getString(R.string.dev_remote_access_rssh),
        };
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item, labels);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        remoteAccessSpinner.setAdapter(adapter);
        RemoteAccessMode current = AppPreferences.getDevRemoteAccessMode(this);
        suppressRemoteAccessSpinnerEvents = true;
        remoteAccessSpinner.setOnItemSelectedListener(null);
        remoteAccessSpinner.setSelection(current.ordinal(), false);
        suppressRemoteAccessSpinnerEvents = false;
        remoteAccessSpinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(android.widget.AdapterView<?> parent, android.view.View view,
                    int position, long id) {
                if (suppressRemoteAccessSpinnerEvents) {
                    return;
                }
                RemoteAccessMode picked = modes[position];
                applyRemoteAccessMode(picked);
            }

            @Override
            public void onNothingSelected(android.widget.AdapterView<?> parent) {}
        });
        bindRemoteAccessVpnRoutingSection();
    }

    private void bindRemoteAccessVpnRoutingSection() {
        remoteAccessVpnRouteLabel = findViewById(R.id.label_dev_remote_access_vpn_route);
        remoteAccessVpnAppLabel = findViewById(R.id.label_dev_remote_access_vpn_app);
        remoteAccessVpnRouteSpinner = findViewById(R.id.spinner_dev_remote_access_vpn_route);
        remoteAccessVpnAppSpinner = findViewById(R.id.spinner_dev_remote_access_vpn_app_scope);
        if (remoteAccessVpnRouteSpinner == null || remoteAccessVpnAppSpinner == null) {
            return;
        }

        final RemoteAccessVpnRouting.RouteScope[] routeScopes = RemoteAccessVpnRouting.RouteScope.values();
        String[] routeLabels = {
                getString(R.string.dev_remote_access_vpn_route_hub_only),
                getString(R.string.dev_remote_access_vpn_route_full_tunnel),
        };
        ArrayAdapter<String> routeAdapter = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item, routeLabels);
        routeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        remoteAccessVpnRouteSpinner.setAdapter(routeAdapter);
        remoteAccessVpnRouteSpinner.setOnItemSelectedListener(null);
        remoteAccessVpnRouteSpinner.setSelection(
                AppPreferences.getDevRemoteAccessVpnRouteScope(this).ordinal(), false);
        remoteAccessVpnRouteSpinner.setOnItemSelectedListener(simpleSelection(() -> {
            int pos = remoteAccessVpnRouteSpinner.getSelectedItemPosition();
            if (pos < 0 || pos >= routeScopes.length) {
                return;
            }
            RemoteAccessVpnRouting.RouteScope picked = routeScopes[pos];
            if (picked == AppPreferences.getDevRemoteAccessVpnRouteScope(this)) {
                return;
            }
            AppPreferences.setDevRemoteAccessVpnRouteScope(this, picked);
            RemoteAccessCoordinator.onVpnRoutingPreferenceChanged(this);
            Toast.makeText(this, R.string.dev_remote_access_vpn_routing_applied, Toast.LENGTH_SHORT).show();
        }));

        final RemoteAccessVpnRouting.AppScope[] appScopes = RemoteAccessVpnRouting.AppScope.values();
        String[] appLabels = {
                getString(R.string.dev_remote_access_vpn_app_all),
                getString(R.string.dev_remote_access_vpn_app_this_only),
        };
        ArrayAdapter<String> appAdapter = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item, appLabels);
        appAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        remoteAccessVpnAppSpinner.setAdapter(appAdapter);
        remoteAccessVpnAppSpinner.setOnItemSelectedListener(null);
        remoteAccessVpnAppSpinner.setSelection(
                AppPreferences.getDevRemoteAccessVpnAppScope(this).ordinal(), false);
        remoteAccessVpnAppSpinner.setOnItemSelectedListener(simpleSelection(() -> {
            int pos = remoteAccessVpnAppSpinner.getSelectedItemPosition();
            if (pos < 0 || pos >= appScopes.length) {
                return;
            }
            RemoteAccessVpnRouting.AppScope picked = appScopes[pos];
            if (picked == AppPreferences.getDevRemoteAccessVpnAppScope(this)) {
                return;
            }
            AppPreferences.setDevRemoteAccessVpnAppScope(this, picked);
            RemoteAccessCoordinator.onVpnRoutingPreferenceChanged(this);
            Toast.makeText(this, R.string.dev_remote_access_vpn_routing_applied, Toast.LENGTH_SHORT).show();
        }));

        updateRemoteAccessVpnRoutingEnabled();
    }

    private void updateRemoteAccessVpnRoutingEnabled() {
        boolean wg = AppPreferences.getDevRemoteAccessMode(this) == RemoteAccessMode.WIREGUARD;
        if (remoteAccessVpnRouteSpinner != null) {
            remoteAccessVpnRouteSpinner.setEnabled(wg);
        }
        if (remoteAccessVpnAppSpinner != null) {
            remoteAccessVpnAppSpinner.setEnabled(wg);
        }
        if (remoteAccessVpnRouteLabel != null) {
            remoteAccessVpnRouteLabel.setEnabled(wg);
        }
        if (remoteAccessVpnAppLabel != null) {
            remoteAccessVpnAppLabel.setEnabled(wg);
        }
    }

    private void applyRemoteAccessMode(RemoteAccessMode mode) {
        if (mode == RemoteAccessMode.WIREGUARD) {
            android.content.Intent prep = android.net.VpnService.prepare(this);
            if (prep != null) {
                pendingRemoteAccessMode = mode;
                startActivityForResult(prep, REQUEST_VPN_PREPARE);
                return;
            }
        }
        AppPreferences.setDevRemoteAccessMode(this, mode);
        RemoteAccessCoordinator.applyMode(this, mode);
        updateRemoteAccessVpnRoutingEnabled();
        Toast.makeText(this, getString(R.string.dev_remote_access_applied, mode.name()), Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, android.content.Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_VPN_PREPARE && pendingRemoteAccessMode != null) {
            RemoteAccessMode mode = pendingRemoteAccessMode;
            pendingRemoteAccessMode = null;
            if (resultCode == RESULT_OK) {
                AppPreferences.setDevRemoteAccessMode(this, mode);
                RemoteAccessCoordinator.applyMode(this, mode);
                updateRemoteAccessVpnRoutingEnabled();
            } else {
                bindRemoteAccessSection();
                Toast.makeText(this, R.string.dev_remote_access_vpn_denied, Toast.LENGTH_LONG).show();
            }
        }
    }

    private void bindDiagPingSection() {
        CheckBox enabled = findViewById(R.id.check_dev_diag_ping_pong);
        bindDeveloperCheckbox(enabled, AppPreferences.isDevDiagPingPongEnabled(this),
                AppPreferences::setDevDiagPingPongEnabled);
        Spinner spinner = findViewById(R.id.spinner_dev_diag_ping_response_mode);
        if (spinner == null) {
            return;
        }
        final byte[] modes = {
                CastDiagPing.DIR_MIRROR_AS_IS,
                CastDiagPing.DIR_MIRROR_SMART,
                CastDiagPing.DIR_REPLY_WITH
        };
        String[] labels = {
                getString(R.string.dev_diag_ping_response_mirror),
                getString(R.string.dev_diag_ping_response_smart),
                getString(R.string.dev_diag_ping_response_reply)
        };
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item, labels);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(null);
        byte current = AppPreferences.getDevDiagPingResponseMode(this);
        int selection = 1;
        for (int i = 0; i < modes.length; i++) {
            if (modes[i] == current) {
                selection = i;
                break;
            }
        }
        spinner.setSelection(selection, false);
        spinner.setOnItemSelectedListener(simpleSelection(() -> {
            int pos = spinner.getSelectedItemPosition();
            if (pos >= 0 && pos < modes.length) {
                AppPreferences.setDevDiagPingResponseMode(this, modes[pos]);
            }
        }));
    }

    private void bindSenderResolutionUiModeSection() {
        Spinner spinner = findViewById(R.id.spinner_dev_sender_resolution_ui_mode);
        if (spinner == null) {
            return;
        }
        final SenderResolutionUiMode[] modes = SenderResolutionUiMode.values();
        String[] labels = {
                getString(R.string.dev_sender_resolution_ui_separated),
                getString(R.string.dev_sender_resolution_ui_legacy),
                getString(R.string.dev_sender_resolution_ui_combined),
        };
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item, labels);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(null);
        SenderResolutionUiMode current = AppPreferences.getDevSenderResolutionUiMode(this);
        spinner.setSelection(current.ordinal(), false);
        spinner.setOnItemSelectedListener(simpleSelection(() -> {
            int pos = spinner.getSelectedItemPosition();
            if (pos >= 0 && pos < modes.length) {
                AppPreferences.setDevSenderResolutionUiMode(this, modes[pos]);
            }
        }));
    }

    private void bindWiredDisplaySection() {
        TextView status = findViewById(R.id.text_wired_display_status);
        if (status != null) {
            status.setText(WiredDisplayMonitor.getInstance(this).getSummaryLine());
        }
        CheckBox hints = findViewById(R.id.check_wired_display_hints);
        bindDeveloperCheckbox(hints, AppPreferences.isDevWiredDisplayHintsEnabled(this),
                AppPreferences::setDevWiredDisplayHintsEnabled);

        Spinner policySpinner = findViewById(R.id.spinner_external_capture_policy);
        TextView policyNote = findViewById(R.id.text_external_capture_policy_note);
        if (policySpinner != null) {
            String[] labels = {
                    getString(R.string.dev_capture_policy_default),
                    getString(R.string.dev_capture_policy_prefer_external),
                    getString(R.string.dev_capture_policy_external_experimental)
            };
            ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                    android.R.layout.simple_spinner_item, labels);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            policySpinner.setAdapter(adapter);
            policySpinner.setSelection(AppPreferences.getExternalDisplayCapturePolicy(this).ordinal());
            policySpinner.setOnItemSelectedListener(simpleSelection(() -> {
                ExternalDisplayCapturePolicy p = ExternalDisplayCapturePolicy.values()[
                        policySpinner.getSelectedItemPosition()];
                AppPreferences.setExternalDisplayCapturePolicy(this, p);
                if (policyNote != null) {
                    policyNote.setText(p.implementationNote());
                }
            }));
            if (policyNote != null) {
                policyNote.setText(AppPreferences.getExternalDisplayCapturePolicy(this).implementationNote());
            }
        }

        CheckBox usbTether = findViewById(R.id.check_usb_tether_transport);
        bindDeveloperCheckbox(usbTether, AppPreferences.isDevUsbTetherTransportEnabled(this),
                AppPreferences::setDevUsbTetherTransportEnabled);
    }

    private void bindCommercialSection() {
        CheckBox ads = findViewById(R.id.check_commercial_ads);
        CheckBox iap = findViewById(R.id.check_commercial_iap);
        CheckBox play = findViewById(R.id.check_commercial_play_store);
        bindDeveloperCheckbox(ads, AppPreferences.isDevCommercialAdsEnabled(this), (ctx, on) -> {
            AppPreferences.setDevCommercialAdsEnabled(ctx, on);
            CommercialFeatures.refreshFromPreferences(ctx);
        });
        bindDeveloperCheckbox(iap, AppPreferences.isDevCommercialIapEnabled(this), (ctx, on) -> {
            AppPreferences.setDevCommercialIapEnabled(ctx, on);
            CommercialFeatures.refreshFromPreferences(ctx);
        });
        bindDeveloperCheckbox(play, AppPreferences.isDevCommercialPlayStoreEnabled(this), (ctx, on) -> {
            AppPreferences.setDevCommercialPlayStoreEnabled(ctx, on);
            CommercialFeatures.refreshFromPreferences(ctx);
        });

        Button listing = findViewById(R.id.btn_open_play_store_listing);
        if (listing != null) {
            listing.setOnClickListener(v -> {
                if (!CommercialFeatures.isPlayStoreIntegrationEnabled(this)) {
                    Toast.makeText(this, R.string.dev_commercial_enable_play_first, Toast.LENGTH_SHORT).show();
                    return;
                }
                PlayStoreIntegration.getInstance(this).openAppListing(this);
            });
        }
        Button review = findViewById(R.id.btn_request_play_review);
        if (review != null) {
            review.setOnClickListener(v -> {
                if (!CommercialFeatures.isPlayStoreIntegrationEnabled(this)) {
                    Toast.makeText(this, R.string.dev_commercial_enable_play_first, Toast.LENGTH_SHORT).show();
                    return;
                }
                PlayStoreIntegration.getInstance(this).requestInAppReviewIfAvailable(this);
            });
        }
        Button queryIap = findViewById(R.id.btn_query_iap_products);
        if (queryIap != null) {
            queryIap.setOnClickListener(v -> {
                if (!CommercialFeatures.isInAppPurchaseEnabled(this)) {
                    Toast.makeText(this, R.string.dev_commercial_enable_iap_first, Toast.LENGTH_SHORT).show();
                    return;
                }
                InAppPurchaseCoordinator.getInstance(this).queryPremiumProduct(() ->
                        Toast.makeText(this, R.string.dev_iap_query_done, Toast.LENGTH_SHORT).show());
            });
        }
    }

    private void bindAvPresets() {
        videoPresetSpinner = findViewById(R.id.spinner_dev_video_preset);
        audioPresetSpinner = findViewById(R.id.spinner_dev_audio_preset);
        intensitySeek = findViewById(R.id.seek_dev_av_intensity);
        intensityValue = findViewById(R.id.text_dev_av_intensity_value);

        if (videoPresetSpinner != null) {
            String[] labels = {
                    getString(R.string.dev_av_video_default),
                    getString(R.string.dev_av_video_immersive),
                    getString(R.string.dev_av_video_hdr)
            };
            ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                    android.R.layout.simple_spinner_item, labels);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            videoPresetSpinner.setAdapter(adapter);
            videoPresetSpinner.setSelection(AppPreferences.getReceiverVideoPreset(this).ordinal());
            videoPresetSpinner.setOnItemSelectedListener(simpleSelection(() -> updateIntensityEnabled()));
        }
        if (audioPresetSpinner != null) {
            String[] labels = {
                    getString(R.string.dev_av_audio_default),
                    getString(R.string.dev_av_audio_bass_boost),
                    getString(R.string.dev_av_audio_immersive),
                    getString(R.string.dev_av_audio_dolby_51),
                    getString(R.string.dev_av_audio_dolby_71)
            };
            ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                    android.R.layout.simple_spinner_item, labels);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            audioPresetSpinner.setAdapter(adapter);
            audioPresetSpinner.setSelection(AppPreferences.getReceiverAudioPreset(this).ordinal());
            audioPresetSpinner.setOnItemSelectedListener(simpleSelection(() -> updateIntensityEnabled()));
        }
        if (intensitySeek != null) {
            intensitySeek.setProgress(AppPreferences.getReceiverAvIntensity(this));
            intensitySeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    updateIntensityLabel(progress);
                }

                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {}

                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {}
            });
        }
        updateIntensityEnabled();
        updateIntensityLabel(intensitySeek != null ? intensitySeek.getProgress() : 0);
    }

    private void saveAvPresets() {
        if (videoPresetSpinner != null) {
            AppPreferences.setReceiverVideoPreset(this,
                    ReceiverVideoPreset.values()[videoPresetSpinner.getSelectedItemPosition()]);
        }
        if (audioPresetSpinner != null) {
            AppPreferences.setReceiverAudioPreset(this,
                    ReceiverAudioPreset.values()[audioPresetSpinner.getSelectedItemPosition()]);
        }
        if (intensitySeek != null) {
            AppPreferences.setReceiverAvIntensity(this, intensitySeek.getProgress());
        }
        ReceiverAvRuntime.reload(this);
    }

    private void updateIntensityEnabled() {
        boolean audioOn = audioPresetSpinner != null
                && ReceiverAudioPreset.values()[audioPresetSpinner.getSelectedItemPosition()]
                        .supportsIntensity();
        boolean videoOn = videoPresetSpinner != null
                && ReceiverVideoPreset.values()[videoPresetSpinner.getSelectedItemPosition()]
                        .supportsIntensity();
        boolean on = audioOn || videoOn;
        if (intensitySeek != null) {
            intensitySeek.setEnabled(on);
        }
        TextView label = findViewById(R.id.label_dev_av_intensity);
        if (label != null) {
            label.setEnabled(on);
        }
    }

    private void updateIntensityLabel(int progress) {
        if (intensityValue != null) {
            intensityValue.setText(getString(R.string.dev_av_intensity_value, progress));
        }
    }

    private android.widget.AdapterView.OnItemSelectedListener simpleSelection(Runnable after) {
        return new android.widget.AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(android.widget.AdapterView<?> parent, android.view.View view,
                    int position, long id) {
                after.run();
            }

            @Override
            public void onNothingSelected(android.widget.AdapterView<?> parent) {}
        };
    }

    private void refreshOtaUi(TextView installedVersion, TextInputEditText manifestUrl, CheckBox otaOnLaunch) {
        if (installedVersion != null) {
            installedVersion.setText(getString(R.string.ota_installed_version,
                    OtaUpdateChecker.installedVersionName(this),
                    OtaUpdateChecker.installedVersionCode(this)));
        }
        if (manifestUrl != null) {
            String url = AppPreferences.getOtaChannelUrl(this);
            if (url.isEmpty() && BuildConfig.OTA_CHANNEL_URL_DEFAULT != null) {
                url = BuildConfig.OTA_CHANNEL_URL_DEFAULT.trim();
            }
            if (url.isEmpty() && BuildConfig.OTA_MANIFEST_URL_DEFAULT != null) {
                url = BuildConfig.OTA_MANIFEST_URL_DEFAULT.trim();
            }
            manifestUrl.setText(url);
        }
        if (otaOnLaunch != null) {
            otaOnLaunch.setOnCheckedChangeListener(null);
            otaOnLaunch.setChecked(AppPreferences.isOtaCheckOnLaunch(this));
            otaOnLaunch.setOnCheckedChangeListener((buttonView, isChecked) ->
                    AppPreferences.setOtaCheckOnLaunch(this, isChecked));
        }
    }

    private void refreshFromPreferences(CheckBox debugOverlay, CheckBox codecScores, CheckBox sessionStats,
            CheckBox streamDump, CheckBox adaptiveDisplay, CheckBox backendNtpSync) {
        bindDeveloperCheckbox(debugOverlay, AppPreferences.isShowReceiverDebugOverlay(this),
                AppPreferences::setShowReceiverDebugOverlay);
        bindDeveloperCheckbox(codecScores, AppPreferences.isShowCodecPriorityScores(this),
                AppPreferences::setShowCodecPriorityScores);
        bindDeveloperCheckbox(sessionStats, AppPreferences.isGrabSessionStats(this),
                AppPreferences::setGrabSessionStats);
        bindDeveloperCheckbox(streamDump, AppPreferences.isDevStreamDumpEnabled(this),
                AppPreferences::setDevStreamDumpEnabled);
        bindDeveloperCheckbox(adaptiveDisplay, AppPreferences.isDevReceiverAdaptiveDisplay(this),
                AppPreferences::setDevReceiverAdaptiveDisplay);
        bindDeveloperCheckbox(backendNtpSync, AppPreferences.isDevBackendNtpSyncEnabled(this),
                AppPreferences::setDevBackendNtpSyncEnabled);
    }

    private interface BoolPrefWriter {
        void write(Context context, boolean value);
    }

    private void bindDeveloperTabs() {
        TabLayout tabs = findViewById(R.id.dev_settings_tabs);
        if (tabs == null) {
            return;
        }
        tabs.addTab(tabs.newTab().setText(R.string.dev_tab_settings));
        tabs.addTab(tabs.newTab().setText(R.string.dev_tab_diagnostics));
        tabs.addTab(tabs.newTab().setText(R.string.dev_tab_network_self_test));
        tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                int pos = tab.getPosition();
                boolean diagnostics = pos == 1;
                boolean selfTest = pos == 2;
                settingsPanel.setVisibility((diagnostics || selfTest) ? android.view.View.GONE : android.view.View.VISIBLE);
                diagnosticsPanel.setVisibility(diagnostics ? android.view.View.VISIBLE : android.view.View.GONE);
                networkSelfTestPanel.setVisibility(selfTest ? android.view.View.VISIBLE : android.view.View.GONE);
                if (diagnostics) {
                    diagnosticsController.onDiagnosticsTabShown();
                }
                if (selfTest) {
                    networkSelfTestController.onSelfTestTabShown();
                }
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {}

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                if (tab.getPosition() == 1) {
                    diagnosticsController.onDiagnosticsTabShown();
                } else if (tab.getPosition() == 2) {
                    networkSelfTestController.onSelfTestTabShown();
                }
            }
        });
    }

    private void bindDeveloperCheckbox(CheckBox box, boolean checked, BoolPrefWriter writer) {
        if (box == null) {
            return;
        }
        box.setOnCheckedChangeListener(null);
        box.setChecked(checked);
        box.setOnCheckedChangeListener((buttonView, isChecked) -> writer.write(this, isChecked));
    }
}
