// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#pragma once

#include <algorithm>
#include <cstddef>
#include <map>
#include <string>
#include <set>
#include <vector>

namespace dragonx {
namespace config {

/**
 * @brief Application settings manager
 * 
 * Handles loading and saving of user preferences.
 */
class Settings {
public:
    Settings();
    ~Settings();

    /**
     * @brief Load settings from default location
     * @return true if loaded successfully
     */
    bool load();

    /**
     * @brief Load settings from specific path
     * @param path Path to settings file
     * @return true if loaded successfully
     */
    bool load(const std::string& path);

    /**
     * @brief Save settings to default location
     * @return true if saved successfully
     */
    bool save();

    /**
     * @brief Save settings to specific path
     * @param path Path to settings file
     * @return true if saved successfully
     */
    bool save(const std::string& path);

    /**
     * @brief Get default settings file path
     */
    static std::string getDefaultPath();

    enum class LiteServerSelectionPreferenceMode {
        Sticky,
        Random
    };

    // Pool selection mode for the mining tab: Manual (user picks the pool) or
    // AutoBalance (the wallet spreads miners across the official pools by hashrate).
    enum class PoolSelectMode {
        Manual,
        AutoBalance
    };

    struct LiteServerPreference {
        std::string url;
        std::string label;
        bool enabled = true;
    };

    // A user-defined portfolio entry: a custom label tied to a group of wallet addresses.
    // The Market tab's portfolio card sums these addresses' balances under the label.
    struct PortfolioEntry {
        std::string label;
        std::vector<std::string> addresses;
        std::string icon;           // project_icons wallet-icon name; empty = no icon
        unsigned int color = 0;     // packed IM_COL32 accent; 0 = theme default
        int outlineOpacity = 25;    // accent-outline opacity, percent (0-100)
        // Per-group price data. priceBasis: 0 = live market (USD), 1 = live market (BTC),
        // 2 = DRGX only (no fiat value), 3 = manual price. Defaults preserve prior behavior
        // (show DRGX + USD value).
        int priceBasis = 0;
        double manualPrice = 0.0;       // price per DRGX for the Manual basis
        std::string manualCurrency = "USD";
        bool showDrgx = true;           // show the DRGX amount on the card
        bool showValue = true;          // show the converted/fiat value on the card
        bool show24h = false;           // show the 24h % change (live-market bases only)
        bool showSparkline = false;     // show a price-trend sparkline (live-market bases only)
        int sparklineInterval = 4;      // 0=minute 1=hour 2=day 3=week 4=month (default month: a real curve
                                        // from the daily series, vs the young in-session minute buffer)
        // Per-wallet visibility: "" (shown in every wallet — legacy/global) or a wallet-identity
        // hash (shown only when that wallet is active). New entries are tagged with the current
        // wallet so a portfolio built for wallet A doesn't clutter wallet B.
        std::string scope;
        // Legacy dashboard-grid placement (deprecated by the row layout; kept for back-compat so an
        // older build's saved positions aren't dropped, but no longer used by the renderer).
        int gridCol = -1;
        int gridRow = -1;
        int gridW = 8;
        int gridH = 3;
    };

    // Theme
    std::string getTheme() const { return theme_; }
    void setTheme(const std::string& theme) { theme_ = theme; }

    // Unified skin
    std::string getSkinId() const { return skin_id_; }
    void setSkinId(const std::string& id) { skin_id_ = id; }

    // Stable z-address chosen for HushChat: the reply-to address in outgoing headers, and (for
    // legacy/non-mnemonic wallets) the source of the seed-derived chat identity. Persisted so the
    // identity + reply address don't shift when new addresses are generated.
    std::string getChatReplyZaddr() const { return chat_reply_zaddr_; }
    void setChatReplyZaddr(const std::string& z) { chat_reply_zaddr_ = z; }

    // Muted chat conversations (by cid) — muted conversations don't badge or raise a toast (Q10).
    bool isChatMuted(const std::string& cid) const {
        return std::find(muted_chat_cids_.begin(), muted_chat_cids_.end(), cid) != muted_chat_cids_.end();
    }
    void setChatMuted(const std::string& cid, bool muted) {
        const bool already = isChatMuted(cid);
        if (muted && !already) muted_chat_cids_.push_back(cid);
        else if (!muted && already)
            muted_chat_cids_.erase(std::remove(muted_chat_cids_.begin(), muted_chat_cids_.end(), cid),
                                   muted_chat_cids_.end());
    }

    // Hidden chat conversations (by cid) — hidden ones are filtered out of the list; a new incoming
    // message un-hides them (you can't un-receive) so nothing is silently lost.
    bool isChatHidden(const std::string& cid) const {
        return std::find(hidden_chat_cids_.begin(), hidden_chat_cids_.end(), cid) != hidden_chat_cids_.end();
    }
    void setChatHidden(const std::string& cid, bool hidden) {
        const bool already = isChatHidden(cid);
        if (hidden && !already) hidden_chat_cids_.push_back(cid);
        else if (!hidden && already)
            hidden_chat_cids_.erase(std::remove(hidden_chat_cids_.begin(), hidden_chat_cids_.end(), cid),
                                    hidden_chat_cids_.end());
    }

    // Blocked chat conversations (by cid). Unlike hide (reversible, keeps messages), block DELETES the
    // local history AND suppresses every message for the cid — old and future — until you unblock, at
    // which point the conversation re-imports from the chain. The last-known peer name is kept so the
    // "Blocked" list can label the entry (its messages are gone from the local store).
    struct BlockedChatConv { std::string cid; std::string name; };
    bool isChatBlocked(const std::string& cid) const {
        for (const auto& b : blocked_chat_convs_) if (b.cid == cid) return true;
        return false;
    }
    void setChatBlocked(const std::string& cid, const std::string& name, bool blocked) {
        const bool already = isChatBlocked(cid);
        if (blocked && !already) blocked_chat_convs_.push_back({cid, name});
        else if (!blocked && already)
            blocked_chat_convs_.erase(
                std::remove_if(blocked_chat_convs_.begin(), blocked_chat_convs_.end(),
                               [&](const BlockedChatConv& b) { return b.cid == cid; }),
                blocked_chat_convs_.end());
    }
    const std::vector<BlockedChatConv>& blockedChatConversations() const { return blocked_chat_convs_; }

    // ── Chat-tab customization (chat settings modal + Settings → Chat & Contacts) ──────
    bool  getChatEmojiColor() const { return chat_emoji_color_; }
    void  setChatEmojiColor(bool v) { chat_emoji_color_ = v; }
    float getChatPollRateSec() const { return chat_poll_rate_sec_; }
    void  setChatPollRateSec(float v) { chat_poll_rate_sec_ = std::max(0.5f, std::min(15.0f, v)); }
    int   getChatBubbleStyle() const { return chat_bubble_style_; }
    void  setChatBubbleStyle(int v) { chat_bubble_style_ = (v < 0 || v > 2) ? 0 : v; }
    int   getChatBubbleAccent() const { return chat_bubble_accent_; }
    void  setChatBubbleAccent(int v) { chat_bubble_accent_ = (v < 0 || v > 5) ? 0 : v; }
    int   getChatDensity() const { return chat_density_; }
    void  setChatDensity(int v) { chat_density_ = (v < 0 || v > 1) ? 0 : v; }
    float getChatFontScale() const { return chat_font_scale_; }
    void  setChatFontScale(float v) { chat_font_scale_ = std::max(0.8f, std::min(1.5f, v)); }
    int   getChatTimeFormat() const { return chat_time_format_; }   // 0=follow global, 1=24h, 2=12h
    void  setChatTimeFormat(int v) { chat_time_format_ = (v < 0 || v > 2) ? 0 : v; }
    bool  getChatEnterSends() const { return chat_enter_sends_; }
    void  setChatEnterSends(bool v) { chat_enter_sends_ = v; }
    // Global clock format (0=24h, 1=12h) — chat can override it for the Chat tab only.
    int   getTimeFormat() const { return time_format_; }
    void  setTimeFormat(int v) { time_format_ = (v < 0 || v > 1) ? 0 : v; }

    // Privacy
    bool getSaveZtxs() const { return save_ztxs_; }
    void setSaveZtxs(bool save) { save_ztxs_ = save; }

    bool getAutoShield() const { return auto_shield_; }
    void setAutoShield(bool shield) { auto_shield_ = shield; }

    bool getUseTor() const { return use_tor_; }
    void setUseTor(bool tor) { use_tor_ = tor; }

    // Fees
    bool getAllowCustomFees() const { return allow_custom_fees_; }
    void setAllowCustomFees(bool allow) { allow_custom_fees_ = allow; }

    double getDefaultFee() const { return default_fee_; }
    void setDefaultFee(double fee) { default_fee_ = fee; }

    // Price
    bool getFetchPrices() const { return fetch_prices_; }
    void setFetchPrices(bool fetch) { fetch_prices_ = fetch; }

    // Explorer URLs
    std::string getTxExplorerUrl() const { return tx_explorer_url_; }
    void setTxExplorerUrl(const std::string& url) { tx_explorer_url_ = url; }

    std::string getAddressExplorerUrl() const { return address_explorer_url_; }
    void setAddressExplorerUrl(const std::string& url) { address_explorer_url_ = url; }

    // Language
    std::string getLanguage() const { return language_; }
    void setLanguage(const std::string& lang) { language_ = lang; }

    // Visual effects
    bool getAcrylicEnabled() const { return acrylic_enabled_; }
    void setAcrylicEnabled(bool v) { acrylic_enabled_ = v; }

    int getAcrylicQuality() const { return acrylic_quality_; }
    void setAcrylicQuality(int v) { acrylic_quality_ = v; }

    float getBlurMultiplier() const { return blur_multiplier_; }
    void setBlurMultiplier(float v) { blur_multiplier_ = v; }

    bool getReducedTransparency() const { return ui_opacity_ >= 0.99f; }
    void setReducedTransparency(bool v) { if (v) ui_opacity_ = 1.0f; }

    float getUIOpacity() const { return ui_opacity_; }
    void setUIOpacity(float v) { ui_opacity_ = std::max(0.3f, std::min(1.0f, v)); }

    float getWindowOpacity() const { return window_opacity_; }
    void setWindowOpacity(float v) { window_opacity_ = std::max(0.3f, std::min(1.0f, v)); }

    float getNoiseOpacity() const { return noise_opacity_; }
    void setNoiseOpacity(float v) { noise_opacity_ = v; }

    // Gradient background mode (use gradient variant of theme background)
    bool getGradientBackground() const { return gradient_background_; }
    void setGradientBackground(bool v) { gradient_background_ = v; }

    // Balance layout (string ID, e.g. "classic", "donut")
    std::string getBalanceLayout() const { return balance_layout_; }
    void setBalanceLayout(const std::string& v) { balance_layout_ = v; }

    // Market-tab portfolio row style: 0 = Table (borderless grid), 1 = Cards (glass card + Z/T bar),
    // 2 = Spotlight (hero value). Cycled with Left/Right arrows or set in the Market settings modal.
    int getPortfolioStyle() const { return portfolio_style_; }
    void setPortfolioStyle(int v) { portfolio_style_ = (v < 0 || v > 2) ? 0 : v; }
    // Contacts tab address-list view: 0 = cards, 1 = list, 2 = table.
    int getContactsViewMode() const { return contacts_view_mode_; }
    void setContactsViewMode(int v) { contacts_view_mode_ = (v < 0 || v > 2) ? 0 : v; }
    // Contacts customization (gear modal): avatar shape + card/list row scale.
    // Avatar shape: 0 = circle, 1 = rounded square, 2 = full-row-height left tab (rounded-left, flat right).
    int   getContactsAvatarShape() const { return contacts_avatar_shape_; }
    void  setContactsAvatarShape(int v) { contacts_avatar_shape_ = (v < 0 || v > 2) ? 0 : v; }
    float getContactsListScale() const { return contacts_list_scale_; }
    void  setContactsListScale(float v) { contacts_list_scale_ = std::max(0.8f, std::min(1.5f, v)); }
    // Play animated contact avatars (GIF/WebP). Off = show the first frame only.
    bool getAnimateAvatars() const { return animate_avatars_; }
    void setAnimateAvatars(bool v) { animate_avatars_ = v; }

    // Console scanline effect
    bool getScanlineEnabled() const { return scanline_enabled_; }
    void setScanlineEnabled(bool v) { scanline_enabled_ = v; }

    // Console output appearance: per-line left color accent bars, and per-channel text coloring.
    // (Defaults match the ConsoleTab statics so an upgrade re-save doesn't flip visible behavior.)
    bool getConsoleLineAccents() const { return console_line_accents_; }
    void setConsoleLineAccents(bool v) { console_line_accents_ = v; }
    bool getConsoleTextColor() const { return console_text_color_; }
    void setConsoleTextColor(bool v) { console_text_color_ = v; }
    float getConsoleZoom() const { return console_zoom_; }
    void setConsoleZoom(float v) { console_zoom_ = v; }
    // Auto-place the text cursor in the command box when the Console tab is opened.
    bool getConsoleAutoFocus() const { return console_auto_focus_; }
    void setConsoleAutoFocus(bool v) { console_auto_focus_ = v; }

    // Hidden addresses (addresses hidden from the UI by the user)
    const std::set<std::string>& getHiddenAddresses() const { return hidden_addresses_; }
    bool isAddressHidden(const std::string& addr) const { return hidden_addresses_.count(addr) > 0; }
    void hideAddress(const std::string& addr) { hidden_addresses_.insert(addr); }
    void unhideAddress(const std::string& addr) { hidden_addresses_.erase(addr); }
    int getHiddenAddressCount() const { return (int)hidden_addresses_.size(); }

    // Favorite addresses (pinned to top of address list)
    const std::set<std::string>& getFavoriteAddresses() const { return favorite_addresses_; }
    bool isAddressFavorite(const std::string& addr) const { return favorite_addresses_.count(addr) > 0; }
    void favoriteAddress(const std::string& addr) { favorite_addresses_.insert(addr); }
    void unfavoriteAddress(const std::string& addr) { favorite_addresses_.erase(addr); }
    int getFavoriteAddressCount() const { return (int)favorite_addresses_.size(); }

    // Address metadata (labels, icons, custom ordering)
    struct AddressMeta {
        std::string label;
        std::string icon;       // material icon name, e.g. "savings"
        int         sortOrder = -1;  // -1 = auto (use default sort)
        bool        mining = false;
    };
    const AddressMeta& getAddressMeta(const std::string& addr) const {
        static const AddressMeta empty{};
        auto it = address_meta_.find(addr);
        return it != address_meta_.end() ? it->second : empty;
    }
    void setAddressLabel(const std::string& addr, const std::string& label) {
        address_meta_[addr].label = label;
    }
    void setAddressIcon(const std::string& addr, const std::string& icon) {
        address_meta_[addr].icon = icon;
    }
    void setAddressSortOrder(const std::string& addr, int order) {
        address_meta_[addr].sortOrder = order;
    }
    bool isMiningAddress(const std::string& addr) const {
        auto it = address_meta_.find(addr);
        return it != address_meta_.end() && it->second.mining;
    }
    void setMiningAddress(const std::string& addr, bool mining) {
        address_meta_[addr].mining = mining;
    }
    std::set<std::string> getMiningAddresses() const {
        std::set<std::string> addresses;
        for (const auto& [addr, meta] : address_meta_) {
            if (meta.mining) addresses.insert(addr);
        }
        return addresses;
    }
    int getNextSortOrder() const {
        int mx = -1;
        for (const auto& [k, v] : address_meta_)
            if (v.sortOrder > mx) mx = v.sortOrder;
        return mx + 1;
    }
    void swapAddressOrder(const std::string& a, const std::string& b) {
        int oa = address_meta_[a].sortOrder;
        int ob = address_meta_[b].sortOrder;
        address_meta_[a].sortOrder = ob;
        address_meta_[b].sortOrder = oa;
    }

    // First-run wizard
    bool getWizardCompleted() const { return wizard_completed_; }
    void setWizardCompleted(bool v) { wizard_completed_ = v; }

    // Whether the one-time "back up your seed phrase" reminder has already been shown.
    bool getSeedBackupReminded() const { return seed_backup_reminded_; }
    void setSeedBackupReminded(bool v) { seed_backup_reminded_ = v; }

    // One-time nudge when wallet.dat grows past the bloat threshold (re-armed if it shrinks back).
    bool getLargeWalletWarned() const { return large_wallet_warned_; }
    void setLargeWalletWarned(bool v) { large_wallet_warned_ = v; }

    // Wallet filenames for which the one-time "this wallet is empty but a sibling holds funds"
    // warning has been dismissed. Keyed per active wallet file so switching to a different empty
    // wallet can warn again (see App::maybeWarnEmptyWalletWithFundedSiblings).
    bool isEmptyWalletWarnAcked(const std::string& walletFile) const {
        return empty_wallet_warning_acked_.count(walletFile) > 0;
    }
    void ackEmptyWalletWarn(const std::string& walletFile) { empty_wallet_warning_acked_.insert(walletFile); }

    // Persisted the moment deferred (wizard) encryption is requested; cleared only once the wallet is
    // observed to be actually encrypted. Lets a quit/crash/failed-connect before it applies be detected
    // and surfaced (W2-2). NEVER stores the passphrase — only the fact that encryption was requested.
    bool getEncryptionPending() const { return encryption_pending_; }
    void setEncryptionPending(bool v) { encryption_pending_ = v; }

    // Bundled-daemon size we last prompted to install (see App::renderDaemonUpdatePrompt). Lets the
    // "a newer node is bundled — update?" prompt fire once per wallet version, never re-nagging.
    long long getDaemonUpdatePromptedSize() const { return daemon_update_prompted_size_; }
    void setDaemonUpdatePromptedSize(long long v) { daemon_update_prompted_size_ = v; }

    // Active wallet file the daemon loads via -wallet=<name> (multi-wallet). A plain filename in
    // the datadir; defaults to the daemon's own default. Used at launch to know which wallet we're
    // on before connect + to scope per-wallet data.
    std::string getActiveWalletFile() const { return active_wallet_file_; }
    void setActiveWalletFile(const std::string& v) { active_wallet_file_ = v; }

    // Pending "migrate to a seed wallet" state (Phase 1 created the wallet; a later sweep/adopt
    // step consumes it). dest = the new wallet's sweep-target z-address; tempDir = its datadir.
    bool getSeedMigrationPending() const { return seed_migration_pending_; }
    void setSeedMigrationPending(bool v) { seed_migration_pending_ = v; }
    std::string getSeedMigrationDest() const { return seed_migration_dest_; }
    void setSeedMigrationDest(const std::string& v) { seed_migration_dest_ = v; }
    std::string getSeedMigrationTempDir() const { return seed_migration_temp_dir_; }
    void setSeedMigrationTempDir(const std::string& v) { seed_migration_temp_dir_ = v; }
    // The sweep transaction id, persisted once the sweep is submitted — non-empty means the
    // migration is past the sweep, so a resume goes to the confirm/adopt stage (not sweep again).
    std::string getSeedMigrationSweepTxid() const { return seed_migration_sweep_txid_; }
    void setSeedMigrationSweepTxid(const std::string& v) { seed_migration_sweep_txid_ = v; }
    // W3-3: the async sweep operation id, persisted while the sweep is in flight (before it resolves
    // to a txid). Lets a resume re-poll a mid-sweep interruption instead of dropping the txid. Cleared
    // in the same write that persists the txid, so the txid always outranks it (see [[decideSeedMigrationResume]]).
    std::string getSeedMigrationSweepOpid() const { return seed_migration_sweep_opid_; }
    void setSeedMigrationSweepOpid(const std::string& v) { seed_migration_sweep_opid_ = v; }
    // Set TRUE synchronously when a sweep is broadcast; survives the stale-opid resume fallback so the
    // Sweep step knows a ~0 legacy balance may be an UNCONFIRMED in-flight sweep (funds not yet safe)
    // and must NOT short-circuit to adopt/swap wallet.dat. Cleared on confirmed-adopt / reset / a
    // failed submit. See [[decideSeedMigrationResume]] and App::renderSeedMigrationDialog (fund-safety).
    bool getSeedMigrationSweepSubmitted() const { return seed_migration_sweep_submitted_; }
    void setSeedMigrationSweepSubmitted(bool v) { seed_migration_sweep_submitted_ = v; }

    // Security — auto-lock timeout (seconds; 0 = disabled)
    int getAutoLockTimeout() const { return auto_lock_timeout_; }
    void setAutoLockTimeout(int seconds) { auto_lock_timeout_ = seconds; }

    // Security — unlock duration (seconds) for walletpassphrase
    int getUnlockDuration() const { return unlock_duration_; }
    void setUnlockDuration(int seconds) { unlock_duration_ = seconds; }

    // Security — PIN unlock enabled
    bool getPinEnabled() const { return pin_enabled_; }
    void setPinEnabled(bool v) { pin_enabled_ = v; }

    // Daemon — keep running in background when closing the app
    bool getKeepDaemonRunning() const { return keep_daemon_running_; }
    void setKeepDaemonRunning(bool v) { keep_daemon_running_ = v; }

    // Daemon — stop externally-started daemons on exit (default: false)
    bool getStopExternalDaemon() const { return stop_external_daemon_; }
    void setStopExternalDaemon(bool v) { stop_external_daemon_ = v; }

    // Daemon — maximum peer connections (0 = daemon default)
    int getMaxConnections() const { return max_connections_; }
    void setMaxConnections(int v) { max_connections_ = std::max(0, v); }
    // Host a RandomX stratum pool from the node (v1.3.0+ daemons). Empty allow-IP = loopback only (safe).
    bool getStratumHost() const { return stratum_host_enabled_; }
    void setStratumHost(bool v) { stratum_host_enabled_ = v; }
    const std::string& getStratumAllowIp() const { return stratum_allowip_; }
    void setStratumAllowIp(const std::string& v) { stratum_allowip_ = v; }

    // Lite wallet server selection
    LiteServerSelectionPreferenceMode getLiteServerSelectionMode() const { return lite_server_selection_mode_; }
    void setLiteServerSelectionMode(LiteServerSelectionPreferenceMode mode) { lite_server_selection_mode_ = mode; }
    std::string getLiteStickyServerUrl() const { return lite_sticky_server_url_; }
    void setLiteStickyServerUrl(const std::string& url) { lite_sticky_server_url_ = url; }
    std::string getLiteChainName() const { return lite_chain_name_; }
    void setLiteChainName(const std::string& chainName) { lite_chain_name_ = chainName; }
    std::size_t getLiteRandomSelectionSeed() const { return lite_random_selection_seed_; }
    void setLiteRandomSelectionSeed(std::size_t seed) { lite_random_selection_seed_ = seed; }
    bool getLitePersistSelectedServer() const { return lite_persist_selected_server_; }
    void setLitePersistSelectedServer(bool persist) { lite_persist_selected_server_ = persist; }
    const std::vector<LiteServerPreference>& getLiteServers() const { return lite_servers_; }
    void setLiteServers(const std::vector<LiteServerPreference>& servers) { lite_servers_ = servers; }
    // Servers shipped as defaults. Also merged into an existing install's saved list on load
    // (see Settings::load), so upgrades surface newly-added servers (e.g. lite6/lite7).
    static std::vector<LiteServerPreference> defaultLiteServers();

    // User-defined portfolio entries (Market tab). "All funds" is implicit, not stored here.
    const std::vector<PortfolioEntry>& getPortfolioEntries() const { return portfolio_entries_; }
    void setPortfolioEntries(const std::vector<PortfolioEntry>& entries) { portfolio_entries_ = entries; }

    // Lite servers the user has hidden from the Network tab (kept by URL, shown via a toggle).
    const std::set<std::string>& getLiteHiddenServers() const { return lite_hidden_servers_; }
    bool isLiteServerHidden(const std::string& url) const { return lite_hidden_servers_.count(url) > 0; }
    void hideLiteServer(const std::string& url) { lite_hidden_servers_.insert(url); }
    void unhideLiteServer(const std::string& url) { lite_hidden_servers_.erase(url); }

    // Lite wallet rollout / kill-switch (see wallet/lite_rollout_policy.h).
    // Override: "auto" (honor rollout manifest), "force_on", or "force_off".
    std::string getLiteRolloutOverride() const { return lite_rollout_override_; }
    void setLiteRolloutOverride(const std::string& v) { lite_rollout_override_ = v; }
    // Stable, locally-generated install id used only to derive the staged-rollout bucket.
    // Never transmitted; carries no PII. Generated on first use if empty.
    std::string getLiteInstallId() const { return lite_install_id_; }
    void setLiteInstallId(const std::string& v) { lite_install_id_ = v; }

    // Verbose diagnostic logging (connection attempts, daemon state, port owner, etc.)
    bool getVerboseLogging() const { return verbose_logging_; }
    void setVerboseLogging(bool v) { verbose_logging_ = v; }

    // Daemon — debug logging categories
    const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
    void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
    bool hasDebugCategory(const std::string& cat) const { return debug_categories_.count(cat) > 0; }
    void toggleDebugCategory(const std::string& cat) {
        if (debug_categories_.count(cat)) debug_categories_.erase(cat);
        else debug_categories_.insert(cat);
    }

    // Visual effects — animated theme effects (hue cycling, shimmer, glow, etc.)
    bool getThemeEffectsEnabled() const { return theme_effects_enabled_; }
    void setThemeEffectsEnabled(bool v) { theme_effects_enabled_ = v; }

    // Low-spec mode — disables heavy visual effects for better performance
    bool getLowSpecMode() const { return low_spec_mode_; }
    void setLowSpecMode(bool v) { low_spec_mode_ = v; }

    // Reduce motion — disables animated transitions for accessibility
    bool getReduceMotion() const { return reduce_motion_; }
    void setReduceMotion(bool v) { reduce_motion_ = v; }

    // Market — last selected exchange + pair
    std::string getSelectedExchange() const { return selected_exchange_; }
    void setSelectedExchange(const std::string& v) { selected_exchange_ = v; }
    std::string getSelectedPair() const { return selected_pair_; }
    void setSelectedPair(const std::string& v) { selected_pair_ = v; }
    int  getChartInterval() const { return chart_interval_; }   // Market chart range 0=Live..4=1M
    void setChartInterval(int v) { chart_interval_ = v; }
    int  getChartStyle() const { return chart_style_; }         // 0 = line, 1 = candlestick
    void setChartStyle(int v) { chart_style_ = v; }

    // Pool mining
    std::string getPoolUrl() const { return pool_url_; }
    void setPoolUrl(const std::string& v) { pool_url_ = v; }
    std::string getPoolAlgo() const { return pool_algo_; }
    void setPoolAlgo(const std::string& v) { pool_algo_ = v; }
    std::string getPoolWorker() const { return pool_worker_; }
    void setPoolWorker(const std::string& v) { pool_worker_ = v; }
    int getPoolThreads() const { return pool_threads_; }
    void setPoolThreads(int v) { pool_threads_ = v; }
    bool getPoolTls() const { return pool_tls_; }
    void setPoolTls(bool v) { pool_tls_ = v; }
    bool getPoolHugepages() const { return pool_hugepages_; }
    void setPoolHugepages(bool v) { pool_hugepages_ = v; }
    bool getPoolMode() const { return pool_mode_; }
    void setPoolMode(bool v) { pool_mode_ = v; }
    PoolSelectMode getPoolSelectMode() const { return pool_select_mode_; }
    void setPoolSelectMode(PoolSelectMode v) { pool_select_mode_ = v; }

    // Installed DRG-XMRig release tag (for in-app miner update detection); empty if unknown/bundled.
    std::string getXmrigVersion() const { return xmrig_version_; }
    void setXmrigVersion(const std::string& v) { xmrig_version_ = v; }

    // Mine when idle (auto-start mining when system is idle)
    bool getMineWhenIdle() const { return mine_when_idle_; }
    void setMineWhenIdle(bool v) { mine_when_idle_ = v; }
    int getMineIdleDelay() const { return mine_idle_delay_; }
    void setMineIdleDelay(int seconds) { mine_idle_delay_ = std::max(30, seconds); }

    // Idle thread scaling — scale thread count instead of start/stop
    bool getIdleThreadScaling() const { return idle_thread_scaling_; }
    void setIdleThreadScaling(bool v) { idle_thread_scaling_ = v; }
    int getIdleThreadsActive() const { return idle_threads_active_; }
    void setIdleThreadsActive(int v) { idle_threads_active_ = std::max(0, v); }
    int getIdleThreadsIdle() const { return idle_threads_idle_; }
    void setIdleThreadsIdle(int v) { idle_threads_idle_ = std::max(0, v); }
    bool getIdleGpuAware() const { return idle_gpu_aware_; }
    void setIdleGpuAware(bool v) { idle_gpu_aware_ = v; }

    // Saved pool URLs (user-managed favorites dropdown)
    const std::vector<std::string>& getSavedPoolUrls() const { return saved_pool_urls_; }
    void addSavedPoolUrl(const std::string& url) {
        // Don't add duplicates
        for (const auto& u : saved_pool_urls_) if (u == url) return;
        saved_pool_urls_.push_back(url);
    }
    void removeSavedPoolUrl(const std::string& url) {
        saved_pool_urls_.erase(
            std::remove(saved_pool_urls_.begin(), saved_pool_urls_.end(), url),
            saved_pool_urls_.end());
    }

    // Saved pool worker addresses (user-managed favorites dropdown)
    const std::vector<std::string>& getSavedPoolWorkers() const { return saved_pool_workers_; }
    void addSavedPoolWorker(const std::string& addr) {
        for (const auto& a : saved_pool_workers_) if (a == addr) return;
        saved_pool_workers_.push_back(addr);
    }
    void removeSavedPoolWorker(const std::string& addr) {
        saved_pool_workers_.erase(
            std::remove(saved_pool_workers_.begin(), saved_pool_workers_.end(), addr),
            saved_pool_workers_.end());
    }

    // Font scale (user accessibility setting, 1.0–1.5)
    float getFontScale() const { return font_scale_; }
    void setFontScale(float v) { font_scale_ = std::max(1.0f, std::min(1.5f, v)); }

    // Window size persistence (logical pixels at 1x scale)
    int getWindowWidth()  const { return window_width_; }
    int getWindowHeight() const { return window_height_; }
    void setWindowSize(int w, int h) { window_width_ = w; window_height_ = h; }

    // Returns true once after an upgrade (version mismatch detected on load)
    bool needsUpgradeSave() const { return needs_upgrade_save_; }
    void clearUpgradeSave() { needs_upgrade_save_ = false; }

private:
    std::string settings_path_;

    // Settings values
    std::string theme_ = "dragonx";
    std::string skin_id_ = "dragonx";
    std::string chat_reply_zaddr_;
    std::vector<std::string> muted_chat_cids_;   // muted chat conversations by cid (Q10)
    std::vector<std::string> hidden_chat_cids_;  // hidden chat conversations by cid
    std::vector<BlockedChatConv> blocked_chat_convs_;  // blocked chat conversations (cid + last-known name)
    // Chat-tab customization (chat settings modal + Settings → Chat & Contacts).
    bool  chat_emoji_color_ = true;    // true = color (needs FreeType; falls back to mono if absent), false = monochrome
    float chat_poll_rate_sec_ = 2.5f;  // 0-conf chat fast-scan cadence (full node)
    int   chat_bubble_style_ = 0;      // 0 = rounded, 1 = square, 2 = minimal
    int   chat_bubble_accent_ = 0;     // outgoing-bubble accent preset (0 = theme primary)
    int   chat_density_ = 0;           // 0 = comfortable, 1 = compact
    float chat_font_scale_ = 1.0f;     // message text scale
    int   chat_time_format_ = 0;       // 0 = follow global, 1 = 24h, 2 = 12h (Chat tab only)
    bool  chat_enter_sends_ = true;    // Enter sends (vs. inserts newline; Ctrl+Enter sends)
    int   time_format_ = 0;            // global clock: 0 = 24h, 1 = 12h
    bool save_ztxs_ = true;
    bool auto_shield_ = true;
    bool use_tor_ = false;
    bool allow_custom_fees_ = false;
    double default_fee_ = 0.0001;
    bool fetch_prices_ = true;
    std::string tx_explorer_url_ = "https://explorer.dragonx.is/tx/";
    std::string address_explorer_url_ = "https://explorer.dragonx.is/address/";
    std::string language_ = "en";
    bool acrylic_enabled_ = true;
    int acrylic_quality_ = 2;
    float blur_multiplier_ = 0.10f;
    float noise_opacity_ = 0.5f;
    bool gradient_background_ = false;
#ifdef _WIN32
    float ui_opacity_ = 0.50f;          // Card/sidebar opacity (0.3–1.0, 1.0 = opaque)
    float window_opacity_ = 0.90f;      // Background alpha (0.3–1.0, <1 = desktop visible)
#else
    float ui_opacity_ = 1.0f;           // Mac/Linux: default fully opaque
    float window_opacity_ = 1.0f;       // Mac/Linux: default fully opaque
#endif
    std::string balance_layout_ = "classic";
    int portfolio_style_ = 0;   // Market portfolio row style (0 Table / 1 Cards / 2 Spotlight)
    int contacts_view_mode_ = 0;   // Contacts address-list view (0 cards / 1 list / 2 table)
    int contacts_avatar_shape_ = 0;         // 0 = circle, 1 = rounded square, 2 = full-height left tab
    float contacts_list_scale_ = 1.0f;      // card/list row scale (does not affect the table view)
    bool animate_avatars_ = true;  // play animated (GIF/WebP) contact avatars
    bool scanline_enabled_ = true;
    bool console_line_accents_ = true;   // left color accent bars in console output
    bool console_text_color_ = true;     // per-channel text coloring in console output
    float console_zoom_ = 1.0f;          // console output font zoom factor
    bool console_auto_focus_ = false;    // focus the command input when the Console tab is opened (opt-in)
    std::set<std::string> hidden_addresses_;
    std::set<std::string> favorite_addresses_;
    std::map<std::string, AddressMeta> address_meta_;
    bool wizard_completed_ = false;
    bool seed_backup_reminded_ = false;
    bool large_wallet_warned_ = false;
    std::set<std::string> empty_wallet_warning_acked_;  // wallet files whose empty-wallet warning was dismissed
    bool encryption_pending_ = false;
    long long daemon_update_prompted_size_ = 0;  // bundled daemon size last offered via the update prompt
    std::string active_wallet_file_ = "wallet.dat";  // -wallet=<name> the daemon loads (multi-wallet)
    bool seed_migration_pending_ = false;
    std::string seed_migration_dest_;
    std::string seed_migration_temp_dir_;
    std::string seed_migration_sweep_txid_;
    std::string seed_migration_sweep_opid_;
    bool seed_migration_sweep_submitted_ = false;
    int auto_lock_timeout_ = 900;   // 15 minutes
    int unlock_duration_ = 600;     // 10 minutes
    bool pin_enabled_ = false;
    bool keep_daemon_running_ = false;
    bool stop_external_daemon_ = false;
    int  max_connections_ = 0;  // 0 = daemon default
    bool stratum_host_enabled_ = false;   // host a RandomX stratum pool from the node (v1.3.0+ daemons)
    std::string stratum_allowip_;         // -stratumallowip filter (empty = daemon default: loopback only)

    // Lite wallet server preferences. These are user/server settings only;
    // wallet secrets, wallet files, and lifecycle state are never stored here.
    LiteServerSelectionPreferenceMode lite_server_selection_mode_ = LiteServerSelectionPreferenceMode::Sticky;
    std::string lite_sticky_server_url_ = "https://lite.dragonx.is";
    std::string lite_chain_name_ = "main";  // SDXL backend chain id; must be main/test/regtest
    std::size_t lite_random_selection_seed_ = 0;
    bool lite_persist_selected_server_ = true;
    std::string lite_rollout_override_ = "auto";  // auto|force_on|force_off
    std::string lite_install_id_;                 // random local-only id; rollout-bucket source
    std::vector<LiteServerPreference> lite_servers_ = defaultLiteServers();
    std::set<std::string> lite_hidden_servers_;   // server URLs hidden from the Network tab

    std::vector<PortfolioEntry> portfolio_entries_;   // Market tab custom portfolio groups

    bool verbose_logging_ = false;
    std::set<std::string> debug_categories_;
    bool theme_effects_enabled_ = true;
    bool low_spec_mode_ = false;
    bool reduce_motion_ = false;
    std::string selected_exchange_ = "Nonkyc.io";
    std::string selected_pair_ = "DRGX/USDT";
    int chart_interval_ = 4;   // Market chart range (0=Live 1=1H 2=1D 3=1W 4=1M)
    int chart_style_ = 1;      // Market chart style (0=line, 1=candlestick)

    // Pool mining
    std::string pool_url_       = "pool.dragonx.is:3433";
    std::string pool_algo_      = "rx/hush";
    std::string pool_worker_    = "";
    int         pool_threads_   = 0;
    bool        pool_tls_       = false;
    bool        pool_hugepages_ = true;
    bool        pool_mode_      = false;   // false=solo, true=pool
    PoolSelectMode pool_select_mode_ = PoolSelectMode::Manual; // manual vs auto-balance pool choice
    std::string xmrig_version_;            // installed DRG-XMRig release tag (update detection)
    bool        mine_when_idle_ = false;   // auto-start mining when system idle
    int         mine_idle_delay_= 120;     // seconds of idle before mining starts
    bool        idle_thread_scaling_ = false; // scale threads instead of start/stop
    int         idle_threads_active_ = 0;    // threads when user active (0 = auto)
    int         idle_threads_idle_   = 0;    // threads when idle (0 = auto = all)
    bool        idle_gpu_aware_  = true;     // treat GPU activity as non-idle
    std::vector<std::string> saved_pool_urls_; // user-saved pool URL favorites
    std::vector<std::string> saved_pool_workers_; // user-saved worker address favorites

    // Font scale (user accessibility, 1.0–3.0; 1.0 = default)
    float       font_scale_     = 1.0f;

    // Window size (logical pixels at 1x scale; 0 = use default 1200×775)
    int         window_width_   = 0;
    int         window_height_  = 0;

    // Wallet version that last wrote this settings file (for upgrade detection)
    std::string settings_version_;
    bool        needs_upgrade_save_ = false;  // true when version changed
};

} // namespace config
} // namespace dragonx
