//! The core application state for the eframe UI.
//!
//! This module defines the `VoiceApp` struct which implements `eframe::App`.
//! It listens to background events via `tokio::sync::watch` and draws the
//! classic TeamSpeak-style layout: channel tree on the left, text chat in the
//! centre, and a control bar at the bottom.

use eframe::egui;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::watch;

// ── Hardcoded channel list (Milestone 2 spec: "tree view of hardcoded channels") ──

/// A channel in the server channel tree.
struct Channel {
    name: &'static str,
    children: &'static [Channel],
}

/// Static channel tree for Milestone 2.
const CHANNEL_TREE: &[Channel] = &[
    Channel {
        name: "General",
        children: &[
            Channel {
                name: "Lobby",
                children: &[],
            },
            Channel {
                name: "Meeting Room",
                children: &[],
            },
        ],
    },
    Channel {
        name: "Gaming",
        children: &[
            Channel {
                name: "Competitive",
                children: &[],
            },
            Channel {
                name: "Casual",
                children: &[],
            },
        ],
    },
    Channel {
        name: "AFK",
        children: &[],
    },
];

// ── Colour palette ──

const BG_DARK: egui::Color32 = egui::Color32::from_rgb(30, 30, 46);
const PANEL_BG: egui::Color32 = egui::Color32::from_rgb(36, 36, 54);
const ACCENT: egui::Color32 = egui::Color32::from_rgb(98, 114, 248);
const ACCENT_DIM: egui::Color32 = egui::Color32::from_rgb(68, 78, 160);
const TEXT_PRIMARY: egui::Color32 = egui::Color32::from_rgb(205, 214, 244);
const TEXT_MUTED: egui::Color32 = egui::Color32::from_rgb(127, 132, 156);
const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 220, 130);
const RED: egui::Color32 = egui::Color32::from_rgb(235, 100, 100);
const YELLOW: egui::Color32 = egui::Color32::from_rgb(250, 200, 80);
const SEPARATOR: egui::Color32 = egui::Color32::from_rgb(55, 55, 75);

// ── Application state ──

/// A single chat message.
struct ChatMessage {
    author: String,
    body: String,
}

/// The central state for the eframe UI.
pub struct VoiceApp {
    // ── Cross-thread channels ──
    /// Receiver for the active speaker state, updated by the DSP thread.
    active_speaker_rx: watch::Receiver<bool>,
    /// Receiver for the current microphone RMS level (0.0–1.0).
    mic_level_rx: watch::Receiver<f32>,
    /// Shared flag to enable/disable the audio dumper.
    audio_dumper_flag: Arc<AtomicBool>,
    /// Shared mute flag (disables outgoing audio when true).
    mute_flag: Arc<AtomicBool>,
    /// Shared flag to enable/disable local audio loopback.
    loopback_flag: Arc<AtomicBool>,

    // ── Local UI state ──
    /// The currently selected channel name.
    selected_channel: String,
    /// Whether the user has deafened themselves.
    is_deafened: bool,
    /// Chat messages in the current channel.
    chat_messages: Vec<ChatMessage>,
    /// The current text input in the chat compose box.
    chat_input: String,
    /// Whether the developer settings panel is visible.
    show_dev_settings: bool,
}

impl VoiceApp {
    /// Creates a new `VoiceApp` instance.
    #[must_use]
    pub fn new(
        active_speaker_rx: watch::Receiver<bool>,
        mic_level_rx: watch::Receiver<f32>,
        audio_dumper_flag: Arc<AtomicBool>,
        mute_flag: Arc<AtomicBool>,
        loopback_flag: Arc<AtomicBool>,
    ) -> Self {
        Self {
            active_speaker_rx,
            mic_level_rx,
            audio_dumper_flag,
            mute_flag,
            loopback_flag,
            selected_channel: "Lobby".to_string(),
            is_deafened: false,
            chat_messages: vec![
                ChatMessage {
                    author: "System".into(),
                    body: "Welcome to Voice App!".into(),
                },
                ChatMessage {
                    author: "System".into(),
                    body: "Press 'V' to talk. Use the controls below to mute/deafen.".into(),
                },
            ],
            chat_input: String::new(),
            show_dev_settings: false,
        }
    }

    /// Applies the dark catppuccin-inspired colour scheme to egui.
    fn apply_theme(ctx: &egui::Context) {
        let mut style = (*ctx.global_style()).clone();
        let visuals = &mut style.visuals;

        visuals.dark_mode = true;
        visuals.override_text_color = Some(TEXT_PRIMARY);
        visuals.panel_fill = BG_DARK;
        visuals.window_fill = PANEL_BG;
        visuals.extreme_bg_color = egui::Color32::from_rgb(24, 24, 37);

        visuals.widgets.noninteractive.bg_fill = PANEL_BG;
        visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(45, 45, 65);
        visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(55, 55, 80);
        visuals.widgets.active.bg_fill = ACCENT;

        visuals.selection.bg_fill = ACCENT_DIM;
        visuals.selection.stroke = egui::Stroke::new(1.0, ACCENT);

        visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, TEXT_MUTED);
        visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, TEXT_PRIMARY);
        visuals.widgets.hovered.fg_stroke = egui::Stroke::new(1.0, TEXT_PRIMARY);
        visuals.widgets.active.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);

        style.spacing.item_spacing = egui::vec2(8.0, 6.0);

        ctx.set_global_style(style);
    }

    // ── Sub-drawing functions ──

    /// Draws the left-hand channel tree panel.
    fn draw_channel_tree(&mut self, ui: &mut egui::Ui) {
        ui.add_space(4.0);
        ui.horizontal(|ui| {
            ui.label(egui::RichText::new("⚡").size(18.0).color(ACCENT));
            ui.label(
                egui::RichText::new("Voice App Server")
                    .size(15.0)
                    .strong()
                    .color(TEXT_PRIMARY),
            );
        });
        ui.add_space(4.0);
        ui.separator();
        ui.add_space(4.0);

        egui::ScrollArea::vertical()
            .auto_shrink([false, false])
            .show(ui, |ui| {
                for channel in CHANNEL_TREE {
                    self.draw_channel_entry(ui, channel, 0);
                }
            });
    }

    /// Recursively draws a single channel entry (with indent for children).
    fn draw_channel_entry(&mut self, ui: &mut egui::Ui, channel: &Channel, depth: usize) {
        #[allow(clippy::cast_precision_loss)] // Channel depth is always tiny.
        let indent = depth as f32 * 16.0;
        let is_selected = self.selected_channel == channel.name;
        let has_children = !channel.children.is_empty();

        ui.horizontal(|ui| {
            ui.add_space(indent);

            let icon = if has_children { "📁" } else { "🔊" };
            let text_color = if is_selected { ACCENT } else { TEXT_PRIMARY };

            let label = egui::RichText::new(format!("{icon}  {}", channel.name))
                .size(13.0)
                .color(text_color);

            let response = ui.selectable_label(is_selected, label);
            if response.clicked() {
                self.selected_channel = channel.name.to_string();
            }
        });

        if has_children {
            for child in channel.children {
                self.draw_channel_entry(ui, child, depth + 1);
            }
        }
    }

    /// Draws the central chat panel.
    fn draw_chat_panel(&mut self, ui: &mut egui::Ui) {
        ui.horizontal(|ui| {
            ui.label(
                egui::RichText::new(format!("# {}", self.selected_channel))
                    .size(16.0)
                    .strong()
                    .color(TEXT_PRIMARY),
            );
        });
        ui.separator();

        // Chat message area (takes all remaining space minus the input box).
        let available = ui.available_height() - 40.0;
        egui::ScrollArea::vertical()
            .auto_shrink([false, false])
            .max_height(available)
            .stick_to_bottom(true)
            .show(ui, |ui| {
                for msg in &self.chat_messages {
                    ui.horizontal_wrapped(|ui| {
                        ui.label(
                            egui::RichText::new(&msg.author)
                                .strong()
                                .color(ACCENT)
                                .size(13.0),
                        );
                        ui.label(
                            egui::RichText::new(&msg.body)
                                .color(TEXT_PRIMARY)
                                .size(13.0),
                        );
                    });
                    ui.add_space(2.0);
                }
            });

        ui.separator();

        // Chat input bar.
        ui.horizontal(|ui| {
            let response = ui.add_sized(
                [ui.available_width() - 60.0, 28.0],
                egui::TextEdit::singleline(&mut self.chat_input)
                    .hint_text("Type a message…")
                    .desired_width(f32::INFINITY),
            );

            if ui.button("Send").clicked()
                || (response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)))
            {
                let text = self.chat_input.trim().to_string();
                if !text.is_empty() {
                    self.chat_messages.push(ChatMessage {
                        author: "You".into(),
                        body: text,
                    });
                    self.chat_input.clear();
                }
                response.request_focus();
            }
        });
    }

    /// Draws the bottom control bar (mute, deafen, PTT status, mic level).
    fn draw_control_bar(&mut self, ui: &mut egui::Ui) {
        let is_speaking = *self.active_speaker_rx.borrow();
        let mic_level = *self.mic_level_rx.borrow();
        let is_muted = self.mute_flag.load(Ordering::Relaxed);

        ui.horizontal(|ui| {
            ui.add_space(8.0);

            // ── User info + speaking indicator ──
            let dot_color = if is_speaking {
                GREEN
            } else if is_muted || self.is_deafened {
                RED
            } else {
                TEXT_MUTED
            };
            ui.label(egui::RichText::new("●").size(16.0).color(dot_color));

            let status = if is_speaking {
                "Speaking"
            } else if is_muted {
                "Muted"
            } else if self.is_deafened {
                "Deafened"
            } else {
                "Idle"
            };
            ui.label(
                egui::RichText::new(format!("TestUser  •  {status}"))
                    .size(13.0)
                    .color(TEXT_PRIMARY),
            );

            ui.add_space(16.0);

            // ── Mic level meter ──
            ui.label(egui::RichText::new("Mic").size(11.0).color(TEXT_MUTED));
            let meter_width = 80.0;
            let (rect, _response) =
                ui.allocate_exact_size(egui::vec2(meter_width, 12.0), egui::Sense::hover());
            let painter = ui.painter();
            painter.rect_filled(rect, 3.0, egui::Color32::from_rgb(40, 40, 58));

            // Clamp and scale the level for visual feedback.
            let clamped = mic_level.clamp(0.0, 0.5) * 2.0; // normalize 0.0–0.5 → 0.0–1.0
            let fill_width = clamped * meter_width;
            let meter_color = if clamped > 0.8 {
                RED
            } else if clamped > 0.4 {
                YELLOW
            } else {
                GREEN
            };
            if fill_width > 0.5 {
                let fill_rect = egui::Rect::from_min_size(rect.min, egui::vec2(fill_width, 12.0));
                painter.rect_filled(fill_rect, 3.0, meter_color);
            }

            ui.add_space(16.0);

            // ── Mute / Deafen / Settings buttons ──
            let mute_label = if is_muted { "🔇 Unmute" } else { "🎤 Mute" };
            let mute_color = if is_muted { RED } else { TEXT_PRIMARY };
            if ui
                .add(egui::Button::new(
                    egui::RichText::new(mute_label).size(12.0).color(mute_color),
                ))
                .clicked()
            {
                self.mute_flag.store(!is_muted, Ordering::Relaxed);
            }

            let deafen_label = if self.is_deafened {
                "🔇 Undeafen"
            } else {
                "🎧 Deafen"
            };
            let deafen_color = if self.is_deafened { RED } else { TEXT_PRIMARY };
            if ui
                .add(egui::Button::new(
                    egui::RichText::new(deafen_label)
                        .size(12.0)
                        .color(deafen_color),
                ))
                .clicked()
            {
                self.is_deafened = !self.is_deafened;
                // Deafening also mutes outgoing audio.
                if self.is_deafened {
                    self.mute_flag.store(true, Ordering::Relaxed);
                }
            }

            // ── Dev settings toggle ──
            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                if ui
                    .add(egui::Button::new(
                        egui::RichText::new("⚙").size(16.0).color(TEXT_MUTED),
                    ))
                    .on_hover_text("Developer Settings")
                    .clicked()
                {
                    self.show_dev_settings = !self.show_dev_settings;
                }
            });
        });
    }

    /// Draws the developer settings side panel (audio dumper toggle, etc.).
    fn draw_dev_settings(&mut self, ui: &mut egui::Ui) {
        ui.add_space(4.0);
        ui.label(
            egui::RichText::new("Developer Settings")
                .size(14.0)
                .strong()
                .color(ACCENT),
        );
        ui.add_space(4.0);
        ui.separator();
        ui.add_space(8.0);

        let mut dumper_enabled = self.audio_dumper_flag.load(Ordering::Relaxed);
        if ui
            .checkbox(&mut dumper_enabled, "Enable Audio Dumper (.wav)")
            .changed()
        {
            self.audio_dumper_flag
                .store(dumper_enabled, Ordering::Relaxed);
        }

        let mut loopback_enabled = self.loopback_flag.load(Ordering::Relaxed);
        if ui
            .checkbox(&mut loopback_enabled, "Enable Mic Loopback")
            .changed()
        {
            self.loopback_flag.store(loopback_enabled, Ordering::Relaxed);
        }
        ui.label(
            egui::RichText::new("Writes raw_mic.wav and post_dsp.wav to the working directory.")
                .size(11.0)
                .color(TEXT_MUTED)
                .italics(),
        );

        ui.add_space(12.0);
        ui.label(
            egui::RichText::new("Audio Pipeline")
                .size(13.0)
                .strong()
                .color(TEXT_PRIMARY),
        );
        ui.add_space(4.0);

        let mic_level = *self.mic_level_rx.borrow();
        ui.label(
            egui::RichText::new(format!("RMS Level: {mic_level:.4}"))
                .size(12.0)
                .monospace()
                .color(TEXT_MUTED),
        );
        ui.label(
            egui::RichText::new(format!("VAD Threshold: {VAD_RMS_THRESHOLD:.4}"))
                .size(12.0)
                .monospace()
                .color(TEXT_MUTED),
        );

        let is_speaking = *self.active_speaker_rx.borrow();
        let vad_label = if is_speaking { "ACTIVE" } else { "SILENT" };
        let vad_color = if is_speaking { GREEN } else { TEXT_MUTED };
        ui.horizontal(|ui| {
            ui.label(
                egui::RichText::new("VAD: ")
                    .size(12.0)
                    .monospace()
                    .color(TEXT_MUTED),
            );
            ui.label(
                egui::RichText::new(vad_label)
                    .size(12.0)
                    .monospace()
                    .strong()
                    .color(vad_color),
            );
        });
    }
}

/// Reference to the DSP module's VAD threshold for display in the dev panel.
const VAD_RMS_THRESHOLD: f32 = 0.01;

impl eframe::App for VoiceApp {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        // Apply theme once per frame (cheap — just pointer comparisons internally).
        Self::apply_theme(ui.ctx());

        // ── Left panel: Channel tree ──
        egui::Panel::left("channel_tree")
            .default_size(200.0)
            .resizable(true)
            .frame(
                egui::Frame::new()
                    .fill(PANEL_BG)
                    .inner_margin(egui::Margin::same(8))
                    .stroke(egui::Stroke::new(1.0, SEPARATOR)),
            )
            .show_inside(ui, |ui| {
                self.draw_channel_tree(ui);
            });

        // ── Right panel: Dev settings (toggled) ──
        if self.show_dev_settings {
            egui::Panel::right("dev_settings")
                .default_size(220.0)
                .resizable(true)
                .frame(
                    egui::Frame::new()
                        .fill(PANEL_BG)
                        .inner_margin(egui::Margin::same(8))
                        .stroke(egui::Stroke::new(1.0, SEPARATOR)),
                )
                .show_inside(ui, |ui| {
                    self.draw_dev_settings(ui);
                });
        }

        // ── Bottom panel: Control bar ──
        egui::Panel::bottom("controls")
            .exact_size(40.0)
            .frame(
                egui::Frame::new()
                    .fill(PANEL_BG)
                    .inner_margin(egui::Margin::same(6))
                    .stroke(egui::Stroke::new(1.0, SEPARATOR)),
            )
            .show_inside(ui, |ui| {
                self.draw_control_bar(ui);
            });

        // ── Central panel: Chat ──
        egui::CentralPanel::default()
            .frame(
                egui::Frame::new()
                    .fill(BG_DARK)
                    .inner_margin(egui::Margin::same(12)),
            )
            .show_inside(ui, |ui| {
                self.draw_chat_panel(ui);
            });

        // Force continuous repaint so the watch channels reflect immediately.
        ui.ctx().request_repaint();
    }
}
