#!/usr/bin/perl
# ==============================================================================
#  PressMint "Ignite" Installer (v1.0 - RC34)
#  (c) 2026 PressMint Open Source
#
#  FEATURES:
#  - Identity Shift (PHP runs as User)
#  - Strict Browser Caching (1 Year Policy)
#  - Full Stack Rebranding v2 (The "Phil Filter" + Config Surgery)
#  - SSL Manager Fix (Fixed "canonical" crash + added Smart Email)
#  - APT Pinning (Prevents PHP Vendor Switch)
#  - Observability (Dedicated Logging)
#  - Proxy Correctness (ProxyPreserveHost On)
# ==============================================================================

use strict;
use warnings;
use Getopt::Long;
use File::Path qw(make_path remove_tree);

# --- GLOBAL CONFIG ---
my $APP_DIR     = "/opt/pressmint";
my $WEB_ROOT    = "$APP_DIR/webroot";
my $STATE_FILE  = "$APP_DIR/.pressmint_state";
my $PMA_DIR     = "$APP_DIR/pma";
my $SYS_USER    = "pressmint";

# --- CLI ARGUMENTS ---
my $cli_create  = 0;
my $cli_domain  = "";
my $cli_php     = "";
my $cli_web     = "";

GetOptions(
    "create"      => \$cli_create,
    "domain=s"    => \$cli_domain,
    "php=s"       => \$cli_php,
    "webserver=s" => \$cli_web,
);

# --- BOOTSTRAP ---
check_root();
check_os_compatibility();
initialize_repos(); 

if ($cli_create) {
    # Headless Mode Defaults
    if (!$cli_php) { $cli_php = get_latest_php_version(); }
    my $target_web = $cli_web || "nginx"; 
    
    # Headless Conflict Resolution
    if ($target_web eq "nginx" && get_service_state("apache2") eq "running") {
        system("systemctl stop apache2");
        system("systemctl disable apache2");
    }

    my %conf = (
        domain      => $cli_domain,
        canonical   => $cli_domain,
        aliases     => [], 
        php         => $cli_php,
        webserver   => $target_web,
        firewall    => "none",
        fw_ips      => [],
        email       => "admin\@example.com",
        wp_version  => "latest", 
        pma_mode    => "none",
        db_name     => "pressmint_db",
        db_user     => "pressmint_user",
        wp_user     => "admin",
        wp_pass     => random_pass(16),
        sys_pass    => random_pass(12),
        paranoid    => 0,
        ssl_staging => 1,
    );
    install_stack(\%conf); 
    exit;
}

# Interactive Dashboard
my %system_status = scan_system_status();
show_dashboard(\%system_status);

while (1) {
    print_menu();
    my $choice = prompt_menu_selection(9); 
    handle_choice($choice, \%system_status);
}

# ==============================================================================
#  CORE LOGIC
# ==============================================================================

sub handle_choice {
    my ($c, $s) = @_;

    if ($c eq "1") {
        if (-e $STATE_FILE) {
            print "\n[!] System is already installed. Use 'Uninstall/Cleanup' menu.\n";
            return;
        }
        
        # --- WIZARD START ---
        system("clear");
        print "========================================================\n";
        print "  PRESSMINT INSTALLATION WIZARD\n";
        print "========================================================\n\n";

        # 1. Shared Domain Wizard
        my ($raw_dom, $final_dom, $final_aliases_ref) = interactive_domain_wizard();
        my @final_aliases = @{$final_aliases_ref};

        # 2. Architecture
        my $target_web = select_webserver_interactive();
        if (!$target_web) { return; } 
        my $php = select_php_interactive();
        my ($fw_type, $fw_ips) = select_firewall_interactive();

        # 3. Database Credentials
        print "\n--------------------------------------------------------\n";
        print "  DATABASE CONFIGURATION\n";
        print "--------------------------------------------------------\n";
        
        my $db_name = prompt_safe(
            "Database Name", "pressmint_db", 
            qr/^[a-zA-Z0-9_]+$/, "Letters, Numbers, Underscore (_)", 64
        );
        my $db_user = prompt_safe(
            "Database User", "pressmint_user", 
            qr/^[a-zA-Z0-9_\-]+$/, "Letters, Numbers, Underscore (_), Dash (-)", 80
        );

        # 4. WordPress Credentials
        print "\n--------------------------------------------------------\n";
        print "  WORDPRESS ADMIN CONFIGURATION\n";
        print "--------------------------------------------------------\n";
        
        my $wp_user = prompt_safe(
            "WP Admin Username", "admin", 
            qr/^[a-zA-Z0-9._\-@]+$/, "Letters, Numbers, . - _ @", 60
        );
        my $wp_pass_def = random_pass(16);
        my $wp_pass = prompt_with_default("  WP Admin Password", $wp_pass_def);
        
        # SMART EMAIL: Check for existing Let's Encrypt accounts
        my $email = get_smart_ssl_email("  Admin Email (for Let's Encrypt & WordPress)");

        # Select Version
        my $wp_version = select_wp_version_interactive();
        
        # 5. System User Credential
        my $sys_pass_def = random_pass(12);
        
        # 6. PMA Setup
        my $pma_mode = select_pma_interactive(); 
        my $pma_url = "";
        my $pma_gate_user = "";
        my $pma_gate_pass = "";
        
        if ($pma_mode ne "none") {
            print "\n  [phpMyAdmin Configuration]\n";
            my $rand_slug = "/db_" . random_hex(8);
            
            $pma_url = prompt_with_default("  Set Custom URL Path", $rand_slug);

            if ($pma_mode eq "fortified") {
                print "\n  [HTTP Basic Auth Credentials]\n";
                print "  Required for the browser popup (Public Internet Access).\n";
                while (!$pma_gate_user) { $pma_gate_user = prompt_user("  Set Browser Username: "); }
                while (!$pma_gate_pass) { $pma_gate_pass = prompt_user("  Set Browser Password: "); }
            } else {
                print "  [Mode: Localhost] Access protected by SSH Tunnel availability.\n";
            }
        }

        # 7. HTTP Auth Overlay
        print "\n--------------------------------------------------------\n";
        print "  SECURITY: HTTP AUTH OVERLAY\n";
        print "--------------------------------------------------------\n";
        print "  Do you want to lock '/wp-admin' with a second password?\n";
        print "  This adds an Nginx/Apache Basic Auth layer (browser popup) in front of WP.\n";
        my $para_in = prompt_user("  Enable Basic Auth Overlay? (y/n) [n]: ");
        my $paranoid = (lc($para_in) eq "y") ? 1 : 0;

        if ($paranoid && !$pma_gate_user) {
             print "\n  [HTTP Basic Auth Credentials]\n";
             print "  Define the user/pass for the browser popup.\n";
             while (!$pma_gate_user) { $pma_gate_user = prompt_user("  Set Browser Username: "); }
             while (!$pma_gate_pass) { $pma_gate_pass = prompt_user("  Set Browser Password: "); }
        }

        # 8. SSL Mode
        print "\n--------------------------------------------------------\n";
        print "  SSL CERTIFICATE MODE\n";
        print "--------------------------------------------------------\n";
        print "  [1] Staging (SAFE): Unlimited retries. Browser warning. Best for testing.\n";
        print "  [2] Production: Browser Trusted (Standard). Strict Rate Limits (5/week).\n";
        print "--------------------------------------------------------\n";
        print "  Selection [Default: 1]: ";
        my $ssl_sel = prompt_menu_selection(2, 1);
        my $is_staging = ($ssl_sel eq "1") ? 1 : 0;

        # Pack Config
        my %conf = (
            domain        => $raw_dom,  # Base name for file paths
            canonical     => $final_dom, # The strict WP URL
            aliases       => \@final_aliases,
            php           => $php,
            webserver     => $target_web,
            firewall      => $fw_type,
            fw_ips        => $fw_ips,
            email         => $email,
            wp_version    => $wp_version,
            pma_mode      => $pma_mode,
            pma_url       => $pma_url,
            pma_gate_user => $pma_gate_user,
            pma_gate_pass => $pma_gate_pass,
            db_name       => $db_name,
            db_user       => $db_user,
            wp_user       => $wp_user,
            wp_pass       => $wp_pass,
            sys_pass      => $sys_pass_def, 
            paranoid      => $paranoid,
            ssl_staging   => $is_staging,
        );

        install_stack(\%conf);
    }
    elsif ($c eq "2") {
        # GUARD: Must be installed first
        if (!-e $STATE_FILE) {
             print "\n[!] Error: PressMint is not installed yet.\n";
             print "    Please use Option 1 (Install) to set up the system.\n";
             print "    (Tip: You can choose Nginx during installation!)\n";
             print "    Press [Enter] to return..."; <STDIN>; return;
        }
        
        my $target_web = select_webserver_interactive();
        if ($target_web) { perform_swap($target_web, $s); }
    }
    elsif ($c eq "3") {
        print "\nCurrent Firewall: " . check_firewall_status() . "\n";
        my ($fw_type, $fw_ips) = select_firewall_interactive();
        configure_firewall($fw_type, $fw_ips);
    }
    elsif ($c eq "4") {
        # GUARD: No Domain
        if (!$s->{domain} || $s->{domain} eq "Not Configured") {
             print "\n[!] Error: No domain configured.\n";
             print "    Install the stack first (Option 1).\n";
             print "    Press [Enter] to return..."; <STDIN>; return;
        }
        manage_ssl_interactive($s);
    }
    elsif ($c eq "5") {
        perform_rebrand_robust($s);
    }
    elsif ($c eq "6") {
        manage_php_ops($s);
    }
    elsif ($c eq "9") {
        show_uninstall_submenu($s);
    }
    elsif ($c eq "0") {
        exit;
    }
    
    %system_status = scan_system_status();
    show_dashboard(\%system_status);
}

sub interactive_domain_wizard {
    # 1. Domain Inventory
    my $raw_dom = prompt_user("  Primary Domain (e.g. example.com): ");
    $raw_dom =~ s/^www\.//; 
    
    # Step 2: WWW Inventory
    my $www_dom = "www.$raw_dom";
    print "\n  [Subdomain Inventory]\n";
    my $add_www = prompt_user("  Add '$www_dom' alias? (y/n) [y]: ");
    my $has_www = (lc($add_www) ne "n") ? 1 : 0;
    
    # Step 3: Extra Aliases
    print "\n  [Additional Aliases]\n";
    print "  Enter any other domains (e.g. blog.$raw_dom) separated by spaces.\n";
    my $alias_in = prompt_user("  Aliases [None]: ");
    my @extra_aliases = split(/\s+/, $alias_in);

    # Step 4: Policy (Canonical Selection)
    my @candidates = ($raw_dom);
    if ($has_www) { push(@candidates, $www_dom); }
    push(@candidates, @extra_aliases);

    my $final_dom = $raw_dom; # Fallback default
    
    if (scalar(@candidates) > 1) {
        print "\n  [Canonical URL Policy]\n";
        print "  Which domain should WordPress enforce as Primary?\n";
        print "  (All others will redirect to this one)\n\n";
        
        my $i = 1;
        foreach my $cand (@candidates) {
            print "  [$i] https://$cand\n";
            $i++;
        }
        
        while(1) {
            print "\n  Select Canonical ID: ";
            my $sel = <STDIN>; chomp($sel);
            if ($sel =~ /^\d+$/ && $sel >= 1 && $sel <= scalar(@candidates)) {
                $final_dom = $candidates[$sel-1];
                last;
            }
            print "  [!] Invalid. Enter a number from 1 to " . scalar(@candidates) . ".";
        }
    } else {
        print "\n  [Canonical URL Policy]\n";
        print "  Only one domain defined. Canonical set to: $final_dom\n";
    }

    # Pack alias list for SSL (exclude the one chosen as canonical)
    my @final_aliases = ();
    foreach my $cand (@candidates) {
        if ($cand ne $final_dom) { push(@final_aliases, $cand); }
    }
    
    return ($raw_dom, $final_dom, \@final_aliases);
}

sub perform_rebrand_robust {
    my $s = shift;

    # GUARD: No Domain
    if (!$s->{domain} || $s->{domain} eq "Not Configured") {
            print "\n[!] Error: No domain configured to migrate.\n";
            print "    Install the stack first (Option 1).\n";
            print "    Press [Enter] to return..."; <STDIN>; return;
    }

    # PRE-FLIGHT CHECK: APACHE SYNTAX
    if ($s->{webserver} eq "Apache") {
        print "\n[*] Running Pre-Flight Check (Apache Syntax)...\n";
        if (system("apache2ctl configtest > /dev/null 2>&1") != 0) {
            print "\n[CRITICAL] Apache configuration is currently BROKEN.\n";
            print "           We cannot safely migrate a broken server.\n";
            print "           Run 'apache2ctl configtest' to diagnose.\n";
            print "Press [Enter] to return..."; <STDIN>; return;
        }
    }

    print "\n  *** PRESSMINT REBRAND WIZARD v2 ***\n";
    print "  (Includes 'Phil Filter' & Config Surgery)\n";
    
    # 1. Wizard
    my ($raw_dom, $final_dom, $final_aliases_ref) = interactive_domain_wizard();
    my @final_aliases = @{$final_aliases_ref};
    if (!$raw_dom) { return; }

    # 2. SSL Selection
    print "\n--------------------------------------------------------\n";
    print "  SSL CERTIFICATE MODE\n";
    print "--------------------------------------------------------\n";
    print "  [1] Staging (SAFE): Unlimited retries. Browser warning.\n";
    print "  [2] Production: Browser Trusted. Strict Rate Limits.\n";
    print "--------------------------------------------------------\n";
    print "  Selection [Default: 1]: ";
    my $ssl_sel = prompt_menu_selection(2, 1);
    my $is_staging = ($ssl_sel eq "1") ? 1 : 0;

    print "\n--------------------------------------------------------\n";
    print "  PHASE 1: THE PHIL FILTER (Hygiene)\n";
    print "--------------------------------------------------------\n";

    # A. Check wp-config.php
    my $wp_config = "$WEB_ROOT/wp-config.php";
    if (system("grep -qE \"define\\(\\s*'WP_HOME'|define\\(\\s*'WP_SITEURL'\" $wp_config") == 0) {
        print "\n  [!] Hardcoded WP_HOME or WP_SITEURL detected in wp-config.php.\n";
        print "      PressMint manages identity via Database. These lines must be disabled.\n";
        print "  Type 'NEXT' to comment out these lines: ";
        my $conf = <STDIN>; chomp($conf);
        if (lc($conf) eq "next") {
            system("sed -i \"s/define(\\s*'WP_HOME'/\/\/ PM-DISABLED: define('WP_HOME'/g\" $wp_config");
            system("sed -i \"s/define(\\s*'WP_SITEURL'/\/\/ PM-DISABLED: define('WP_SITEURL'/g\" $wp_config");
            print "  [SUCCESS] Hardcoded lines disabled.\n";
        } else { return; }
    } else {
        print "  [OK] wp-config.php is clean.\n";
    }

    # B. Check Database for Toxic Import Artifacts
    print "  [*] Scanning Database for 'localhost/127.0.0.1' artifacts...\n";
    my @toxic = ("http://localhost", "https://localhost", "http://127.0.0.1", "https://127.0.0.1");
    my $found_toxic = 0;
    foreach my $url (@toxic) {
        my $count = `runuser -u $SYS_USER -- wp search-replace '$url' '$url' --dry-run --format=count --path=$WEB_ROOT 2>/dev/null`;
        chomp($count);
        if ($count && $count > 0) {
            print "  [!] Found $count entries referencing $url\n";
            $found_toxic = 1;
        }
    }

    if ($found_toxic) {
        print "      These entries are likely from a bad import (e.g. Bitnami).\n";
        print "  Type 'NEXT' to sanitize DB (replace with http://$s->{domain}) and continue: ";
        my $conf = <STDIN>; chomp($conf);
        if (lc($conf) eq "next") {
            foreach my $url (@toxic) {
                system("runuser -u $SYS_USER -- wp search-replace '$url' 'http://$s->{domain}' --path=$WEB_ROOT --quiet");
            }
            print "  [SUCCESS] Database sanitized.\n";
        } else { return; }
    } else {
        print "  [OK] Database is clean.\n";
    }

    print "\n--------------------------------------------------------\n";
    print "  PHASE 2: CONFIGURATION SURGERY\n";
    print "--------------------------------------------------------\n";
    
    my $old_file_dom = $s->{domain};
    $old_file_dom =~ s/^www\.//;
    my $new_file_dom = $raw_dom;
    
    # Apache Config Surgery
    if ($s->{webserver} eq "Apache") {
        my $old_conf_path = "/etc/apache2/sites-available/$old_file_dom.conf";
        my $new_conf_path = "/etc/apache2/sites-available/$new_file_dom.conf";
        
        if (!-e $old_conf_path) {
            print "\n[CRITICAL] Old config $old_conf_path not found. Cannot proceed.\n";
            print "Press [Enter]..."; <STDIN>; return;
        }

        print "  [*] Cloning $old_file_dom.conf -> $new_file_dom.conf...\n";
        
        # Read Old
        open(my $fh_in, '<', $old_conf_path) or die "Cannot read $old_conf_path";
        my @lines = <$fh_in>;
        close($fh_in);

        # Write New (Surgery)
        open(my $fh_out, '>', $new_conf_path) or die "Cannot write $new_conf_path";
        
        my $injected = 0;
        foreach my $line (@lines) {
            # Comment out Identity & Rewrites
            if ($line =~ /^\s*(ServerName|ServerAlias|RewriteRule|RewriteCond)/i) {
                print $fh_out "# [PM-OLD] " . $line;
            } else {
                print $fh_out $line;
            }
            
            # Inject New Identity after VirtualHost
            if ($line =~ /<VirtualHost \*:80>/i && !$injected) {
                print $fh_out "    ServerName $final_dom\n";
                if (@final_aliases) {
                    print $fh_out "    ServerAlias " . join(" ", @final_aliases) . "\n";
                }
                $injected = 1;
            }
        }
        close($fh_out);
        print "  [SUCCESS] Config Surgery Complete. Secrets/Aliases preserved.\n";
        
        # Disable Old, Test New
        system("a2dissite $old_file_dom.conf >/dev/null 2>&1");
        if (-e "/etc/apache2/sites-enabled/$old_file_dom-le-ssl.conf") {
            system("a2dissite $old_file_dom-le-ssl.conf >/dev/null 2>&1");
        }
        system("a2ensite $new_file_dom.conf >/dev/null 2>&1");
        
        if (system("apache2ctl configtest > /dev/null 2>&1") != 0) {
            print "\n  [FAIL] New configuration failed syntax check.\n";
            print "         Reverting changes...\n";
            system("a2dissite $new_file_dom.conf >/dev/null 2>&1");
            system("a2ensite $old_file_dom.conf >/dev/null 2>&1");
            system("rm $new_conf_path");
            print "  Press [Enter] to return..."; <STDIN>; return;
        }
        system("systemctl reload apache2");
    } 
    else {
        # Nginx (Regeneration Path - safer for Nginx structure)
        # Note: Nginx doesn't have the same 'localhost' redirect loop issue as Apache
        print "  [*] Regenerating Nginx Configuration...\n";
        
        # Unlink old
        if (-e "/etc/nginx/sites-enabled/$old_file_dom") {
            unlink("/etc/nginx/sites-enabled/$old_file_dom");
        }
        
        # Use existing configure_vhost to build fresh
        # Note: This does lose custom blocks on Nginx, but matches user request 
        # to focus on Apache surgery for now.
        my $pma_mode = "none";
        if (-d $PMA_DIR) { $pma_mode = "fortified"; }
        
        my %new_conf = (
            domain    => $raw_dom,
            canonical => $final_dom,
            aliases   => \@final_aliases,
            php       => $s->{php},
            webserver => "nginx",
            pma_mode  => $pma_mode,
            paranoid  => 0,
            pma_url   => "/db_admin", # Warning: Reset to default on Nginx
        );
        configure_vhost(\%new_conf);
    }

    print "\n--------------------------------------------------------\n";
    print "  PHASE 3: MIGRATION & SSL\n";
    print "--------------------------------------------------------\n";

# DB Update
    print "  [*] Updating Database Links ($s->{domain} -> $final_dom)...\n";
    system("runuser -u $SYS_USER -- wp search-replace '$s->{domain}' '$final_dom' --path=$WEB_ROOT --quiet");
    system("runuser -u $SYS_USER -- wp cache flush --path=$WEB_ROOT --quiet");

    # SSL
    print "  [*] Requesting New SSL...\n";
    my $email = get_smart_ssl_email("  Confirm Email for SSL");
    my %ssl_conf = (
        canonical => $final_dom,
        aliases   => \@final_aliases,
        webserver => lc($s->{webserver}),
        email     => $email
    );
    configure_ssl_auto(\%ssl_conf, { staging => $is_staging, force => 0 });

    # State Update
    my @lines = ();
    open(my $fh_in, '<', $STATE_FILE); while(<$fh_in>){ push(@lines,$_); } close $fh_in;
    open(my $fh_out, '>', $STATE_FILE);
    foreach (@lines) {
        if (/^DOMAIN=/) { print $fh_out "DOMAIN=$final_dom\n"; } else { print $fh_out $_; }
    }
    close $fh_out;

    print "\n[SUCCESS] Migration Complete. System is now hosting: $final_dom\n";
    
    # Optional: Delete old certs
    print "\n  [Cleanup]\n";
    print "  Do you want to delete the SSL certificate for $s->{domain}? (y/n): ";
    my $del = <STDIN>; chomp($del);
    if (lc($del) eq "y") {
        system("certbot delete --cert-name $s->{domain}");
    }
    
    print "Press [Enter] to return..."; <STDIN>;
}

sub perform_swap {
    my ($target, $s) = @_;
    print "[*] Installing $target...\n";
    system("apt-get install -y $target");
    
    print "[*] Generating Configuration for $target...\n";
    
    # Re-read state (Best Guess)
    my $pma_mode = "none";
    if (-d $PMA_DIR) { $pma_mode = "fortified"; } 
    
    my %conf = (
        domain => $s->{domain},
        canonical => $s->{domain},
        aliases => [],
        php => $s->{php},
        webserver => $target,
        pma_mode => $pma_mode,
        paranoid => 0, # Lost setting
        pma_url => "/db_admin", # Lost setting default
    );
    
    configure_vhost(\%conf);
    
    # Start new, Stop old
    my $old = ($target eq "nginx") ? "apache2" : "nginx";
    system("systemctl stop $old");
    system("systemctl disable $old");
    system("systemctl enable $target");
    system("systemctl restart $target");
    
    # Update State
    my @lines = ();
    open(my $fh_in, '<', $STATE_FILE); while(<$fh_in>){ push(@lines,$_); } close $fh_in;
    open(my $fh_out, '>', $STATE_FILE);
    foreach (@lines) {
        if (/^WEBSERVER=/) { print $fh_out "WEBSERVER=$target\n"; } else { print $fh_out $_; }
    }
    close $fh_out;
    
    print "[SUCCESS] Swapped to $target.\n";
    print "[NOTE] If you had custom PMA URLs or Auth settings, they reset to defaults.\n";
    print "       Please use menu [1] (Install/Repair) if you need to re-enter them.\n";
    print "Press [Enter]..."; <STDIN>;
}

sub initialize_repos {
    if (-e "/etc/apt/sources.list.d/php.list") { return; }
    print "[*] Initializing System Repositories...\n";
    system("apt-get update >/dev/null");
    system("apt-get -y install lsb-release apt-transport-https ca-certificates curl git unzip >/dev/null 2>&1");
    my $codename = `lsb_release -sc`; chomp($codename);
    system("curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg");
    system("echo 'deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $codename main' > /etc/apt/sources.list.d/php.list");
    
    # --- APT PINNING (Prevent Overwrites) ---
    my $pin_conf = <<EOF;
Package: php*
Pin: origin packages.sury.org
Pin-Priority: 1000

Package: php*
Pin: release o=Debian
Pin-Priority: 100
EOF
    open(my $fh, '>', "/etc/apt/preferences.d/pressmint-php"); print $fh $pin_conf; close $fh;
    # ----------------------------------------

    print "[*] Updating package lists...\n";
    if (system("apt-get update") != 0) {
        print "\n[CRITICAL] REPO ERROR. Fallback to native?\n[1] Yes [2] Abort: ";
        my $c = <STDIN>; chomp($c);
        if ($c eq "1") { unlink("/etc/apt/sources.list.d/php.list"); system("apt-get update"); } 
        else { die "Aborted.\n"; }
    }
    print "[*] System Libraries Updated.\n";
}

sub get_latest_php_version {
    my @v = `apt-cache pkgnames | grep -E '^php[0-9]+\\.[0-9]+\$' | sort -V`;
    my @c;
    foreach(@v){ if(/php(\d+\.\d+)/){ push(@c,$1); } }
    return $c[-1] || "8.2";
}

sub install_stack {
    my $conf_ref = shift;
    my %c = %{$conf_ref}; 
    
    print "\n==================================================\n";
    print "  IGNITING PRESSMINT INSTALLATION\n";
    print "  Domain: $c{canonical} | Stack: $c{webserver} + PHP $c{php}\n";
    print "==================================================\n";

    print "[*] Installing packages...\n";
    my @pkgs = (
        "sudo", "mariadb-server", "mariadb-client",
        "php$c{php}-fpm", "php$c{php}-mysql", "php$c{php}-xml", 
        "php$c{php}-mbstring", "php$c{php}-curl", "php$c{php}-zip", "php$c{php}-intl", 
        "php$c{php}-gd", "php$c{php}-bcmath", "php$c{php}-imagick", "certbot"
    );

    if ($c{webserver} eq "nginx") {
        push(@pkgs, "nginx", "python3-certbot-nginx");
    } else {
        push(@pkgs, "apache2", "python3-certbot-apache");
    }
    
    if (system("apt-get -y install " . join(" ", @pkgs)) != 0) {
        die "\n[CRITICAL ERROR] Apt-get failed.\n";
    }

    # --- IDENTITY SHIFT ---
    # Create System User First (so we can set FPM to it)
    print "[*] Configuring System User ($SYS_USER)...\n";
    if (!getpwnam($SYS_USER)) {
        # Create user with home, bash, and group
        system("useradd -m -d $APP_DIR -s /bin/bash $SYS_USER");
        # Set Password
        my $enc = `openssl passwd -1 '$c{sys_pass}'`; chomp($enc);
        system("usermod -p '$enc' $SYS_USER");
    }

    tune_php_ini($c{php});
    configure_fpm_identity($c{php});

    if ($c{webserver} eq "apache") {
        print "[*] Configuring Apache for FPM/Event mode...\n";
        system("a2dismod mpm_prefork >/dev/null 2>&1");
        system("a2dismod php$c{php} >/dev/null 2>&1");
        # Added headers and expires for caching fix
        system("a2enmod mpm_event proxy_fcgi setenvif rewrite headers expires");
        system("systemctl restart apache2");
    }

    if ($c{firewall}) { configure_firewall($c{firewall}, $c{fw_ips}); }

    print "[*] Creating structure...\n";
    # Ensure dirs exist (useradd -m creates root, we add webroot)
    make_path($WEB_ROOT);

    # --- CRITICAL FIX: TRANSFER OWNERSHIP BEFORE DOWNLOAD ---
    system("chown -R $SYS_USER:$SYS_USER $APP_DIR");
    system("chmod 755 $APP_DIR");
    # ----------------------------------------------------
    
    if (!-e "/usr/local/bin/wp") {
        system("curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar");
        system("chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp");
    }

    install_phpmyadmin($c{pma_mode}, $c{pma_url});

    if ($c{pma_gate_user}) {
        my $crypt = `openssl passwd -5 '$c{pma_gate_pass}'`;
        chomp($crypt);
        open(my $fh, '>', "$APP_DIR/.htpasswd");
        print $fh "$c{pma_gate_user}:$crypt\n";
        close $fh;
    }

    configure_vhost(\%c);
    deploy_wordpress(\%c);
    
    configure_ssl_auto(\%c, { staging => $c{ssl_staging}, force => 0 });

    # --- FINAL PERMISSION SWEEP ---
    print "[*] Applying Final Permission Lock ($SYS_USER)...\n";
    system("chown -R $SYS_USER:$SYS_USER $APP_DIR");
    system("chmod 755 $APP_DIR");
    system("find $WEB_ROOT -type d -exec chmod 755 {} \\;");
    system("find $WEB_ROOT -type f -exec chmod 644 {} \\;");

    open(my $fh, '>', $STATE_FILE);
    print $fh "INSTALLED=1\nDOMAIN=$c{canonical}\nPHP=$c{php}\nWEBSERVER=$c{webserver}\n";
    close $fh;

    # --- PAUSE FOR REVIEW ---
    print "\n[!] Installation tasks complete.\n";
    print "    Review the logs above, then press [Enter] to view credentials...";
    <STDIN>; 

    # --- FINAL CREDENTIAL BLOCK ---
    system("clear");
    print "\n########################################################\n";
    print "  PRESSMINT INSTALLATION COMPLETE\n";
    print "########################################################\n\n";
    
    print "  [1] WORDPRESS (Application Layer)\n";
    print "  URL:  https://$c{canonical}/wp-admin\n";
    print "  User: $c{wp_user}\n";
    print "  Pass: $c{wp_pass}\n\n";

    print "  [2] SYSTEM USER (SFTP / SSH)\n";
    print "  User: $SYS_USER\n";
    print "  Pass: $c{sys_pass}\n";
    print "  Path: $APP_DIR\n\n";

    print "  [3] DATABASE (Internal)\n";
    print "  Name: $c{db_name}\n";
    print "  User: $c{db_user}\n";
    print "  Pass: $c{db_pass}\n\n";

    if ($c{pma_mode} ne "none") {
        print "  [4] PHPMYADMIN (Database UI)\n";
        if ($c{pma_mode} eq "fortified") {
            print "  URL:  https://$c{canonical}$c{pma_url}\n";
            print "  Note: Requires 'Browser Popup Credentials' (below).\n";
        } elsif ($c{pma_mode} eq "localhost") {
            print "  URL:  http://localhost:8888$c{pma_url}\n";
            print "  Note: Requires SSH Tunnel (ssh -L 8888:127.0.0.1:80 ...)\n";
        }
        print "\n";
    }

    if ($c{pma_gate_user}) {
        print "  [5] BROWSER POPUP CREDENTIALS (HTTP Basic Auth)\n";
        print "  Required for: ";
        my @reqs = ();
        if ($c{pma_mode} eq "fortified") { push(@reqs, "phpMyAdmin"); }
        if ($c{paranoid}) { push(@reqs, "/wp-admin"); }
        print join(" AND ", @reqs) . "\n";
        print "  User: $c{pma_gate_user}\n";
        print "  Pass: $c{pma_gate_pass}\n\n";
    }
    
    print "########################################################\n";
    print "  Please save these credentials now.\n";
    print "  --> This is the last time they will be shown. <--\n";
    
    while(1) {
        print "  Type 'exit' to close installer: ";
        my $in = <STDIN>; chomp($in);
        if (lc($in) eq "exit") { last; }
    }
}

sub tune_php_ini {
    my ($v, $mem, $post, $time) = @_;
    # Defaults if not provided (for initial install)
    $mem  ||= "256M";
    $post ||= "100M";
    $time ||= "300";

    print "[*] Tuning PHP $v (Mem: $mem, Time: $time)...\n";
    my $ini = "/etc/php/$v/fpm/php.ini";
    if (-e $ini) {
        system("sed -i 's/^upload_max_filesize.*/upload_max_filesize = $post/' $ini");
        system("sed -i 's/^post_max_size.*/post_max_size = $post/' $ini");
        system("sed -i 's/^memory_limit.*/memory_limit = $mem/' $ini");
        # Add execution time if not present or replace it
        system("sed -i 's/^max_execution_time.*/max_execution_time = $time/' $ini");
        system("systemctl restart php$v-fpm");
    } else {
        print "[!] Warning: Could not find php.ini at $ini\n";
    }
}

# --- FPM IDENTITY MANAGER ---
sub configure_fpm_identity {
    my $v = shift;
    print "[*] Configuring PHP $v to run as '$SYS_USER' (Identity Shift)...\n";
    
    my $pool_conf = "/etc/php/$v/fpm/pool.d/www.conf";
    if (-e $pool_conf) {
        # 1. Change User/Group to pressmint
        system("sed -i 's/^user = www-data/user = $SYS_USER/' $pool_conf");
        system("sed -i 's/^group = www-data/group = $SYS_USER/' $pool_conf");
        
        # 2. Ensure Listen Owner remains www-data (so Nginx/Apache can talk to it)
        system("sed -i 's/^;listen.owner/listen.owner/' $pool_conf");
        system("sed -i 's/^;listen.group/listen.group/' $pool_conf");
        system("sed -i 's/^listen.owner = .*/listen.owner = www-data/' $pool_conf");
        system("sed -i 's/^listen.group = .*/listen.group = www-data/' $pool_conf");

        system("systemctl restart php$v-fpm");
    } else {
        print "[!] Error: FPM Pool config not found at $pool_conf\n";
    }
}

# --- PMA LOGIC ---

sub select_pma_interactive {
    print "\n--------------------------------------------------\n";
    print "  Database Management (phpMyAdmin)\n";
    print "--------------------------------------------------\n";
    print "  [1] None (Best Security)\n";
    print "  [2] Localhost Only (Requires SSH Tunnel)\n";
    print "  [3] Fortified Public Access (Protected URL)\n";
    print "--------------------------------------------------\n";
    print "  Selection [Default: 1]: ";
    my $sel = prompt_menu_selection(3, 1);
    
    if ($sel eq "2") { return "localhost"; }
    if ($sel eq "3") { return "fortified"; }
    return "none";
}

sub install_phpmyadmin {
    my ($mode, $custom_path) = @_;
    if ($mode eq "none") { return; }
    
    print "[*] Installing phpMyAdmin...\n";
    remove_tree($PMA_DIR);
    make_path($PMA_DIR);
    system("curl -sL https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-english.tar.gz | tar xz -C $PMA_DIR --strip-components=1");
    
    my $secret = random_pass(32);
    my $cfg = <<EOF;
<?php
\$cfg['blowfish_secret'] = '$secret';
\$i = 0; \$i++;
\$cfg['Servers'][\$i]['auth_type'] = 'cookie';
\$cfg['Servers'][\$i]['host'] = 'localhost';
\$cfg['Servers'][\$i]['compress'] = false;
\$cfg['Servers'][\$i]['AllowNoPassword'] = false;
\$cfg['UploadDir'] = '';
\$cfg['SaveDir'] = '';
EOF
    open(my $fh, '>', "$PMA_DIR/config.inc.php"); print $fh $cfg; close $fh;
}

sub configure_vhost {
    my $c = shift;
    print "[*] Generating $c->{webserver} configuration...\n";
    
    my %uniq_domains;
    $uniq_domains{$c->{canonical}} = 1;
    foreach (@{$c->{aliases}}) { $uniq_domains{$_} = 1; }
    
    my @extras = ();
    foreach my $d (keys %uniq_domains) {
        if ($d ne $c->{canonical}) { push(@extras, $d); }
    }
    
    my $apache_server_name = $c->{canonical};
    my $apache_alias_cmd = (@extras) ? "ServerAlias " . join(" ", @extras) : "";
    
    my $nginx_server_names = join(" ", keys %uniq_domains);

    my $pma_block = "";
    
    if ($c->{webserver} eq "nginx") {
        my $auth_directive = "";
        my $allow_directive = "deny all;"; 

        if ($c->{pma_mode} eq "localhost") {
            $allow_directive = "allow 127.0.0.1; deny all;";
        }
        elsif ($c->{pma_mode} eq "fortified") {
            $allow_directive = "";
            $auth_directive = <<EOF;
        auth_basic "Restricted Database Access";
        auth_basic_user_file $APP_DIR/.htpasswd;
EOF
        }

        if ($c->{pma_mode} ne "none") {
            my $url = $c->{pma_url} || "/pma_admin";
            # FIXED: Added HTTPS on to fix mismatch blank page.
            $pma_block = <<EOF;
    location $url {
        alias $PMA_DIR;
        index index.php;
        $auth_directive
        $allow_directive

        location ~ \\.php\$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME \$request_filename;
            fastcgi_param HTTPS on;
            fastcgi_pass unix:/run/php/php$c->{php}-fpm.sock;
        }
    }
EOF
        }
        
        my $paranoid_block = "";
        if ($c->{paranoid}) {
            $paranoid_block = <<EOF;
    location ^~ /wp-admin/ {
        auth_basic "Restricted Administration";
        auth_basic_user_file $APP_DIR/.htpasswd;

        location ~ \\.php\$ {
            include snippets/fastcgi-php.conf;
            fastcgi_param SCRIPT_FILENAME \$request_filename;
            fastcgi_pass unix:/run/php/php$c->{php}-fpm.sock;
        }
        try_files \$uri \$uri/ /index.php?\$args;
    }
EOF
        }
        
        # Nginx Config with Browser Caching & Logs
        my $conf = <<EOF;
server {
    listen 80;
    server_name $nginx_server_names;
    root $WEB_ROOT;
    index index.php index.html;
    
    access_log /var/log/nginx/pressmint-site-access.log;
    error_log /var/log/nginx/pressmint-site-error.log;

    location / { try_files \$uri \$uri/ /index.php?\$args; }
    
    # Browser Caching for Static Assets
    location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)\$ {
        expires 1y;
        log_not_found off;
    }

    $paranoid_block

    location ~ \\.php\$ {
        include snippets/fastcgi-php.conf;
        fastcgi_param SCRIPT_FILENAME \$request_filename;
        fastcgi_pass unix:/run/php/php$c->{php}-fpm.sock;
        fastcgi_pass_header Authorization;
    }
    $pma_block
    location ~ /\\.ht { deny all; }
}
EOF
        my $file_dom = $c->{domain};
        $file_dom =~ s/^www\.//; 

        open(my $fh, '>', "/etc/nginx/sites-available/$file_dom"); print $fh $conf; close $fh;
        system("ln -sf /etc/nginx/sites-available/$file_dom /etc/nginx/sites-enabled/");
        system("rm -f /etc/nginx/sites-enabled/default");
        system("systemctl reload nginx");

    } else { 
        # Apache Config
        if ($c->{pma_mode} ne "none") {
            my $url = $c->{pma_url} || "/pma_admin";
            my $alias_cmd = "Alias $url $PMA_DIR";
            my $auth_cmd = "Require local";
            if ($c->{pma_mode} eq "fortified") {
                $auth_cmd = <<EOF;
        AuthType Basic
        AuthName "Restricted Database Access"
        AuthUserFile $APP_DIR/.htpasswd
        Require valid-user
EOF
            }
            
            $pma_block = <<EOF;
    $alias_cmd
    <Directory $PMA_DIR>
        Options FollowSymLinks
        DirectoryIndex index.php
        AllowOverride All
        $auth_cmd
        <FilesMatch \\.php\$>
            SetHandler "proxy:unix:/run/php/php$c->{php}-fpm.sock|fcgi://localhost"
        </FilesMatch>
    </Directory>
EOF
        }

        my $paranoid_block = "";
        if ($c->{paranoid}) {
             $paranoid_block = <<EOF;
    <Directory "$WEB_ROOT/wp-admin">
        AuthType Basic
        AuthName "Restricted Administration"
        AuthUserFile $APP_DIR/.htpasswd
        Require valid-user
    </Directory>
EOF
        }
    
        # Apache Config with Caching, Auth Header, Logs & ProxyPreserveHost
        my $conf = <<EOF;
<VirtualHost *:80>
    ServerName $c->{canonical}
    $apache_alias_cmd
    DocumentRoot $WEB_ROOT
    
    # Critical Fix for Reverse Proxy Identity
    ProxyPreserveHost On
    
    # Logging
    ErrorLog \${APACHE_LOG_DIR}/pressmint-site-error.log
    CustomLog \${APACHE_LOG_DIR}/pressmint-site-access.log combined

    <Directory $WEB_ROOT>
        AllowOverride All
        Require all granted
    </Directory>
    
    # Fix Authorization Header for REST API
    SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=\$1
    
    # Browser Caching
    <IfModule mod_expires.c>
        ExpiresActive On
        ExpiresByType image/jpg "access plus 1 year"
        ExpiresByType image/jpeg "access plus 1 year"
        ExpiresByType image/gif "access plus 1 year"
        ExpiresByType image/png "access plus 1 year"
        ExpiresByType text/css "access plus 1 year"
        ExpiresByType application/pdf "access plus 1 month"
        ExpiresByType text/javascript "access plus 1 year"
        ExpiresByType application/javascript "access plus 1 year"
        ExpiresByType application/x-javascript "access plus 1 year"
        ExpiresByType image/x-icon "access plus 1 year"
        ExpiresByType font/woff2 "access plus 1 year"
        ExpiresByType font/woff "access plus 1 year"
        ExpiresByType application/font-woff "access plus 1 year"
        ExpiresDefault "access plus 2 days"
    </IfModule>

    $paranoid_block

    <FilesMatch \\.php\$>
        SetHandler "proxy:unix:/run/php/php$c->{php}-fpm.sock|fcgi://localhost"
    </FilesMatch>
    $pma_block
</VirtualHost>
EOF
        my $file_dom = $c->{domain};
        $file_dom =~ s/^www\.//; 

        open(my $fh, '>', "/etc/apache2/sites-available/$file_dom.conf");
        print $fh $conf; close $fh;
        system("a2ensite $file_dom.conf");
        system("a2dissite 000-default.conf");
        system("a2enmod rewrite proxy_fcgi setenvif headers expires");
        system("systemctl restart apache2");
    }
}

sub deploy_wordpress {
    my $c = shift;
    
    my $ver_str = ($c->{wp_version} eq "latest") ? "Latest" : $c->{wp_version};
    print "[*] Downloading & Installing WordPress ($ver_str)...\n";
    
    my $ver_flag = "";
    if ($c->{wp_version} ne "latest") {
        $ver_flag = "--version=" . $c->{wp_version};
    }
    
    # Use runuser to avoid sudo noise
    system("runuser -u $SYS_USER -- wp core download $ver_flag --path=$WEB_ROOT --force");
    
    $c->{db_pass} = random_pass(12);
    system("mysql -e \"CREATE DATABASE IF NOT EXISTS $c->{db_name};\"");
    system("mysql -e \"GRANT ALL PRIVILEGES ON $c->{db_name}.* TO '$c->{db_user}'\@'localhost' IDENTIFIED BY '$c->{db_pass}';\"");
    system("mysql -e \"FLUSH PRIVILEGES;\"");
    
    system("runuser -u $SYS_USER -- wp config create --dbname=$c->{db_name} --dbuser=$c->{db_user} --dbpass='$c->{db_pass}' --path=$WEB_ROOT");
    system("runuser -u $SYS_USER -- wp config set FS_METHOD direct --path=$WEB_ROOT");

    print "[*] Running WP Install...\n";
    # Added --skip-email to silence 'sendmail not found' errors
    system("runuser -u $SYS_USER -- wp core install --url='https://$c->{canonical}' --title='New PressMint Site' --admin_user='$c->{wp_user}' --admin_password='$c->{wp_pass}' --admin_email='$c->{email}' --path=$WEB_ROOT --skip-email");
    
    print "\n[+] WordPress Installed.\n";
}

sub configure_ssl_auto {
    my ($c, $opts) = @_;
    my $is_staging = $opts->{staging} || 0;
    my $force = $opts->{force} || 0;

    my $type_lbl = $is_staging ? "STAGING (TEST)" : "PRODUCTION";
    print "[*] Requesting SSL Certificate [$type_lbl]...\n";
    
    my %uniq_domains;
    $uniq_domains{$c->{canonical}} = 1;
    foreach (@{$c->{aliases}}) { $uniq_domains{$_} = 1; }
    
    my $dom_args = "";
    foreach my $d (keys %uniq_domains) { $dom_args .= " -d $d"; }

    my $flags = "--non-interactive --agree-tos -m $c->{email}";
    if ($is_staging) { $flags .= " --test-cert"; }
    if ($force)      { $flags .= " --force-renewal"; }

    my $cmd = ($c->{webserver} eq "nginx") 
        ? "certbot --nginx $dom_args $flags"
        : "certbot --apache $dom_args $flags";
    
    while(1) {
        if (system($cmd) == 0) { 
            print "[SUCCESS] SSL Installed ($type_lbl).\n"; 
            if ($is_staging) { print "[NOTE] This is a FAKE certificate. Your browser will warn you.\n"; }
            last; 
        }
        print "\n[!] SSL Failed (DNS issue?). [r]etry or [s]kip: ";
        my $in = <STDIN>; chomp($in); if (lc($in) eq "s") { last; }
    }
}

sub configure_firewall {
    my ($mode, $ips_ref) = @_;
    my @ips = @{$ips_ref};
    if ($mode eq "none") { return; }
    
    my $ssh_port = `netstat -tlpn | grep sshd | awk '{print \$4}' | awk -F: '{print \$NF}' | head -n 1`; chomp($ssh_port); $ssh_port ||= 22;
    if ($mode eq "ufw") {
        print "[*] Configuring UFW (Safe Mode)...\n";
        system("apt-get install -y ufw");
        system("ufw default deny incoming");
        system("ufw default allow outgoing");
        if (@ips) { foreach (@ips) { system("ufw allow from $_ to any port $ssh_port proto tcp"); } }
        else { system("ufw allow $ssh_port/tcp"); }
        system("ufw allow 80/tcp"); system("ufw allow 443/tcp");
        system("echo 'y' | ufw enable");
    } elsif ($mode eq "nftables") {
        print "[*] Configuring NFTables with Dry-Run Check...\n";
        system("apt-get install -y nftables");
        my $ssh_rule = (@ips) ? "ip saddr { " . join(", ", @ips) . " } tcp dport $ssh_port accept" : "tcp dport $ssh_port accept";
        
        my $conf_content = "#!/usr/sbin/nft -f\nflush ruleset\ntable inet filter {\nchain input {\ntype filter hook input priority 0; policy drop;\niifname \"lo\" accept\nct state established,related accept\nip protocol icmp accept\nip6 nexthdr icmpv6 accept\n$ssh_rule\ntcp dport 80 accept\ntcp dport 443 accept\n}\nchain forward { type filter hook forward priority 0; policy drop; }\nchain output { type filter hook output priority 0; policy accept; }\n}\n";
        
        open(my $fh_tmp, '>', "/tmp/nft_check.conf"); print $fh_tmp $conf_content; close $fh_tmp;
        if (system("nft -c -f /tmp/nft_check.conf") != 0) {
            print "\n[CRITICAL ERROR] NFTables Syntax Check Failed! Aborting firewall change.\n";
            print "Your rules generated invalid syntax. System safe.\n";
            return;
        }

        open(my $fh, '>', "/etc/nftables.conf"); print $fh $conf_content; close $fh;
        system("systemctl enable nftables && systemctl restart nftables");
        print "[SUCCESS] NFTables applied successfully.\n";
    }
}

sub show_dashboard {
    my $s = shift;
    system("clear");
    print "========================================================\n";
    print "  PressMint Control Deck (v1.0)\n";
    print "========================================================\n";
    print "  STATUS:\n";
    print "  [*] Web Engine:    $s->{webserver}\n";
    print "  [*] PHP Version:   $s->{php}\n";
    print "  [*] Domain:        " . ($s->{domain} || "Not Configured") . "\n";
    print "========================================================\n";
}

sub print_menu {
    print "\n  AVAILABLE OPERATIONS:\n";
    print "  [1] INSTALL / REPAIR\n";
    print "  ------------------------------------------------\n";
    print "  [2] SWAP ARCH: Webserver (Nginx <-> Apache)\n";
    print "  [3] SWAP ARCH: Firewall (UFW / NFTables / None)\n";
    print "  [4] CERTIFICATE: Manage SSL\n";
    print "  ------------------------------------------------\n";
    print "  [5] REBRAND: Change Primary Domain\n";
    print "  [6] PHP & PERFORMANCE OPS (Switch / Tune)\n";
    print "  ------------------------------------------------\n";
    print "  [9] UNINSTALL / CLEANUP (Reset Options)\n";
    print "  [0] Exit\n";
    print "\n  Select Command: ";
}

sub select_webserver_interactive {
    my $current_ws = "None";
    if (get_service_state("apache2") eq "running") { $current_ws = "apache"; }
    elsif (get_service_state("nginx") eq "running") { $current_ws = "nginx"; }
    
    print "\n--------------------------------------------------\n";
    print "  Select Web Server Architecture\n";
    print "--------------------------------------------------\n";
    my $lbl_apache = ($current_ws eq "apache") ? "Apache (Active)" : "Apache";
    my $lbl_nginx  = ($current_ws eq "nginx")  ? "Nginx  (Active)" : "Nginx";
    print "  [1] $lbl_apache\n";
    print "  [2] $lbl_nginx\n";
    print "--------------------------------------------------\n";
    print "  Selection: ";
    
    my $def_idx = ($current_ws eq "apache") ? 1 : ($current_ws eq "nginx") ? 2 : undef;
    my $sel = prompt_menu_selection(2, $def_idx); 
    
    my $target = ($sel eq "1") ? "apache" : "nginx";
    if ($target eq $current_ws) {
        print "\n[*] Keeping $lbl_apache active. Installing as VHost.\n";
        return $target;
    }

    # SMART CLEANUP: If this is a fresh install (no state file), 
    # and we are switching from a running service, silently kill it.
    if (!-e $STATE_FILE && $current_ws ne "None") {
        print "\n[*] Detected pre-installed $current_ws. Removing to clear Port 80...\n";
        my $svc = ($current_ws eq "apache") ? "apache2" : "nginx";
        system("systemctl stop $svc");
        system("systemctl disable $svc");
        return $target;
    }
    
    if ($current_ws ne "None" && $target ne $current_ws && $target ne "None") {
        print "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
        print "  WARNING: ARCHITECTURE SWITCH\n";
        print "  Switching from $current_ws to $target.\n";
        print "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
        print "  Proceed? (y/n): ";
        my $conf = <STDIN>; chomp($conf);
        if (lc($conf) ne "y") { return 0; }
        
        print "[*] Disabling $current_ws...\n";
        my $svc = ($current_ws eq "apache") ? "apache2" : "nginx";
        system("systemctl stop $svc");
        system("systemctl disable $svc");
    }
    return $target;
}

sub select_firewall_interactive {
    print "\n--------------------------------------------------\n";
    print "  Select Firewall Strategy\n";
    print "--------------------------------------------------\n";
    print "  [1] UFW (Simple)\n";
    print "  [2] NFTables (Pro/Atomic)\n";
    print "  [3] None (Do NOT touch firewall settings)\n";
    print "--------------------------------------------------\n";
    print "  Selection: ";
    my $sel = prompt_menu_selection(3);
    
    if ($sel eq "3") { return ("none", []); }
    my $type = ($sel eq "1") ? "ufw" : "nftables";
    
    print "\n  [SSH Access Control]\n";
    print "  [1] Open to World (Any IP)\n";
    print "  [2] Restrict to Specific IPs / Networks\n";
    print "  Selection: ";
    my $acc = prompt_menu_selection(2);
    
    my @ips = ();
    if ($acc eq "2") {
        print "\n  Enter permitted IPs (comma separated).\n";
        print "  Examples: 192.168.1.50, 10.0.0.0/24\n";
        print "  Allowed: ";
        
        while (1) {
            my $input = <STDIN>; chomp($input);
            my @candidates = split(/[\s,]+/, $input);
            my ($ok, $err) = validate_ip_list(@candidates);
            if ($ok) { @ips = @candidates; last; }
            print "  [!] Error: $err\n  Try again: ";
        }
    }
    return ($type, \@ips);
}

sub manage_ssl_interactive {
    my $s = shift;
    system("clear");
    print "========================================================\n";
    print "  SSL CERTIFICATE MANAGER\n";
    print "========================================================\n\n";
    
    my $le_status = "Not Found";
    if (-d "/etc/letsencrypt/live/$s->{domain}") { $le_status = "Active (Let's Encrypt)"; }
    
    print "  Current Status: $le_status\n";
    print "  Domain: $s->{domain}\n\n";
    
    print "  OPTIONS:\n";
    print "  [1] Install Production Cert (Standard)\n";
    print "  [2] Revoke & Delete Certificate (The Nuclear Option)\n";
    print "  [3] Emergency Self-Signed Cert\n";
    print "  [4] Install Staging Cert (Unlimited Testing / Untrusted)\n";
    print "--------------------------------------------------------\n";
    print "  [0] Back\n";
    print "  Select: ";
    
    my $c = prompt_menu_selection(4);
    
    if ($c eq "1") {
        my %dummy_conf = (
            domain => $s->{domain},
            canonical => $s->{domain}, # FIXED: Was missing, caused crash
            webserver => lc($s->{webserver}),
            email => get_smart_ssl_email("Enter Email for Renewal"), # FIXED: Smart Email
            aliases => [] 
        );
        configure_ssl_auto(\%dummy_conf, { staging => 0, force => 1 });
	print "\nPress [Enter] to return..."; <STDIN>;
    }
    elsif ($c eq "2") {
        print "\n[!] WARNING: You are about to Revoke and DELETE the certificate.\n";
        print "    Type 'REVOKE' to confirm: ";
        my $k = <STDIN>; chomp($k);
        if ($k eq "REVOKE") {
            system("certbot revoke --cert-path /etc/letsencrypt/live/$s->{domain}/cert.pem");
            system("certbot delete --cert-name $s->{domain}");
            print "[*] Certificate Revoked and Deleted.\n"; sleep(2);
        }
    }
    elsif ($c eq "3") {
        print "\n[*] Generating Emergency Self-Signed Certificate...\n";
        my $dir = "/etc/ssl/certs";
        my $key = "/etc/ssl/private/ssl-cert-snakeoil.key";
        my $crt = "/etc/ssl/certs/ssl-cert-snakeoil.pem";
        system("openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $key -out $crt -subj '/CN=$s->{domain}'");
        my $conf = ($s->{webserver} =~ /nginx/i) ? "/etc/nginx/sites-available/$s->{domain}" : "/etc/apache2/sites-available/$s->{domain}.conf";
        
        my $file_dom = $s->{domain};
        $file_dom =~ s/^www\.//;
        
        if ($s->{webserver} =~ /nginx/i) { $conf = "/etc/nginx/sites-available/$file_dom"; }
        else { $conf = "/etc/apache2/sites-available/$file_dom.conf"; }
        
        if (-e $conf) {
            system("sed -i 's|ssl_certificate /etc/letsencrypt.*|ssl_certificate $crt;|g' $conf");
            system("sed -i 's|ssl_certificate_key /etc/letsencrypt.*|ssl_certificate_key $key;|g' $conf");
            system("sed -i 's|SSLCertificateFile .*|SSLCertificateFile $crt|g' $conf");
            system("sed -i 's|SSLCertificateKeyFile .*|SSLCertificateKeyFile $key|g' $conf");
            my $svc = ($s->{webserver} =~ /nginx/i) ? "nginx" : "apache2";
            system("systemctl restart $svc");
            print "[SUCCESS] Webserver should be back online (Self-Signed).\n";
        } else {
            print "[!] Could not find config file: $conf\n";
        }
        sleep(2);
    }
    elsif ($c eq "4") {
        print "\n[*] Activating Staging Mode (Unlimited Testing)...\n";
        my %dummy_conf = (
            domain => $s->{domain},
            canonical => $s->{domain}, # FIXED: Was missing, caused crash
            webserver => lc($s->{webserver}),
            email => get_smart_ssl_email("Enter Email for Renewal"), # FIXED: Smart Email
            aliases => [] 
        );
        configure_ssl_auto(\%dummy_conf, { staging => 1, force => 0 });
	print "\nPress [Enter] to return..."; <STDIN>;
    }
}

sub show_uninstall_submenu {
    my $s = shift;
    system("clear");
    print "========================================================\n";
    print "  CLEANUP / UNINSTALL OPERATIONS\n";
    print "========================================================\n";
    print "  [1] Remove Site & Configs Only (Safe)\n";
    print "  [2] Reset Firewall Only\n";
    print "  [3] TOTAL UNINSTALL (The Hammer)\n";
    print "--------------------------------------------------------\n";
    print "  [0] Back\n";
    print "  Select: ";
    
    my $c = prompt_menu_selection(3);
    if ($c eq "1") {
        print "\n[!] Removing PressMint Site Data. Binaries will remain.\n";
        print "Type 'CLEAN' to confirm: ";
        my $k = <STDIN>; chomp($k);
        if ($k eq "CLEAN") {
            remove_tree($WEB_ROOT); remove_tree($PMA_DIR);
            system("mysql -e \"DROP DATABASE IF EXISTS pressmint_db;\""); unlink($STATE_FILE);
            print "[SUCCESS] Site data removed.\n"; sleep(2);
        }
    }
    elsif ($c eq "2") {
        print "\n[*] Flushing Firewall Rules...\n";
        if (`systemctl is-active ufw` =~ /active/) { system("ufw disable"); system("ufw reset"); }
        if (`systemctl is-active nftables` =~ /active/) { system("nft flush ruleset"); }
        sleep(2);
    }
    elsif ($c eq "3") {
        print "\n[WARNING] TOTAL DESTRUCTION selected.\n";
        print "Type 'DESTROY' to confirm: ";
        my $conf = <STDIN>; chomp($conf);
        if ($conf eq "DESTROY") {
            print "[*] Nuking...\n";
            system("userdel -r $SYS_USER >/dev/null 2>&1");
            system("systemctl stop nginx apache2 mariadb php*-fpm");
            system("apt-get purge -y nginx* apache2* php* mariadb-server*");
            system("apt-get autoremove -y");
            remove_tree($APP_DIR); unlink($STATE_FILE);
            print "[*] Clean.\n"; sleep(2);
        }
    }
}

sub scan_system_status {
    my %s;
    $s{webserver} = "None";
    my $ngx = get_service_state("nginx");
    my $apc = get_service_state("apache2");
    
    if ($ngx eq "running") { $s{webserver} = "Nginx"; }
    elsif ($apc eq "running") { $s{webserver} = "Apache"; }
    elsif ($ngx ne "missing") { $s{webserver} = "Nginx (Stopped)"; }
    elsif ($apc ne "missing") { $s{webserver} = "Apache (Stopped)"; }

    $s{php} = `php -r 'echo PHP_VERSION;' 2>/dev/null` || "Not Found"; chomp($s{php});
    if ($s{php} =~ /^(\d+\.\d+)/) { $s{php} = $1; }

    $s{ssl} = "Unknown";
    if (-e "/etc/letsencrypt/live") { $s{ssl} = "Let's Encrypt"; }
    
    if (-e $STATE_FILE) { 
        open(my $fh, '<', $STATE_FILE); 
        while(<$fh>){ if(/^DOMAIN=(.*)/){$s{domain}=$1} } 
    }
    return %s;
}

sub check_root { if ($> != 0) { die "[Error] Must run as root.\n"; } }
sub prompt_user { print $_[0]; my $v=<STDIN>; chomp($v); return $v; }
sub prompt_with_default { my($p,$d)=@_; print "$p [Default: $d]: "; my $v=<STDIN>; chomp($v); return $v eq "" ? $d : $v; }

sub prompt_safe {
    my ($label, $default, $regex, $human_rule, $max_len) = @_;
    while (1) {
        print "  $label [Default: $default]: ";
        my $in = <STDIN>; chomp($in);
        $in = ($in eq "") ? $default : $in;
        if (length($in) > $max_len) {
            print "  [!] Error: Too long (Max $max_len chars).\n"; next;
        }
        if ($in =~ /'/) {
            print "  [!] FATAL: Single quotes (') are strictly forbidden.\n"; next;
        }
        if ($in !~ /$regex/) {
            print "  [!] Error: Invalid characters.\n";
            print "  Allowed: $human_rule\n";
            next;
        }
        return $in;
    }
}

sub prompt_email {
    my $label = shift;
    while(1) {
        print "  $label: ";
        my $in = <STDIN>; chomp($in);
        if ($in =~ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/) {
            return $in;
        }
        print "  [!] Error: That doesn't look a valid email address.\n";
    }
}

sub prompt_menu_selection {
    my ($max, $def) = @_;
    while (1) {
        my $sel = <STDIN>; chomp($sel);
        if ($sel eq "" && defined $def) { return $def; }
        if ($sel eq "0") { return "0"; } 
        if ($sel =~ /^\d+$/ && $sel >= 1 && $sel <= $max) {
            return $sel;
        }
        print "  [!] Invalid Selection. Choose 1-$max" . (defined $def ? " (or Enter for $def)" : "") . ": ";
    }
}

sub validate_ip_list {
    my @list = @_;
    if (!@list) { return (0, "List is empty."); }
    foreach my $item (@list) {
        if ($item =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) {
            if ($1>255 || $2>255 || $3>255 || $4>255) { return (0, "Invalid IP: $item"); }
            next;
        }
        if ($item =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/) {
            if ($1>255 || $2>255 || $3>255 || $4>255 || $5>32) { return (0, "Invalid CIDR: $item"); }
            next;
        }
        return (0, "Not a valid IP or Network: $item");
    }
    return (1, "OK");
}

sub random_pass { my $l=shift; my @c=('a'..'z','A'..'Z','0'..'9'); my $p=""; for(1..$l){$p.=$c[rand @c]} return $p; }
sub random_hex { my $l=shift; my @c=('0'..'9','a'..'f'); my $p=""; for(1..$l){$p.=$c[rand @c]} return $p; }
sub check_firewall_status { 
    if (`systemctl is-active ufw 2>/dev/null` =~ /active/) { return "UFW (Active)"; }
    if (`systemctl is-active nftables 2>/dev/null` =~ /active/) { return "NFTables (Active)"; }
    return "None / Unknown";
}
sub get_service_state {
    my $s=shift;
    my $a=`systemctl is-active $s 2>/dev/null`; chomp($a);
    return ($a eq "active") ? "running" : "missing";
}
sub check_os_compatibility {
    open(my $fh, '<', '/etc/os-release');
    while(<$fh>){ if(/^VERSION_ID="?([^"]+)"?/){ if($1 !~ /^(12|13)/){ 
        print "\n[!] Warning: Non-Debian 12/13 detected. Proceeding anyway.\n";
    }}}
}
sub select_php_interactive {
    print "\n[*] Finding PHP versions...\n";
    my @v = `apt-cache pkgnames | grep -E '^php[0-9]+\\.[0-9]+\$' | sort -V`;
    my @c; foreach(@v){ if(/php(\d+\.\d+)/){ push(@c,$1); } }
    my %s; @c = grep{!$s{$_}++} @c; 
    my $l = $c[-1] || "8.2";
    print "-------------------------------------------------------\n";
    print "  Available PHP Versions:\n";
    my $i = 1;
    my $def_idx = 1;
    foreach my $ver (@c) {
        my $tag = ($ver eq $l) ? " (*)" : "";
        print "  [$i] PHP $ver $tag\n";
        if ($ver eq $l) { $def_idx = $i; }
        $i++;
    }
    print "-------------------------------------------------------\n";
    print "  Selection [Default: $l]: ";
    my $sel = prompt_menu_selection(scalar(@c), $def_idx);
    my $val = $c[$sel-1];
    if ($val =~ /(\d+\.\d+)/) { return $1; }
    return $val;
}

sub select_wp_version_interactive {
    print "\n  [WordPress Core Version]\n";
    print "  Fetching available versions from WordPress.org...\n";
    
    my $json = `curl -s https://api.wordpress.org/core/version-check/1.7/`;
    my @versions = ();
    while ($json =~ /"version":"([0-9\.]+)"/g) {
        push(@versions, $1);
    }
    my %seen;
    my @unique = grep { !$seen{$_}++ } @versions;
    @unique = sort { version_cmp($b, $a) } @unique;
    @unique = splice(@unique, 0, 10);
    
    print "--------------------------------------------------\n";
    print "  [1] Latest (Recommended)\n";
    my $i = 2;
    foreach my $v (@unique) { print "  [$i] WordPress $v\n"; $i++; }
    print "  [0] Manual Input (Type a custom version)\n";
    print "--------------------------------------------------\n";
    print "  Selection [Default: 1]: ";
    
    my $sel = prompt_menu_selection($i - 1, 1);
    if ($sel eq "1") { return "latest"; }
    if ($sel eq "0") { return prompt_user("  Enter Version (e.g. 5.9): "); }
    return $unique[$sel - 2];
}

sub version_cmp {
    my ($a, $b) = @_;
    my @a_parts = split(/\./, $a);
    my @b_parts = split(/\./, $b);
    for (my $i = 0; $i < scalar(@a_parts); $i++) {
        return 1 if ($a_parts[$i] > ($b_parts[$i] // 0));
        return -1 if ($a_parts[$i] < ($b_parts[$i] // 0));
    }
    return 0;
}

sub manage_php_ops {
    my $s = shift;
    
    # GUARD CLAUSE: Prevent crash if stack is not installed
    if ($s->{php} eq "Not Found" || !$s->{php}) {
        print "\n[!] Error: PHP is not detected on this system.\n";
        print "    You must install the stack (Option 1) before tuning it.\n";
        print "    Press [Enter] to return...";
        <STDIN>;
        return;
    }

    system("clear");
    print "========================================================\n";
    print "  PHP OPERATIONS & TUNING\n";
    print "========================================================\n\n";
    print "  Current Active: $s->{php}\n";
    print "  Web Server:     $s->{webserver}\n";
    print "  System Domain:  $s->{domain}\n\n"; 
    
    print "  OPTIONS:\n";
    print "  [1] SWITCH / UPGRADE PHP VERSION\n";
    print "  [2] TUNE PERFORMANCE SETTINGS\n";
    print "--------------------------------------------------------\n";
    print "  [0] Back\n";
    print "  Select: ";

    my $c = prompt_menu_selection(2);
    
    if ($c eq "1") {
        my $new_ver = select_php_interactive();
        if ($new_ver eq $s->{php}) {
            print "\n[!] REPAIR MODE DETECTED.\n";
            print "    You selected the current version. This will re-run the\n";
            print "    installation and configuration steps to fix missing files.\n";
            print "    Press [Enter] to Repair or Ctrl+C to abort...";
            <STDIN>;
        }
        
        # 1. Detect extensions
        print "\n[*] Scanning extensions on current PHP $s->{php}...\n";
        my @inst = `dpkg -l | grep php$s->{php}- | awk '{print \$2}'`;
        my @suffixes = ();
        foreach my $pkg (@inst) {
            chomp($pkg);
            if ($pkg =~ /php$s->{php}-(.*)/) { push(@suffixes, $1); }
        }
        
        # 2. Build install list (Validating availability)
        my @to_install = ("php$new_ver-fpm", "php$new_ver-cli");
        
        # Force "Must Haves" to the list
        push(@suffixes, "opcache", "readline", "xml", "curl", "mbstring", "mysql", "imagick");
        
        # Deduplicate
        my %seen;
        @suffixes = grep { !$seen{$_}++ } @suffixes;

        print "[*] Verifying package availability for PHP $new_ver...\n";
        foreach my $suf (@suffixes) {
            next if ($suf eq "fpm" || $suf eq "cli" || $suf eq "common");
            my $candidate = "php$new_ver-$suf";
            # Check if repo has it
            my $exists = system("apt-cache show $candidate >/dev/null 2>&1");
            if ($exists == 0) { push(@to_install, $candidate); }
        }
        
        print "[*] Installing PHP $new_ver environment...\n";
        system("apt-get update >/dev/null");
        if (system("apt-get -y install " . join(" ", @to_install)) != 0) {
            print "\n[!] CRITICAL: Apt-get Install Failed.\n";
            print "Press [Enter] to return (No changes made)..."; 
            <STDIN>; return;
        }

        # --- FPM IDENTITY RE-APPLY ---
        configure_fpm_identity($new_ver);

        # --- SAFETY CHECK ---
        print "[*] Verifying service integrity...\n";
        my $svc_check = system("systemctl list-unit-files php$new_ver-fpm.service >/dev/null 2>&1");
        
        if ($svc_check != 0) {
            print "\n[!!!!] CRITICAL SAFETY ABORT [!!!!]\n";
            print "The package manager claimed success, but the FPM Service\n";
            print "(php$new_ver-fpm.service) was NOT found on the system.\n";
            print "\nWe have ABORTED the switch to protect your site uptime.\n";
            print "Press [Enter] to return..."; 
            <STDIN>; return;
        }

        # 3. Port Settings
        print "[*] Porting performance settings...\n";
        my $old_ini = "/etc/php/$s->{php}/fpm/php.ini";
        my $new_ini = "/etc/php/$new_ver/fpm/php.ini";
        
        if (-e $old_ini && -e $new_ini) {
            my $mem = `grep '^memory_limit' $old_ini`; chomp($mem);
            my $post = `grep '^post_max_size' $old_ini`; chomp($post);
            my $time = `grep '^max_execution_time' $old_ini`; chomp($time);
            
            if ($mem =~ /= (.*)/) { system("sed -i 's/^memory_limit.*/memory_limit = $1/' $new_ini"); }
            if ($post =~ /= (.*)/) { 
                system("sed -i 's/^post_max_size.*/post_max_size = $1/' $new_ini");
                system("sed -i 's/^upload_max_filesize.*/upload_max_filesize = $1/' $new_ini");
            }
            if ($time =~ /= (.*)/) { system("sed -i 's/^max_execution_time.*/max_execution_time = $1/' $new_ini"); }
        }

        # 4. Switch Webserver Socket
        print "[*] Updating Webserver Config...\n";
        
        my $file_dom = $s->{domain};
        $file_dom =~ s/^www\.//;

        my @targets = ();
        if ($s->{webserver} eq "Nginx") {
            push(@targets, "/etc/nginx/sites-available/$file_dom");
        } else {
            push(@targets, "/etc/apache2/sites-available/$file_dom.conf");
            if (-e "/etc/apache2/sites-available/$file_dom-le-ssl.conf") {
                push(@targets, "/etc/apache2/sites-available/$file_dom-le-ssl.conf");
            }
        }
        
        my $found_any = 0;
        foreach my $file (@targets) {
            print "    [DEBUG] Checking for file: $file ... ";
            if (-e $file) {
                print "FOUND. Updating.\n";
                system("sed -i 's|/run/php/php.*-fpm.sock|/run/php/php$new_ver-fpm.sock|g' $file");
                $found_any = 1;
            } else {
                print "MISSING.\n";
            }
        }

        if (!$found_any) {
             print "\n[!] Error: Could not find ANY configuration files.\n";
             print "    Tried: " . join(", ", @targets) . "\n";
             print "Press [Enter] to return..."; 
             <STDIN>; return;
        }

        # Restart Services
        system("systemctl restart php$new_ver-fpm");
        if ($s->{webserver} eq "Nginx") {
            system("systemctl reload nginx");
        } else {
            system("a2dismod php$s->{php} >/dev/null 2>&1");
            system("systemctl restart apache2");
        }
        
        # 5. Sync CLI
        print "[*] Synchronizing System CLI to PHP $new_ver...\n";
        system("update-alternatives --set php /usr/bin/php$new_ver >/dev/null 2>&1");

        # Update State File
        my @lines = ();
        open(my $fh_in, '<', $STATE_FILE); while(<$fh_in>){ push(@lines,$_); } close $fh_in;
        open(my $fh_out, '>', $STATE_FILE);
        foreach (@lines) {
            if (/^PHP=/) { print $fh_out "PHP=$new_ver\n"; } else { print $fh_out $_; }
        }
        close $fh_out;

        print "\n[SUCCESS] Switched to PHP $new_ver. CLI & Web are now synced.\n";
        print "Press [Enter] to return to the dashboard...";
        <STDIN>; 
    }
    elsif ($c eq "2") {
        # Tune Logic
        print "\n  [Tuning Active PHP: $s->{php}]\n";
        my $curr_mem = `grep '^memory_limit' /etc/php/$s->{php}/fpm/php.ini | awk '{print \$3}'`; chomp($curr_mem);
        my $curr_time = `grep '^max_execution_time' /etc/php/$s->{php}/fpm/php.ini | awk '{print \$3}'`; chomp($curr_time);
        
        print "  Current Memory Limit: $curr_mem\n";
        print "  Current Max Exec:     $curr_time s\n\n";
        
        print "  Enter new Memory Limit (e.g. 512M) or press Enter to keep:\n";
        my $new_mem = prompt_user("  Memory [$curr_mem]: ");
        
        print "  Enter new Max Exec Time (e.g. 300) or press Enter to keep:\n";
        my $new_time = prompt_user("  Seconds [$curr_time]: ");
        
        if ($new_mem || $new_time) {
            tune_php_ini($s->{php}, $new_mem || $curr_mem, "100M", $new_time || $curr_time);
            print "[SUCCESS] Settings updated and FPM restarted.\n";
            print "Press [Enter] to return..."; <STDIN>;
        }
    }
}

# --- SMART EMAIL HELPER ---
sub get_smart_ssl_email {
    my $def_label = shift || "Admin Email";
    
    # 1. Scan for existing accounts
    my @candidates = ();
    if (-d "/etc/letsencrypt/accounts") {
        # Grep recursively for mailto: tags in JSON configs
        my @lines = `grep -r "mailto:" /etc/letsencrypt/accounts 2>/dev/null`;
        foreach my $line (@lines) {
            if ($line =~ /mailto:([^"\\]+)/) {
                push(@candidates, $1);
            }
        }
    }
    
    # Deduplicate
    my %seen;
    my @unique = grep { !$seen{$_}++ } @candidates;
    
    # 2. If none found, fall back to standard prompt
    if (scalar(@unique) == 0) {
        return prompt_email($def_label);
    }
    
    # 3. Present Menu
    print "\n  [Smart Email Detection]\n";
    print "  We found existing Let's Encrypt accounts on this server:\n";
    my $i = 1;
    foreach my $em (@unique) {
        print "  [$i] $em\n";
        $i++;
    }
    print "  ------------------------------------------------\n";
    print "  [0] Enter a New Email Address\n";
    print "  ------------------------------------------------\n";
    print "  Selection [Default: 1]: ";
    
    my $sel = prompt_menu_selection(scalar(@unique), 1);
    
    if ($sel eq "0") {
        return prompt_email("  Enter New Email");
    }
    
    return $unique[$sel - 1];
}
