//! Application state: owns the project, editor context, and all UI/playback state.

mod clipboard;
mod dispatch;
mod editing;
mod navigation;
mod persistence;
mod scripting;
mod sequencer;
mod staging;
mod undo;

use arc_swap::ArcSwap;
use parking_lot::Mutex;
use rand::rngs::StdRng;
use rand::SeedableRng;
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};

use cagire_ratatui::CompletionCandidate;

use crate::engine::LinkState;
use crate::midi::MidiState;
use crate::model::{self, Bank, Dictionary, Pattern, Rng, ScriptEngine, Variables};
use crate::page::Page;
use crate::state::{
    undo::UndoHistory, AudioSettings, EditorContext, LiveKeyState, Metrics, Modal, MuteState,
    OptionsState, PanelState, PatternField, PatternPropsField, PatternsNav, PlaybackState,
    ProjectState, ScriptEditorState, UiState,
};

static COMPLETION_CANDIDATES: LazyLock<Vec<CompletionCandidate>> = LazyLock::new(|| {
    model::WORDS
        .iter()
        .map(|w| CompletionCandidate {
            name: w.name.to_string(),
            signature: w.stack.to_string(),
            description: w.desc.to_string(),
            example: w.example.to_string(),
        })
        .collect()
});

pub struct App {
    pub project_state: ProjectState,
    pub ui: UiState,
    pub playback: PlaybackState,
    pub mute: MuteState,

    pub page: Page,
    pub editor_ctx: EditorContext,
    pub script_editor: ScriptEditorState,

    pub patterns_nav: PatternsNav,

    pub metrics: Metrics,
    pub script_engine: ScriptEngine,
    pub variables: Variables,
    pub dict: Dictionary,
    // Held to keep the Arc alive (shared with ScriptEngine).
    pub _rng: Rng,
    pub live_keys: Arc<LiveKeyState>,
    pub copied_patterns: Option<Vec<Pattern>>,
    pub copied_banks: Option<Vec<Bank>>,

    pub undo: UndoHistory,

    pub audio: AudioSettings,
    pub options: OptionsState,
    pub panel: PanelState,
    pub midi: MidiState,
    pub plugin_mode: bool,
}

impl Default for App {
    fn default() -> Self {
        Self::new()
    }
}

impl App {
    pub fn new() -> Self {
        let variables = Arc::new(ArcSwap::from_pointee(HashMap::new()));
        let dict = Arc::new(Mutex::new(HashMap::new()));
        let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(0)));
        Self::build(variables, dict, rng, false)
    }

    #[allow(dead_code)] // used by plugin crate
    pub fn new_plugin(variables: Variables, dict: Dictionary, rng: Rng) -> Self {
        Self::build(variables, dict, rng, true)
    }

    fn build(variables: Variables, dict: Dictionary, rng: Rng, plugin_mode: bool) -> Self {
        let script_engine =
            ScriptEngine::new(Arc::clone(&variables), Arc::clone(&dict), Arc::clone(&rng));
        let live_keys = Arc::new(LiveKeyState::new());

        Self {
            project_state: ProjectState::default(),
            ui: UiState::default(),
            playback: PlaybackState::default(),
            mute: MuteState::default(),

            page: Page::default(),
            editor_ctx: EditorContext::default(),
            script_editor: ScriptEditorState::default(),

            patterns_nav: PatternsNav::default(),

            metrics: Metrics::default(),
            variables,
            dict,
            _rng: rng,
            live_keys,
            script_engine,
            copied_patterns: None,
            copied_banks: None,

            undo: UndoHistory::default(),

            audio: if plugin_mode {
                AudioSettings::new_plugin()
            } else {
                AudioSettings::default()
            },
            options: OptionsState::default(),
            panel: PanelState::default(),
            midi: MidiState::new(),
            plugin_mode,
        }
    }

    fn current_bank_pattern(&self) -> (usize, usize) {
        (self.editor_ctx.bank, self.editor_ctx.pattern)
    }

    fn selected_steps(&self) -> Vec<usize> {
        match self.editor_ctx.selection_range() {
            Some(range) => range.collect(),
            None => vec![self.editor_ctx.step],
        }
    }

    pub fn mark_all_patterns_dirty(&mut self) {
        self.project_state.mark_all_dirty();
    }

    pub fn toggle_playing(&mut self, link: &LinkState) {
        let was_playing = self.playback.playing;
        self.playback.toggle();
        if !was_playing && self.playback.playing {
            self.evaluate_prelude(link);
        }
    }

    pub fn tempo_up(&self, link: &LinkState) {
        let current = link.tempo();
        link.set_tempo((current + 1.0).min(300.0));
    }

    pub fn tempo_down(&self, link: &LinkState) {
        let current = link.tempo();
        link.set_tempo((current - 1.0).max(20.0));
    }

    pub fn current_edit_pattern(&self) -> &Pattern {
        let (bank, pattern) = self.current_bank_pattern();
        self.project_state.project.pattern_at(bank, pattern)
    }

    pub fn open_script_modal(&mut self, field: crate::state::ScriptField) {
        use crate::state::ScriptField;
        let current = match field {
            ScriptField::Speed => self.project_state.project.script_speed.label().to_string(),
            ScriptField::Length => self.project_state.project.script_length.to_string(),
        };
        self.ui.modal = Modal::SetScript {
            field,
            input: current,
        };
    }

    pub fn open_pattern_modal(&mut self, field: PatternField) {
        let current = match field {
            PatternField::Length => self.current_edit_pattern().length.to_string(),
            PatternField::Speed => self.current_edit_pattern().speed.label().to_string(),
        };
        self.ui.modal = Modal::SetPattern {
            field,
            input: current,
        };
    }

    pub fn open_pattern_props_modal(&mut self, bank: usize, pattern: usize) {
        let pat = self.project_state.project.pattern_at(bank, pattern);
        self.ui.modal = Modal::PatternProps {
            bank,
            pattern,
            field: PatternPropsField::default(),
            name: pat.name.clone().unwrap_or_default(),
            description: pat.description.clone().unwrap_or_default(),
            length: pat.length.to_string(),
            speed: pat.speed,
            quantization: pat.quantization,
            sync_mode: pat.sync_mode,
            follow_up: pat.follow_up,
        };
    }

    pub fn maybe_show_onboarding(&mut self) {
        if self.plugin_mode {
            return;
        }
        if self.ui.modal != Modal::None {
            return;
        }
        let name = self.page.name();
        if self.ui.onboarding_dismissed.iter().any(|d| d == name) {
            return;
        }
        self.ui.modal = Modal::Onboarding { page: 0 };
    }
}
