//! Plugin editor: renders the ratatui TUI inside an egui surface.

use std::sync::atomic::{AtomicBool, AtomicI64};
use std::sync::Arc;
use std::time::Instant;

use arc_swap::ArcSwap;
use crossbeam_channel::Sender;
use egui_ratatui::RataguiBackend;
use nih_plug::prelude::*;
use nih_plug_egui::egui;
use nih_plug_egui::{create_egui_editor, EguiState};
use ratatui::Terminal;
use soft_ratatui::embedded_graphics_unicodefonts::{
    mono_10x20_atlas, mono_6x13_atlas, mono_6x13_bold_atlas, mono_6x13_italic_atlas,
    mono_7x13_atlas, mono_7x13_bold_atlas, mono_7x13_italic_atlas, mono_8x13_atlas,
    mono_8x13_bold_atlas, mono_8x13_italic_atlas, mono_9x15_atlas, mono_9x15_bold_atlas,
    mono_9x18_atlas, mono_9x18_bold_atlas,
};
use soft_ratatui::{EmbeddedGraphics, SoftBackend};

use cagire::block_renderer::BlockCharBackend;
use cagire::app::App;
use cagire::engine::{AudioCommand, LinkState, SequencerSnapshot};
use cagire::input::{handle_key, handle_mouse, InputContext};
use cagire::model::{Dictionary, Rng, Variables};
use cagire::theme;
use cagire::views;

use cagire::input_egui::{convert_egui_events, convert_egui_mouse, EguiMouseState};
use crate::params::CagireParams;
use crate::PluginBridge;

type TerminalType = Terminal<RataguiBackend<BlockCharBackend>>;

#[derive(Clone, Copy, PartialEq)]
enum FontChoice {
    Size6x13,
    Size7x13,
    Size8x13,
    Size9x15,
    Size9x18,
    Size10x20,
}

impl FontChoice {
    fn from_setting(s: &str) -> Self {
        match s {
            "6x13" => Self::Size6x13,
            "7x13" => Self::Size7x13,
            "9x15" => Self::Size9x15,
            "9x18" => Self::Size9x18,
            "10x20" => Self::Size10x20,
            _ => Self::Size8x13,
        }
    }

    fn to_setting(self) -> &'static str {
        match self {
            Self::Size6x13 => "6x13",
            Self::Size7x13 => "7x13",
            Self::Size8x13 => "8x13",
            Self::Size9x15 => "9x15",
            Self::Size9x18 => "9x18",
            Self::Size10x20 => "10x20",
        }
    }

    fn label(self) -> &'static str {
        match self {
            Self::Size6x13 => "6x13 (Compact)",
            Self::Size7x13 => "7x13",
            Self::Size8x13 => "8x13 (Default)",
            Self::Size9x15 => "9x15",
            Self::Size9x18 => "9x18",
            Self::Size10x20 => "10x20 (Large)",
        }
    }

    const ALL: [Self; 6] = [
        Self::Size6x13,
        Self::Size7x13,
        Self::Size8x13,
        Self::Size9x15,
        Self::Size9x18,
        Self::Size10x20,
    ];
}

fn create_terminal(font: FontChoice) -> TerminalType {
    let (regular, bold, italic) = match font {
        FontChoice::Size6x13 => (
            mono_6x13_atlas(),
            Some(mono_6x13_bold_atlas()),
            Some(mono_6x13_italic_atlas()),
        ),
        FontChoice::Size7x13 => (
            mono_7x13_atlas(),
            Some(mono_7x13_bold_atlas()),
            Some(mono_7x13_italic_atlas()),
        ),
        FontChoice::Size8x13 => (
            mono_8x13_atlas(),
            Some(mono_8x13_bold_atlas()),
            Some(mono_8x13_italic_atlas()),
        ),
        FontChoice::Size9x15 => (mono_9x15_atlas(), Some(mono_9x15_bold_atlas()), None),
        FontChoice::Size9x18 => (mono_9x18_atlas(), Some(mono_9x18_bold_atlas()), None),
        FontChoice::Size10x20 => (mono_10x20_atlas(), None, None),
    };

    let eg = SoftBackend::<EmbeddedGraphics>::new(80, 24, regular, bold, italic);
    let soft = SoftBackend {
        buffer: eg.buffer,
        cursor: eg.cursor,
        cursor_pos: eg.cursor_pos,
        char_width: eg.char_width,
        char_height: eg.char_height,
        blink_counter: eg.blink_counter,
        blinking_fast: eg.blinking_fast,
        blinking_slow: eg.blinking_slow,
        rgb_pixmap: eg.rgb_pixmap,
        always_redraw_list: eg.always_redraw_list,
        raster_backend: BlockCharBackend {
            inner: eg.raster_backend,
        },
    };
    Terminal::new(RataguiBackend::new("cagire", soft)).expect("terminal")
}

struct EditorState {
    app: App,
    terminal: TerminalType,
    link: Arc<LinkState>,
    snapshot: SequencerSnapshot,
    playing: Arc<AtomicBool>,
    nudge_us: Arc<AtomicI64>,
    audio_tx: Arc<ArcSwap<Sender<AudioCommand>>>,
    bridge: Arc<PluginBridge>,
    params: Arc<CagireParams>,
    last_frame: Instant,
    current_font: FontChoice,
    zoom_factor: f32,
    egui_mouse: EguiMouseState,
}

// SAFETY: EditorState is only accessed from the GUI thread via RwLock.
// The non-Send types (RefCell in App's tachyonfx effects) are never shared
// across threads — baseview's event loop guarantees single-threaded access.
unsafe impl Send for EditorState {}
unsafe impl Sync for EditorState {}

/// Build the egui-based plugin editor with ratatui rendering.
pub fn create_editor(
    params: Arc<CagireParams>,
    egui_state: Arc<EguiState>,
    bridge: Arc<PluginBridge>,
    variables: Variables,
    dict: Dictionary,
    rng: Rng,
) -> Option<Box<dyn Editor>> {
    let egui_state_clone = Arc::clone(&egui_state);
    create_egui_editor(
        egui_state,
        None::<EditorState>,
        |ctx, _state| {
            ctx.set_visuals(egui::Visuals {
                panel_fill: egui::Color32::BLACK,
                ..egui::Visuals::dark()
            });
            ctx.style_mut(|style| {
                style.spacing.window_margin = egui::Margin::ZERO;
            });
        },
        move |ctx, _setter, state| {
            let editor = state.get_or_insert_with(|| {
                let palette =
                    cagire::state::ColorScheme::default().to_palette();
                theme::set(cagire_ratatui::theme::build::build(&palette));

                let mut app = App::new_plugin(
                    Arc::clone(&variables),
                    Arc::clone(&dict),
                    Arc::clone(&rng),
                );
                app.audio.section = cagire::state::EngineSection::Settings;
                app.audio.setting_kind = cagire::state::SettingKind::Polyphony;

                // Load persisted project
                app.project_state.project = params.project.lock().clone();
                app.mark_all_patterns_dirty();

                // Init window size from egui state
                let (w, h) = egui_state_clone.size();
                app.ui.window_width = w;
                app.ui.window_height = h;

                EditorState {
                    app,
                    terminal: create_terminal(FontChoice::Size8x13),
                    link: Arc::new(LinkState::new(
                        params.tempo.value() as f64,
                        4.0,
                    )),
                    snapshot: SequencerSnapshot::empty(),
                    playing: Arc::new(AtomicBool::new(false)),
                    nudge_us: Arc::new(AtomicI64::new(0)),
                    audio_tx: Arc::new(ArcSwap::from_pointee(bridge.audio_cmd_tx.clone())),
                    bridge: Arc::clone(&bridge),
                    params: Arc::clone(&params),
                    last_frame: Instant::now(),
                    current_font: FontChoice::Size8x13,
                    zoom_factor: 1.0,
                    egui_mouse: EguiMouseState::default(),
                }
            });

            // Flush pattern data and queued changes to the audio thread
            let had_dirty = editor.app.flush_dirty_patterns(&editor.bridge.cmd_tx);
            editor.app.flush_queued_changes(&editor.bridge.cmd_tx);

            // Sync project changes back to persisted params
            if had_dirty {
                *editor.params.project.lock() = editor.app.project_state.project.clone();
            }

            // Sync sample registry from the audio engine (set once after initialize)
            if editor.app.audio.sample_registry.is_none() {
                if let Some(reg) = editor.bridge.sample_registry.load().as_ref() {
                    editor.app.audio.sample_registry = Some(Arc::clone(reg));
                }
            }

            // Read live snapshot from the audio thread
            let shared = editor.bridge.shared_state.load();
            editor.snapshot = SequencerSnapshot::from(shared.as_ref());

            // Sync host tempo into LinkState so title bar shows real tempo
            if shared.tempo > 0.0 {
                editor.link.set_tempo(shared.tempo);
            }

            // Feed scope and spectrum data into app metrics
            editor.app.metrics.scope = editor.bridge.scope_buffer.read();
            (editor.app.metrics.peak_left, editor.app.metrics.peak_right) =
                editor.bridge.scope_buffer.peaks();
            editor.app.metrics.spectrum = editor.bridge.spectrum_buffer.read();

            // Handle input
            let term = editor.terminal.get_frame().area();
            let widget_rect = ctx.content_rect();

            for mouse in convert_egui_mouse(ctx, widget_rect, term, &mut editor.egui_mouse) {
                let mut input_ctx = InputContext {
                    app: &mut editor.app,
                    link: &editor.link,
                    snapshot: &editor.snapshot,
                    playing: &editor.playing,
                    audio_tx: &editor.audio_tx,
                    seq_cmd_tx: &editor.bridge.cmd_tx,
                    nudge_us: &editor.nudge_us,
                };
                handle_mouse(&mut input_ctx, mouse, term);
            }

            for key in convert_egui_events(ctx) {
                let mut input_ctx = InputContext {
                    app: &mut editor.app,
                    link: &editor.link,
                    snapshot: &editor.snapshot,
                    playing: &editor.playing,
                    audio_tx: &editor.audio_tx,
                    seq_cmd_tx: &editor.bridge.cmd_tx,
                    nudge_us: &editor.nudge_us,
                };
                let _ = handle_key(&mut input_ctx, key);
            }

            cagire::state::effects::tick_effects(&mut editor.app.ui, editor.app.page);

            // Sync font/zoom from App state (options page or context menu may have changed them)
            let desired_font = FontChoice::from_setting(&editor.app.ui.font);
            if desired_font != editor.current_font {
                editor.terminal = create_terminal(desired_font);
                editor.current_font = desired_font;
            }
            if (editor.app.ui.zoom_factor - editor.zoom_factor).abs() > 0.01 {
                editor.zoom_factor = editor.app.ui.zoom_factor;
                ctx.set_zoom_factor(editor.zoom_factor);
            }

            // Sync window size: if app state differs from egui state, request resize
            let (cur_w, cur_h) = egui_state_clone.size();
            if editor.app.ui.window_width != cur_w || editor.app.ui.window_height != cur_h {
                egui_state_clone.set_requested_size((editor.app.ui.window_width, editor.app.ui.window_height));
            }

            let elapsed = editor.last_frame.elapsed();
            editor.last_frame = Instant::now();

            let link = &editor.link;
            let app = &editor.app;
            let snapshot = &editor.snapshot;
            editor
                .terminal
                .draw(|frame| views::render(frame, app, link, snapshot, elapsed))
                .expect("draw");

            let current_font = editor.current_font;
            let current_zoom = editor.zoom_factor;

            egui::CentralPanel::default()
                .frame(egui::Frame::NONE)
                .show(ctx, |ui| {
                    ui.add(editor.terminal.backend_mut());

                    let response = ui.interact(
                        ui.max_rect(),
                        egui::Id::new("terminal_context"),
                        egui::Sense::click(),
                    );
                    response.context_menu(|ui| {
                        ui.menu_button("Font", |ui| {
                            for choice in FontChoice::ALL {
                                if ui.selectable_label(current_font == choice, choice.label()).clicked() {
                                    editor.app.ui.font = choice.to_setting().to_string();
                                    editor.terminal = create_terminal(choice);
                                    editor.current_font = choice;
                                    ui.close();
                                }
                            }
                        });
                        ui.menu_button("Zoom", |ui| {
                            for &level in &[0.5_f32, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0] {
                                let selected = (current_zoom - level).abs() < 0.01;
                                let label = format!("{:.0}%", level * 100.0);
                                if ui.selectable_label(selected, label).clicked() {
                                    editor.app.ui.zoom_factor = level;
                                    editor.zoom_factor = level;
                                    ctx.set_zoom_factor(level);
                                    ui.close();
                                }
                            }
                        });
                    });
                });
        },
    )
}
