//! Kurulum iş kuyruğu.
//!
//! Mimari:
//!   egui (ana iş parçacığı)
//!     └─► std::thread::spawn
//!           └─► tokio::runtime::Runtime::block_on
//!                 └─► JobQueue::run_all  ─► std::sync::mpsc::Sender<InstallMessage>
//!
//! egui her frame'de `Receiver::try_recv` ile kuyruktan mesaj okur.
//! `egui::Context::request_repaint()` arka plan iş parçacığından çağrılarak
//! UI'nin hemen güncellenmesi sağlanır.
//!
//! `mod.rs` yalnızca çekirdeği (mesaj, `UiSender`, `Job`, `JobQueue`), ortak
//! yardımcıları ve kuyruk fabrikalarını barındırır. Somut işler adım adlarıyla
//! ayrı modüllere dağılmıştır:
//!
//! | Modül            | İçerik |
//! |------------------|--------|
//! | `partition`      | Bölümleme, bağlama, fstab, LUKS/LVM |
//! | `execution`      | rsync kopyalama, bind-mount'lar |
//! | `bootloader`     | GRUB kurulumu + initramfs |
//! | `autologin`      | Otomatik oturum açma (sddm/lightdm/gdm) |
//! | `network`        | Hostname |
//! | `location`       | Locale, zaman dilimi, tarih/saat |
//! | `keyboard`       | Klavye düzeni |
//! | `users`          | Kullanıcı oluşturma |
//! | `netinstall`     | Ek paket grupları |
//! | `finish`         | COMAR, ldconfig, temizlik |

mod autologin;
mod bootloader;
mod execution;
mod finish;
mod keyboard;
mod location;
mod netinstall;
mod network;
mod partition;
mod users;

pub use autologin::ConfigureAutologinJob;
pub use bootloader::{GenerateInitramfsJob, InstallGrubJob};
pub use execution::{CopyFilesJob, MountBindJob, UnmountBindJob};
pub use finish::{CleanupLiveJob, RunComarJob, RunLdconfigJob, UpdateEnvironmentJob};
pub use keyboard::ConfigureKeyboardJob;
pub use location::{ConfigureDateTimeJob, ConfigureLocaleJob, ConfigureTimezoneJob};
pub use netinstall::InstallExtraPackagesJob;
pub use network::ConfigureHostnameJob;
pub use partition::{
    CustomPartitionJob, FstabFromBlkidJob, FstabFromCustomPartitionsJob, MountCustomPartitionsJob,
    MountPartitionsJob, PartitionJob, SetupLvmJob,
};
pub use users::CreateUserJob;

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::time::Instant;
use tokio::process::Command;
use rust_i18n::t;

use crate::installer::PartitionPlan;

// ─────────────────────────────────────────────
// MESAJ TİPİ
// ─────────────────────────────────────────────

/// Arka plan iş parçacığından UI'ye gönderilen mesajlar.
#[derive(Debug)]
pub enum InstallMessage {
    /// Bir log satırı.
    Log(String),
    /// Genel ilerleme; 0.0–1.0 arasında.
    Progress(f32),
    /// Alt ilerleme (ör: rsync içindeki dosya kopyalama); 0.0–1.0 arasında.
    SubProgress(f32),
    /// Kopyalanmakta olan dosya adı.
    CurrentFile(String),
    /// rsync kopyalama fazı başladı. UI yalnızca bu fazda tek ilerleme
    /// çubuğunu gösterir (eski YALI düzeni).
    CopyStarted,
    /// rsync kopyalama fazı bitti. UI ilerleme çubuğunu gizler.
    CopyFinished,
    /// Tüm işler başarıyla tamamlandı.
    Done,
    /// Bir iş başarısız oldu; kurulum durdu.
    Error(String),
}

/// `mpsc::Sender<InstallMessage>` ile `egui::Context` birlikte taşır.
/// Mesaj gönderiminde `request_repaint()` çağrısı UI'yi uyandırır,
/// ancak en fazla saniyede ~30 kez (33ms aralıkla) olacak şekilde
/// throttle edilir. Bu, özellikle rsync gibi yoğun I/O işlemleri
/// sırasında Intel tümleşik GPU'lu (UMA) sistemlerde GPU belleği
/// rekabetini önler ve karakter bozulmasını engeller.
pub struct UiSender {
    tx: mpsc::Sender<InstallMessage>,
    ctx: eframe::egui::Context,
    last_repaint: std::sync::Arc<std::sync::Mutex<Instant>>,
}

impl Clone for UiSender {
    fn clone(&self) -> Self {
        Self {
            tx: self.tx.clone(),
            ctx: self.ctx.clone(),
            last_repaint: self.last_repaint.clone(),
        }
    }
}

impl UiSender {
    pub fn new(tx: mpsc::Sender<InstallMessage>, ctx: eframe::egui::Context) -> Self {
        Self {
            tx,
            ctx,
            last_repaint: std::sync::Arc::new(std::sync::Mutex::new(Instant::now())),
        }
    }

    pub fn send(&self, msg: InstallMessage) {
        let _ = self.tx.send(msg);
        let mut last = self.last_repaint.lock().unwrap();
        if last.elapsed() >= std::time::Duration::from_millis(33) {
            self.ctx.request_repaint();
            *last = Instant::now();
        }
    }

    pub fn log(&self, s: impl Into<String>) {
        self.send(InstallMessage::Log(s.into()));
    }

    pub fn sub_progress(&self, p: f32) {
        self.send(InstallMessage::SubProgress(p));
    }

    pub fn copy_started(&self) {
        self.send(InstallMessage::CopyStarted);
    }

    pub fn copy_finished(&self) {
        self.send(InstallMessage::CopyFinished);
    }

    pub fn current_file(&self, f: impl Into<String>) {
        self.send(InstallMessage::CurrentFile(f.into()));
    }
}

// ─────────────────────────────────────────────
// JOB TRAIT
// ─────────────────────────────────────────────

#[async_trait::async_trait]
pub trait Job: Send + Sync {
    fn name(&self) -> &str;
    /// İşi asenkron olarak çalıştırır.
    /// Hata durumunda `Err(açıklama)` döner; kuyruk durur.
    async fn run(&self, ui: &UiSender) -> Result<(), String>;
}

// ─────────────────────────────────────────────
// JOB QUEUE
// ─────────────────────────────────────────────

pub struct JobQueue {
    jobs: Vec<Box<dyn Job>>,
}

impl JobQueue {
    pub fn new(jobs: Vec<Box<dyn Job>>) -> Self {
        Self { jobs }
    }

    /// Tüm işleri sırayla çalıştırır.
    /// İlerleme ve loglar `ui` üzerinden UI'ye iletilir.
    pub async fn run_all(&mut self, ui: UiSender) {
        let total = self.jobs.len();

        for (i, job) in self.jobs.iter().enumerate() {
            let localized_name = t!(job.name());
            ui.log(format!("▶ {}", localized_name));

            match job.run(&ui).await {
                Ok(()) => {
                    ui.log(format!("✓ {}", localized_name));
                }
                Err(e) => {
                    let msg = format!("✗ {}: {}", localized_name, e);
                    ui.log(msg.clone());
                    ui.send(InstallMessage::Error(msg));
                    return;
                }
            }

            let progress = (i + 1) as f32 / total as f32;
            ui.send(InstallMessage::Progress(progress));
        }

        ui.send(InstallMessage::Done);
    }
}

// ─────────────────────────────────────────────
// ORTAK YARDIMCILAR (tüm job modüllerinin kullandığı)
// ─────────────────────────────────────────────

/// DEMO modu: gerçek disk işlemleri yerine kısa bekleme yapılır.
/// `build_full_job_queue` / `build_custom_job_queue` tarafından ayarlanır.
static DEMO_MODE: AtomicBool = AtomicBool::new(false);

/// Başarısızlık durumunda stderr'i hata mesajı olarak döner.
async fn run_cmd(program: &str, args: &[&str]) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(3000)).await;
        return Ok(());
    }

    let output = Command::new(program)
        .args(args)
        .output()
        .await
        .map_err(|e| format!("{} çalıştırılamadı: {}", program, e))?;

    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
        Err(if stderr.is_empty() { stdout } else { stderr })
    }
}

/// Standart `chroot` ile verilen komutu hedef dizinde çalıştırır.
/// Bind-mount'ların önceden yapılmış olduğu varsayılır (MountBindJob).
async fn run_chroot(mount: &str, cmd: &[&str]) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
        return Ok(());
    }

    // chroot <mount> <cmd[0]> [cmd[1..]...]
    let mut args = vec![mount];
    args.extend_from_slice(cmd);

    let output = Command::new("chroot")
        .args(&args)
        .output()
        .await
        .map_err(|e| format!("chroot çalıştırılamadı: {}", e))?;

    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
        Err(if stderr.is_empty() { stdout } else { stderr })
    }
}

/// Disk adı ve bölüm numarasından tam aygıt yolunu oluşturur.
///
/// - SATA/SCSI: `/dev/sda`  + 1 → `/dev/sda1`
/// - NVMe:      `/dev/nvme0n1` + 1 → `/dev/nvme0n1p1`
/// - MMC:       `/dev/mmcblk0` + 1 → `/dev/mmcblk0p1`
fn part_path(disk: &str, num: u32) -> String {
    crate::installer::part_path(disk, num)
}

/// `/proc/mounts` ile bir yolun bağlama noktası olup olmadığını kontrol eder.
fn is_mount_point(path: &std::path::Path) -> bool {
    if let Ok(mounts) = std::fs::read_to_string("/proc/mounts") {
        let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
        let canon_str = canon.to_string_lossy();
        mounts.lines().any(|line| {
            let parts: Vec<&str> = line.split_whitespace().collect();
            parts.len() >= 2 && parts[1] == canon_str.as_ref()
        })
    } else {
        false
    }
}

/// `blkid` ile bir bölümün dosya sistemi türünü döndürür.
async fn get_fs_type(device: &str) -> Result<String, String> {
    let output = tokio::process::Command::new("blkid")
        .args(["-s", "TYPE", "-o", "value", device])
        .output()
        .await
        .map_err(|e| format!("blkid çalıştırılamadı: {}", e))?;
    let fs = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if fs.is_empty() {
        Err(format!("{} dosya sistemi türü bulunamadı", device))
    } else {
        Ok(fs)
    }
}

/// Bir bölümün UUID'sini `blkid` ile okur (3 deneme, sonra `lsblk` yedeği).
async fn blkid_uuid(device: &str) -> Result<String, String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
        return Ok(format!("demo-uuid-{}", device.replace("/", "-")));
    }

    // Aygıt düğümünün var olmasını bekle (maks 5 sn)
    for i in 0..5 {
        if tokio::fs::metadata(device).await.is_ok() {
            break;
        }
        if i == 4 {
            return Err(format!("{} aygıtı bulunamadı", device));
        }
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    }

    // blkid ile UUID oku (3 deneme)
    for _ in 0..3 {
        let output = tokio::process::Command::new("blkid")
            .args(["-s", "UUID", "-o", "value", device])
            .output()
            .await
            .map_err(|e| format!("blkid çalıştırılamadı: {}", e))?;

        let uuid = String::from_utf8_lossy(&output.stdout).trim().to_string();
        if !uuid.is_empty() && output.status.success() {
            return Ok(uuid);
        }
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    }

    // Son çare: lsblk ile dene
    if let Ok(output) = tokio::process::Command::new("lsblk")
        .args(["-no", "UUID", device])
        .output()
        .await
    {
        let uuid = String::from_utf8_lossy(&output.stdout).trim().to_string();
        if !uuid.is_empty() && output.status.success() {
            return Ok(uuid);
        }
    }

    Err(format!("{} için UUID bulunamadı", device))
}

// ─────────────────────────────────────────────
// KAYNAK DİZİNİ TESPİTİ
// ─────────────────────────────────────────────

/// ISO ortamında canlı sistemin kaynak dizinini otomatik tespit eder.
///
/// Sırasıyla:
///   1. `/proc/mounts` içinde squashfs/overlay mount noktalarını tara
///   2. Sabit aday listesini dene (her adayda bin/ + etc/ varlığı aranır)
///   3. Hiçbiri bulunamazsa `/` (canlı sistem kökü) kullan — rsync
///      zaten /proc /sys /dev /run'u exclude eder.
fn has_root_fs(path: &std::path::Path) -> bool {
    (path.join("bin").exists() || path.join("usr/bin").exists()) && path.join("etc").exists()
}

fn is_iso_mount(path: &str) -> bool {
    let sqfs_check = [
        "pisi/pisi.sqfs",
        "live/pisi.sqfs",
        "live/filesystem.squashfs",
        "casper/filesystem.squashfs",
        "live squashfs",
    ];
    for pattern in &sqfs_check {
        if std::path::Path::new(path).join(pattern).exists() {
            return true;
        }
    }
    false
}

pub fn detect_source_dir() -> String {
    // 1. /proc/mounts içinde squashfs veya overlay mount noktalarını tara
    if let Ok(mounts) = std::fs::read_to_string("/proc/mounts") {
        for line in mounts.lines() {
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.len() < 3 { continue; }
            let fs_type   = parts[2];
            let mount_pt  = parts[1];

            match fs_type {
                "squashfs" => {
                    if mount_pt != "/" {
                        let p = std::path::Path::new(mount_pt);
                        if has_root_fs(p) {
                            return format!("{}/", mount_pt.trim_end_matches('/'));
                        }
                    }
                }
                "overlay" => {
                    // lowerdir parametresini ayıkla
                    let opts = parts.get(3).unwrap_or(&"");
                    for opt in opts.split(',') {
                        if let Some(lower) = opt.strip_prefix("lowerdir=") {
                            for dir in lower.split(':') {
                                let p = std::path::Path::new(dir);
                                if p.exists() && has_root_fs(p) {
                                    return format!("{}/", dir.trim_end_matches('/'));
                                }
                            }
                        }
                    }
                }
                _ => {}
            }
        }
    }

    // 2. Sabit aday listesi (her adayda bin/ + etc/ kontrolü)
    //    NOT: /bootmnt ISO kökü olabilir; altında pisi.sqfs varsa atlanır.
    for path in &[
        "/run/livecd/squashfs-root",
        "/run/live/rootfs",
        "/run/live/medium",
        "/lib/live/mount/rootfs",
        "/mnt/sqfs",
        "/mnt/livecd",
        "/mnt/cdrom",
        "/bootmnt/live",
        "/bootmnt/livecd",
    ] {
        let p = std::path::Path::new(path);
        if p.exists() && has_root_fs(p) {
            return format!("{}/", path);
        }
    }

    // 2b. /bootmnt son çare; ama ISO köküyse (pisi.sqfs varsa) atla
    let bootmnt = std::path::Path::new("/bootmnt");
    if bootmnt.exists() && !is_iso_mount("/bootmnt") && has_root_fs(bootmnt) {
        return "/bootmnt/".to_string();
    }

    // 3. Hiçbiri bulunamazsa canlı sistem kökü
    "/".to_string()
}

// ─────────────────────────────────────────────
// KUYRUK YAPILANDIRMASI
// ─────────────────────────────────────────────

/// Kurulum kuyruğu için ortak yapılandırma.
pub struct JobQueueConfig {
    pub mount:               String,
    pub source:              String,
    pub language:            String,
    pub locale:              String,
    pub timezone:            String,
    pub username:            String,
    pub password:            String,
    pub hostname:            String,
    pub kb_layout:           String,
    pub kb_variant:          String,
    pub demo_mode:           bool,
    pub boot_device:         String,
    pub bootloader_timeout:  u32,
    pub bootloader_password: String,
    pub kernel_options:      String,
    pub use_ntp:             bool,
    pub manual_year:         i32,
    pub manual_month:        u32,
    pub manual_day:          u32,
    pub manual_hour:         u32,
    pub manual_minute:       u32,
    pub root_password:       String,
    pub display_manager:     String,
    pub autologin:           bool,
    pub desktop_environment: String,
    pub selected_package_groups: Vec<String>,
}

// ─────────────────────────────────────────────
// FABRİKA: STANDART KURULUM KUYRUĞU
// ─────────────────────────────────────────────

/// Tam kurulum iş kuyruğu.
///
/// Sıra:
///   1.  Disk bölümlendirme (parted + mkfs)
///   2.  Bölümleri bağlama
///   3.  /proc /sys /dev bind-mount (chroot hazırlığı)
///   4.  Sistem dosyalarını kopyalama (rsync)
///   5.  UUID'leri oku → fstab yaz
///   6.  Hostname + /etc/hosts
///   7.  Locale yapılandırması + locale.gen
///   8.  Zaman dilimi + hwclock
///   9.  Klavye (vconsole + X11)
///  10.  Kullanıcı oluşturma + şifre (chpasswd)
///  11.  initramfs yenile (mkinitramfs / dracut / mkinitcpio)
///  12.  GRUB kurulumu (chroot içinde)
///  13.  COMAR / pisi configure-pending
///  14.  ldconfig
///  15.  update-environment (PisiLinux init)
///  16.  Live ortam temizliği
///  17.  Bind-mount'ları temizle
pub fn build_full_job_queue(
    plan: PartitionPlan,
    cfg:  JobQueueConfig,
) -> JobQueue {
    DEMO_MODE.store(cfg.demo_mode, Ordering::Relaxed);

    let is_uefi      = plan.efi_mb > 0;
    let encrypt_root = plan.encrypt_root;
    let disk         = plan.disk.clone();
    let mt           = cfg.mount.clone();
    let plan2        = plan.clone();
    let plan3        = plan.clone();

    // Kaynak dizini: parametre boşsa otomatik tespit et
    let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source };

    let mut jobs: Vec<Box<dyn Job>> = vec![
        // 1. Bölümlendir
        Box::new(PartitionJob { plan }),
        // 2. Bağla
        Box::new(MountPartitionsJob { plan: plan2, mount: mt.clone() }),
        // 3. /proc /sys /dev bind-mount
        Box::new(MountBindJob { mount: mt.clone() }),
        // 4. Kopyala
        Box::new(CopyFilesJob { source: src, target: mt.clone() }),
        // 5. UUID oku + fstab yaz
        Box::new(FstabFromBlkidJob { plan: plan3, mount: mt.clone() }),
        // 6. Hostname
        Box::new(ConfigureHostnameJob {
            mount:    mt.clone(),
            hostname: cfg.hostname.clone(),
        }),
        // 7. Locale
        Box::new(ConfigureLocaleJob {
            mount:    mt.clone(),
            locale:   cfg.locale.clone(),
        }),
        // 8. Zaman dilimi
        Box::new(ConfigureTimezoneJob {
            mount:    mt.clone(),
            timezone: cfg.timezone.clone(),
        }),
        // 9. Tarih/Saat (NTP)
        Box::new(ConfigureDateTimeJob {
            mount:  mt.clone(),
            use_ntp: cfg.use_ntp,
            year:   cfg.manual_year,
            month:  cfg.manual_month,
            day:    cfg.manual_day,
            hour:   cfg.manual_hour,
            minute: cfg.manual_minute,
        }),
        // 10. Klavye
        Box::new(ConfigureKeyboardJob {
            mount:    mt.clone(),
            layout:   cfg.kb_layout.clone(),
            variant:  cfg.kb_variant.clone(),
            language: cfg.language.clone(),
        }),
        // 11. Kullanıcı
        Box::new(CreateUserJob {
            mount:         mt.clone(),
            username:      cfg.username.clone(),
            password:      cfg.password.clone(),
            root_password: cfg.root_password.clone(),
        }),
        // 12. initramfs
        Box::new(GenerateInitramfsJob { mount: mt.clone(), encrypt_root, lvm_active: false }),
    ];

    // 13. Bootloader (GRUB)
    jobs.push(Box::new(InstallGrubJob {
        disk: disk.clone(),
        mount: mt.clone(),
        is_uefi,
        boot_device: cfg.boot_device.clone(),
        timeout: cfg.bootloader_timeout,
        password: cfg.bootloader_password.clone(),
        kernel_options: cfg.kernel_options.clone(),
        encrypt_root,
    }));

    // 14. Otomatik oturum açma (yalnızca seçildiyse)
    jobs.push(Box::new(ConfigureAutologinJob {
        mount: mt.clone(),
        display_manager: cfg.display_manager.clone(),
        autologin: cfg.autologin,
        username: cfg.username.clone(),
        desktop_environment: cfg.desktop_environment.clone(),
    }));

    // 15. COMAR / pisi configure-pending
    jobs.push(Box::new(RunComarJob { mount: mt.clone() }));

    // 16. ldconfig
    jobs.push(Box::new(RunLdconfigJob { mount: mt.clone() }));

    // 17. update-environment (PisiLinux init yapılandırması)
    jobs.push(Box::new(UpdateEnvironmentJob { mount: mt.clone() }));

    // 18. Ek paket grupları (netinstall)
    if !cfg.selected_package_groups.is_empty() {
        jobs.push(Box::new(InstallExtraPackagesJob {
            mount: mt.clone(),
            package_groups: cfg.selected_package_groups.clone(),
        }));
    }

    // 19. Live ortam izlerini temizle
    jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() }));

    // 20. Bind-mount temizle
    jobs.push(Box::new(UnmountBindJob { mount: mt.clone() }));

    JobQueue::new(jobs)
}

/// Manuel bölümleme kullanıldığında derlenecek kurulum iş kuyruğu.
pub fn build_custom_job_queue(
    disk:              String,
    custom_partitions: Vec<crate::installer::CustomPartition>,
    volume_groups:     Vec<crate::installer::VolumeGroup>,
    logical_volumes:   Vec<crate::installer::LogicalVolume>,
    cfg:               JobQueueConfig,
) -> JobQueue {
    DEMO_MODE.store(cfg.demo_mode, Ordering::Relaxed);

    let is_uefi      = std::path::Path::new("/sys/firmware/efi").exists();
    let encrypt_root = custom_partitions.iter().any(|p| p.mountpoint == "/" && p.encrypt);
    let mt           = cfg.mount.clone();

    let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source };

    let mut jobs: Vec<Box<dyn Job>> = vec![
        Box::new(CustomPartitionJob { disk: disk.clone(), custom_partitions: custom_partitions.clone() }),
        Box::new(SetupLvmJob { volume_groups: volume_groups.clone(), logical_volumes: logical_volumes.clone() }),
        Box::new(MountCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }),
        Box::new(MountBindJob { mount: mt.clone() }),
        Box::new(CopyFilesJob { source: src, target: mt.clone() }),
        Box::new(FstabFromCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }),
        Box::new(ConfigureHostnameJob { mount: mt.clone(), hostname: cfg.hostname.clone() }),
        Box::new(ConfigureLocaleJob { mount: mt.clone(), locale: cfg.locale.clone() }),
        Box::new(ConfigureTimezoneJob { mount: mt.clone(), timezone: cfg.timezone.clone() }),
        Box::new(ConfigureDateTimeJob {
            mount:  mt.clone(),
            use_ntp: cfg.use_ntp,
            year:   cfg.manual_year,
            month:  cfg.manual_month,
            day:    cfg.manual_day,
            hour:   cfg.manual_hour,
            minute: cfg.manual_minute,
        }),
        Box::new(ConfigureKeyboardJob { mount: mt.clone(), layout: cfg.kb_layout.clone(), variant: cfg.kb_variant.clone(), language: cfg.language.clone() }),
        Box::new(CreateUserJob { mount: mt.clone(), username: cfg.username.clone(), password: cfg.password.clone(), root_password: cfg.root_password.clone() }),
        Box::new(GenerateInitramfsJob { mount: mt.clone(), encrypt_root, lvm_active: !volume_groups.is_empty() }),
    ];

    jobs.push(Box::new(InstallGrubJob {
        disk: disk.clone(),
        mount: mt.clone(),
        is_uefi,
        boot_device: cfg.boot_device.clone(),
        timeout: cfg.bootloader_timeout,
        password: cfg.bootloader_password.clone(),
        kernel_options: cfg.kernel_options.clone(),
        encrypt_root,
    }));

    jobs.push(Box::new(ConfigureAutologinJob {
        mount: mt.clone(),
        display_manager: cfg.display_manager.clone(),
        autologin: cfg.autologin,
        username: cfg.username.clone(),
        desktop_environment: cfg.desktop_environment.clone(),
    }));
    jobs.push(Box::new(RunComarJob { mount: mt.clone() }));
    jobs.push(Box::new(RunLdconfigJob { mount: mt.clone() }));
    jobs.push(Box::new(UpdateEnvironmentJob { mount: mt.clone() }));
    if !cfg.selected_package_groups.is_empty() {
        jobs.push(Box::new(InstallExtraPackagesJob {
            mount: mt.clone(),
            package_groups: cfg.selected_package_groups.clone(),
        }));
    }
    jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() }));
    jobs.push(Box::new(UnmountBindJob { mount: mt.clone() }));

    JobQueue::new(jobs)
}
