use std::any::Any;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::sync::mpsc;
use std::sync::Arc;
use std::thread;

use egui::{ScrollArea, Ui, WidgetText};
use egui_extras::{Size, StripBuilder};
use egui_file::FileDialog;
use egui_tiles::TileId;

use common::formats::binary_dse::DseExecutable;
use disassembler::{disassemble, Disassembly};

use crate::ui::panels::TilePane;
use crate::ui::{Action, DataSource};
use crate::ui::panels::memory::MemoryViewer;

#[derive(PartialEq, Clone, Copy)]
enum ViewMode {
    Memory,
    Disassembly,
}

enum DisassemblyState {
    Idle,
    Running(mpsc::Receiver<Disassembly>),
    Done(Disassembly),
}

pub struct BinaryViewer {
    dialog: Option<FileDialog>,
    error: Option<String>,

    program: Option<Arc<DseExecutable>>,
    memory_image: Option<Vec<u8>>,

    view: ViewMode,
    memory_viewer: MemoryViewer,
    disassembly: DisassemblyState,
}

impl BinaryViewer {
    pub fn new() -> Self {
        Self {
            dialog: None,
            error: None,
            program: None,
            memory_image: None,
            view: ViewMode::Memory,
            memory_viewer: MemoryViewer::new(),
            disassembly: DisassemblyState::Idle,
        }
    }

    pub fn new_open(path: &Path) -> Self {
        let mut viewer = Self::new();
        match File::open(path).map(|f| DseExecutable::read(&mut BufReader::new(f))) {
            Ok(Ok(bin)) => viewer.load_program(bin),
            Ok(Err(e)) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())),
            Err(e) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())),
        }
        viewer
    }

    fn load_program(&mut self, bin: DseExecutable) {
        // TODO: replace with however DseExecutable actually exposes its raw memory image.
        // Assuming a method like this exists — swap for the real accessor.
        self.memory_image = Some(bin.to_memory_image());
        self.program = Some(Arc::new(bin));
        self.disassembly = DisassemblyState::Idle;
        self.view = ViewMode::Memory;
        self.error = None;
    }

    fn open(&mut self) {
        let Ok(cwd) = std::env::current_dir() else {
            self.error = Some("Couldn't get CWD to open file!".to_string());
            return;
        };

        if self.dialog.is_none() {
            let mut dialog = FileDialog::open_file();
            dialog.set_path(cwd);
            dialog.open();
            self.dialog = Some(dialog);
        }
    }

    fn handle_file_dialog(&mut self, ui: &mut Ui) {
        let Some(dialog) = &mut self.dialog else { return };
        if !dialog.show(ui).selected() { return }

        let Some(file) = dialog.path() else { return };
        let file = file.to_path_buf();
        self.dialog = None;

        if !file.extension().is_some_and(|ext| ext == "dsb") {
            self.error = Some("Selected file is not a .dsb binary".into());
            return;
        }

        let reader = match File::open(&file) {
            Ok(f) => f,
            Err(e) => { self.error = Some(e.to_string()); return; }
        };

        match DseExecutable::read(&mut BufReader::new(reader)) {
            Ok(bin) => self.load_program(bin),
            Err(e) => self.error = Some(e.to_string()),
        }
    }

    /// Kicks disassembly off on a background thread so a large binary
    /// doesn't stall the UI. `DseExecutable` is wrapped in `Arc` so the
    /// thread can borrow it without cloning the whole binary image.
    fn start_disassembly(&mut self) {
        let Some(program) = self.program.clone() else { return };
        let (tx, rx) = mpsc::channel();

        thread::spawn(move || {
            let result = disassemble(&program);
            let _ = tx.send(result);
        });

        self.disassembly = DisassemblyState::Running(rx);
        self.view = ViewMode::Disassembly;
    }

    fn poll_disassembly(&mut self) {
        if let DisassemblyState::Running(rx) = &self.disassembly {
            if let Ok(result) = rx.try_recv() {
                self.disassembly = DisassemblyState::Done(result);
            }
        }
    }

    fn toolbar(&mut self, ui: &mut Ui) -> Option<Action> {
        let mut action = None;

        ui.horizontal(|ui| {
            ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
            ui.spacing_mut().item_spacing.x = 6.0;

            if ui.button("Open").clicked() {
                self.open();
            }

            let has_program = self.program.is_some();

            if ui.add_enabled(has_program, egui::Button::new("Load")).clicked() {
                if let Some(data) = &self.memory_image {
                    action = Some(Action::LoadBinary(DataSource::Data(data.clone())));
                }
            }

            let disassembling = matches!(self.disassembly, DisassemblyState::Running(_));
            if ui
                .add_enabled(has_program && !disassembling, egui::Button::new("Disassemble"))
                .clicked()
            {
                self.start_disassembly();
            }

            ui.separator();

            ui.selectable_value(&mut self.view, ViewMode::Memory, "Memory");
            let has_disassembly = !matches!(self.disassembly, DisassemblyState::Idle);
            ui.add_enabled_ui(has_disassembly, |ui| {
                ui.selectable_value(&mut self.view, ViewMode::Disassembly, "Disassembly");
            });

            if let Some(err) = &self.error {
                ui.separator();
                ui.colored_label(egui::Color32::from_rgb(220, 80, 80), err);
            }
        });

        action
    }
}

impl TilePane for BinaryViewer {
    fn title(&self) -> WidgetText {
        "Binary Viewer".into()
    }

    fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
        self.handle_file_dialog(ui);
        self.poll_disassembly();

        if matches!(self.disassembly, DisassemblyState::Running(_)) {
            ui.ctx().request_repaint(); // keep polling promptly while the background thread works
        }

        let action = self.toolbar(ui);
        ui.separator();

        match self.view {
            ViewMode::Memory => match &self.memory_image {
                Some(memory) => self.memory_viewer.ui(ui, memory, 0),
                None => {
                    ui.centered_and_justified(|ui| {
                        ui.weak("Open a binary to inspect its memory image.");
                    });
                }
            },
            ViewMode::Disassembly => match &self.disassembly {
                DisassemblyState::Idle => {
                    ui.centered_and_justified(|ui| {
                        ui.weak("Click Disassemble to generate a disassembly.");
                    });
                }
                DisassemblyState::Running(_) => {
                    ui.centered_and_justified(|ui| {
                        ui.horizontal(|ui| {
                            ui.spinner();
                            ui.label("Disassembling…");
                        });
                    });
                }
                DisassemblyState::Done(output) => render_disassembly(ui, output),
            },
        }

        action
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

fn render_disassembly(ui: &mut Ui, output: &Disassembly) {
    StripBuilder::new(ui)
        .size(Size::exact(350.0))
        .size(Size::remainder().at_most(500.0))
        .horizontal(|mut strip| {
            strip.cell(|ui| {
                ScrollArea::vertical().id_salt("symbols").show(ui, |ui| {
                    egui::Grid::new("symbols_grid")
                        .striped(true)
                        .spacing([12.0, 2.0])
                        .show(ui, |ui| {
                            ui.strong("Addr");
                            ui.strong("Name");
                            ui.strong("Section");
                            ui.strong("Bind");
                            ui.end_row();

                            for symbol in &output.symbols {
                                ui.monospace(format!("{:#06x}", symbol.address));
                                ui.monospace(&symbol.name);
                                ui.label(format!("{:?}", symbol.section));
                                ui.label(format!("{:?}", symbol.binding));
                                ui.end_row();
                            }
                        });
                });
            });

            strip.cell(|ui| {
                ScrollArea::vertical().id_salt("asm").show(ui, |ui| {
                    egui::Grid::new("asm_grid")
                        .striped(true)
                        .spacing([12.0, 2.0])
                        .show(ui, |ui| {
                            for (addr, line) in output.text_lines.iter().enumerate() {
                                ui.label(
                                    egui::RichText::new(format!("{:04X}", addr))
                                        .monospace()
                                        .weak(),
                                );
                                ui.monospace(line);
                                ui.end_row();
                            }
                        });
                });
            });
        });
}