//! Self-contained in-window passphrase modal.
//!
//! Owns its struct, layout, and per-frame logic; the App only knows
//! the [`PassphraseModal`] type, calls [`render`], and acts on the
//! returned [`ModalAction`]. Keeps [`crate::main`] focused on
//! sidebar / dispatch / app-state and lets the modal evolve in one
//! file (text-edit fallback, virtual keyboard, injection-detection
//! warnings, new-vault confirm flow).

use eframe::egui;
use zeroize::{Zeroize, Zeroizing};

use crate::virtual_keyboard::{KeyPress, VirtualKeyboard};

/// In-window passphrase entry. Replaces the platform passphrase
/// dialog (PowerShell/zenity/osascript) for desktop GUI use.
pub struct PassphraseModal<P> {
    /// True when no `master.key` exists yet — modal renders the
    /// "set up your vault" copy and a confirm field.
    pub is_new_vault: bool,
    /// Actions to retry once the vault is unlocked. Generic so the
    /// App owns its action enum without leaking it into this file.
    /// A queue rather than a single slot so two near-simultaneous
    /// user actions both bounce back to NeedsPassphrase, queue here,
    /// and ALL retry after one passphrase entry.
    pub pending: Vec<P>,
    /// Live passphrase entry. Cleared on submit.
    pub passphrase: String,
    /// Confirm field (only used when [`Self::is_new_vault`] is true).
    pub confirm: String,
    /// Last unlock error to render in the modal, if any.
    pub error: Option<String>,
    /// True while a `WorkerCmd::UnlockVault` is in flight.
    pub submitting: bool,
    /// Snapshot of the injection-monitor counter taken when the
    /// modal opened. If the live count exceeds this on Submit, a
    /// `SendInput` injection happened during entry and we refuse
    /// the unlock attempt.
    pub injection_baseline: u64,
    /// Once true, stays true for the modal's lifetime. Modal shows
    /// a hard warning and disables Submit; user must Cancel and
    /// reopen the modal to clear it.
    pub injection_detected: bool,
    /// Per-modal randomized on-screen keypad. `Some` only when the
    /// loaded `SecurityConfig::effective_input_method()` is
    /// `VirtualKeyboard` — Hardened / Lockdown tiers, or X11 with
    /// auto-upgrade. Defeats `SendKeys` and forces an attacker to
    /// also screen-scrape + drive `mouse_event` (which the injection
    /// monitor catches).
    pub keypad: Option<VirtualKeyboard>,
}

impl<P> PassphraseModal<P> {
    pub fn new(
        is_new_vault: bool,
        initial: Option<P>,
        injection_baseline: u64,
        use_virtual_keyboard: bool,
    ) -> Self {
        Self {
            is_new_vault,
            pending: initial.map(|a| vec![a]).unwrap_or_default(),
            passphrase: String::new(),
            confirm: String::new(),
            error: None,
            submitting: false,
            injection_baseline,
            injection_detected: false,
            keypad: if use_virtual_keyboard {
                Some(VirtualKeyboard::new())
            } else {
                None
            },
        }
    }
}

impl<P> PassphraseModal<P> {
    /// Whether the form satisfies submission preconditions. Used to
    /// gate the submit button so the user can't fire a half-typed
    /// passphrase against the worker.
    pub fn can_submit(&self) -> bool {
        if self.submitting {
            return false;
        }
        if self.injection_detected {
            return false;
        }
        if self.passphrase.len() < 8 {
            return false;
        }
        if self.is_new_vault && self.passphrase != self.confirm {
            return false;
        }
        true
    }
}

impl<P> Drop for PassphraseModal<P> {
    fn drop(&mut self) {
        self.passphrase.zeroize();
        self.confirm.zeroize();
    }
}

/// What happened in the modal this frame.
pub enum ModalAction {
    /// User clicked Cancel — App should drop the modal and toast.
    Cancel,
    /// User clicked Submit — App should send `UnlockVault` with this
    /// passphrase. Modal stays open in `submitting=true` state until
    /// the worker replies.
    Submit(Zeroizing<String>),
    /// No state change this frame.
    None,
}

/// Render the modal. The two scalars are passed in so the modal
/// does not have to depend on `crate::InjectionMonitor` directly —
/// the App polls and supplies the values it sees this frame.
pub fn render<P>(
    ctx: &egui::Context,
    modal: &mut PassphraseModal<P>,
    live_injection_count: u64,
    monitor_supported: bool,
) -> ModalAction {
    if monitor_supported && live_injection_count > modal.injection_baseline {
        modal.injection_detected = true;
    }

    let screen = ctx.viewport_rect();
    egui::Area::new(egui::Id::new("passphrase_modal_dim"))
        .order(egui::Order::Foreground)
        .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
        .interactable(true)
        .show(ctx, |ui| {
            // Strong dim — at 220/255 the dashboard is barely visible behind
            // the dialog. Anything lighter let dashboard text bleed through.
            ui.painter()
                .rect_filled(screen, 0.0, egui::Color32::from_black_alpha(220));
            // Eat clicks so widgets behind the modal can't be activated.
            ui.allocate_rect(screen, egui::Sense::click_and_drag());
        });

    let mut action = ModalAction::None;

    egui::Area::new(egui::Id::new("passphrase_modal"))
        .order(egui::Order::Foreground)
        .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
        .show(ctx, |ui| {
            egui::Frame::new()
                .fill(ui.visuals().window_fill())
                .stroke(ui.visuals().window_stroke())
                .corner_radius(12.0)
                .inner_margin(28.0)
                .show(ui, |ui| {
                    ui.set_min_width(420.0);

                    if modal.is_new_vault {
                        ui.heading("Set up your vault");
                        ui.add_space(6.0);
                        ui.label(
                            egui::RichText::new(
                                "envseal will protect a master key with this passphrase \
                                 plus your device's hardware security module \
                                 (DPAPI on Windows, Secure Enclave on macOS, TPM 2.0 \
                                 on Linux). Choose something strong — there is no \
                                 password reset.",
                            )
                            .size(12.0)
                            .weak(),
                        );
                    } else {
                        ui.heading("Unlock vault");
                        ui.add_space(6.0);
                        ui.label(
                            egui::RichText::new("Enter your vault passphrase to continue.")
                                .size(12.0)
                                .weak(),
                        );
                    }

                    ui.add_space(14.0);
                    if let Some(keypad) = modal.keypad.as_mut() {
                        ui.label(
                            egui::RichText::new(
                                "Hardened tier — type with the on-screen keypad below.",
                            )
                            .size(12.0)
                            .strong(),
                        );
                        ui.add_space(4.0);
                        ui.label(format!(
                            "Passphrase: {} characters entered",
                            modal.passphrase.len()
                        ));
                        if modal.is_new_vault {
                            ui.label(format!(
                                "Confirm:    {} characters entered",
                                modal.confirm.len()
                            ));
                            if !modal.confirm.is_empty() && modal.passphrase != modal.confirm {
                                ui.colored_label(
                                    egui::Color32::from_rgb(239, 68, 68),
                                    "Passphrases do not match.",
                                );
                            }
                        }
                        ui.add_space(8.0);
                        let kp_press = keypad.render(ui);
                        if let Some(press) = kp_press {
                            match press {
                                KeyPress::Char(c) => modal.passphrase.push(c),
                                KeyPress::Backspace => {
                                    modal.passphrase.pop();
                                }
                            }
                        }
                        if modal.is_new_vault {
                            ui.add_space(6.0);
                            ui.horizontal(|ui| {
                                let can_copy =
                                    !modal.passphrase.is_empty() && modal.confirm.is_empty();
                                if ui
                                    .add_enabled(
                                        can_copy,
                                        egui::Button::new("Copy entry to Confirm field"),
                                    )
                                    .clicked()
                                {
                                    modal.confirm = std::mem::take(&mut modal.passphrase);
                                }
                                if !modal.confirm.is_empty() && ui.button("Reset confirm").clicked()
                                {
                                    modal.confirm.clear();
                                }
                            });
                        }
                    } else {
                        ui.label("Passphrase");
                        ui.add(
                            egui::TextEdit::singleline(&mut modal.passphrase)
                                .password(true)
                                .desired_width(f32::INFINITY)
                                .hint_text("at least 8 characters"),
                        );

                        if modal.is_new_vault {
                            ui.add_space(8.0);
                            ui.label("Confirm passphrase");
                            ui.add(
                                egui::TextEdit::singleline(&mut modal.confirm)
                                    .password(true)
                                    .desired_width(f32::INFINITY),
                            );
                            if !modal.confirm.is_empty() && modal.passphrase != modal.confirm {
                                ui.colored_label(
                                    egui::Color32::from_rgb(239, 68, 68),
                                    "Passphrases do not match.",
                                );
                            }
                        }
                    }

                    if let Some(err) = &modal.error {
                        ui.add_space(6.0);
                        ui.colored_label(egui::Color32::from_rgb(239, 68, 68), err);
                    }

                    if modal.injection_detected {
                        ui.add_space(8.0);
                        ui.colored_label(
                            egui::Color32::from_rgb(239, 68, 68),
                            "Synthetic input detected — a script appears to be \
                             driving this window. Submit is disabled. Cancel and \
                             try again, or unlock from the `envseal` CLI.",
                        );
                    } else if !monitor_supported {
                        ui.add_space(6.0);
                        ui.colored_label(
                            egui::Color32::from_rgb(245, 158, 11),
                            "Note: synthetic-input detection is not available on \
                             this platform. Use Hardened tier (virtual keyboard) \
                             for the strongest defense.",
                        );
                    }

                    ui.add_space(16.0);
                    ui.horizontal(|ui| {
                        if ui.button("Cancel").clicked() {
                            action = ModalAction::Cancel;
                        }
                        ui.add_space(8.0);
                        let can = modal.can_submit();
                        let label = if modal.submitting {
                            "Working…"
                        } else if modal.is_new_vault {
                            "Create vault"
                        } else {
                            "Unlock"
                        };
                        let btn = ui.add_enabled(
                            can,
                            egui::Button::new(label)
                                .min_size(egui::vec2(120.0, 28.0))
                                .corner_radius(6.0),
                        );
                        if btn.clicked() && can {
                            let trimmed_pass = modal
                                .passphrase
                                .trim_end_matches(&['\n', '\r'][..])
                                .to_string();
                            let p = Zeroizing::new(trimmed_pass);
                            modal.passphrase.zeroize();
                            modal.confirm.zeroize();
                            action = ModalAction::Submit(p);
                        }
                    });
                });
        });

    action
}
