//! Forth script compilation, evaluation, and editor ↔ step synchronization.

use crossbeam_channel::Sender;

use crate::engine::LinkState;
use crate::model::StepContext;
use crate::services::pattern_editor;
use crate::state::{EditorTarget, FlashKind, Modal, SampleTree};

use super::{App, COMPLETION_CANDIDATES};

impl App {
    /// Build a `StepContext` for evaluating a script outside the sequencer.
    pub(super) fn create_step_context(&self, step_idx: usize, link: &LinkState) -> StepContext<'static> {
        let (bank, pattern) = self.current_bank_pattern();
        let speed = self
            .project_state
            .project
            .pattern_at(bank, pattern)
            .speed
            .multiplier();
        StepContext {
            step: step_idx,
            beat: link.beat(),
            bank,
            pattern,
            tempo: link.tempo(),
            phase: link.phase(),
            slot: 0,
            runs: 0,
            iter: 0,
            speed,
            fill: false,
            nudge_secs: 0.0,
            cc_access: None,
            speed_key: "",
            mouse_x: 0.5,
            mouse_y: 0.5,
            mouse_down: 0.0,
        }
    }

    /// Load the current step's script into the editor widget.
    pub(super) fn load_step_to_editor(&mut self) {
        let (bank, pattern) = self.current_bank_pattern();
        if let Some(script) = pattern_editor::get_step_script(
            &self.project_state.project,
            bank,
            pattern,
            self.editor_ctx.step,
        ) {
            let lines: Vec<String> = if script.is_empty() {
                vec![String::new()]
            } else {
                script.lines().map(String::from).collect()
            };
            self.editor_ctx.editor.set_content(lines);
            self.editor_ctx.editor.set_candidates(COMPLETION_CANDIDATES.clone());
            self.editor_ctx
                .editor
                .set_completion_enabled(self.ui.show_completion);
            let tree = SampleTree::from_paths(&self.audio.config.sample_paths);
            self.editor_ctx.editor.set_sample_folders(tree.all_folder_names());
            if self.editor_ctx.show_stack {
                crate::services::stack_preview::update_cache(&self.editor_ctx);
            }
        }
    }

    /// Write the editor widget's content back to the current step.
    pub fn save_editor_to_step(&mut self) {
        let text = self.editor_ctx.editor.content();
        let (bank, pattern) = self.current_bank_pattern();
        let change = pattern_editor::set_step_script(
            &mut self.project_state.project,
            bank,
            pattern,
            self.editor_ctx.step,
            text,
        );
        self.project_state.mark_dirty(change.bank, change.pattern);
    }

    /// Switch the editor to the project prelude script.
    pub fn open_prelude_editor(&mut self) {
        let prelude = &self.project_state.project.prelude;
        let lines: Vec<String> = if prelude.is_empty() {
            vec![String::new()]
        } else {
            prelude.lines().map(String::from).collect()
        };
        self.editor_ctx.editor.set_content(lines);
        self.editor_ctx.editor.set_candidates(COMPLETION_CANDIDATES.clone());
        self.editor_ctx
            .editor
            .set_completion_enabled(self.ui.show_completion);
        let tree = SampleTree::from_paths(&self.audio.config.sample_paths);
        self.editor_ctx.editor.set_sample_folders(tree.all_folder_names());
        self.editor_ctx.target = EditorTarget::Prelude;
        self.ui.modal = Modal::Editor;
    }

    pub fn save_prelude(&mut self) {
        let text = self.editor_ctx.editor.content();
        self.project_state.project.prelude = text;
    }

    pub fn close_prelude_editor(&mut self) {
        self.editor_ctx.target = EditorTarget::Step;
        self.load_step_to_editor();
    }

    /// Evaluate the project prelude to seed variables and definitions.
    pub fn evaluate_prelude(&mut self, link: &LinkState) {
        let prelude = &self.project_state.project.prelude;
        if prelude.trim().is_empty() {
            return;
        }
        let ctx = self.create_step_context(0, link);
        match self.script_engine.evaluate(prelude, &ctx) {
            Ok(_) => {
                self.ui.flash("Prelude evaluated", 150, FlashKind::Info);
            }
            Err(e) => {
                self.ui
                    .flash(&format!("Prelude error: {e}"), 300, FlashKind::Error);
            }
        }
    }

    /// Evaluate a script and immediately send its audio commands.
    pub fn execute_script_oneshot(
        &self,
        script: &str,
        link: &LinkState,
        audio_tx: &arc_swap::ArcSwap<Sender<crate::engine::AudioCommand>>,
    ) -> Result<(), String> {
        let ctx = self.create_step_context(self.editor_ctx.step, link);
        let cmds = self.script_engine.evaluate(script, &ctx)?;
        for cmd in cmds {
            let _ = audio_tx
                .load()
                .send(crate::engine::AudioCommand::Evaluate { cmd, time: None });
        }
        Ok(())
    }

    /// Compile (evaluate) the current step's script to check for errors.
    pub fn compile_current_step(&mut self, link: &LinkState) {
        let step_idx = self.editor_ctx.step;
        let (bank, pattern) = self.current_bank_pattern();

        let script =
            pattern_editor::get_step_script(&self.project_state.project, bank, pattern, step_idx)
                .unwrap_or_default();

        if script.trim().is_empty() {
            return;
        }

        let ctx = self.create_step_context(step_idx, link);

        match self.script_engine.evaluate(&script, &ctx) {
            Ok(_) => {
                self.ui.flash("Script compiled", 150, FlashKind::Info);
            }
            Err(e) => {
                self.ui
                    .flash(&format!("Script error: {e}"), 300, FlashKind::Error);
            }
        }
    }

    /// Load the project's periodic script into the script editor.
    pub fn load_script_to_editor(&mut self) {
        let script = &self.project_state.project.script;
        let lines: Vec<String> = if script.is_empty() {
            vec![String::new()]
        } else {
            script.lines().map(String::from).collect()
        };
        self.script_editor.editor.set_content(lines);
        self.script_editor.editor.set_candidates(COMPLETION_CANDIDATES.clone());
        self.script_editor
            .editor
            .set_completion_enabled(self.ui.show_completion);
        let tree = SampleTree::from_paths(&self.audio.config.sample_paths);
        self.script_editor.editor.set_sample_folders(tree.all_folder_names());
    }

    /// Write the script editor content back to the project.
    pub fn save_script_from_editor(&mut self) {
        let text = self.script_editor.editor.content();
        self.project_state.project.script = text;
        self.script_editor.dirty = true;
    }

    /// Evaluate the script page content to check for errors.
    pub fn evaluate_script_page(&mut self, link: &LinkState) {
        let script = self.script_editor.editor.content();
        if script.trim().is_empty() {
            return;
        }
        let ctx = self.create_step_context(0, link);
        match self.script_engine.evaluate(&script, &ctx) {
            Ok(_) => {
                self.ui.flash("Script compiled", 150, FlashKind::Info);
            }
            Err(e) => {
                self.ui
                    .flash(&format!("Script error: {e}"), 300, FlashKind::Error);
            }
        }
    }

    /// Compile all steps in the current pattern to warm up definitions.
    pub fn compile_all_steps(&mut self, link: &LinkState) {
        let pattern_len = self.current_edit_pattern().length;
        let (bank, pattern) = self.current_bank_pattern();

        for step_idx in 0..pattern_len {
            let script = pattern_editor::get_step_script(
                &self.project_state.project,
                bank,
                pattern,
                step_idx,
            )
            .unwrap_or_default();

            if script.trim().is_empty() {
                continue;
            }

            let ctx = self.create_step_context(step_idx, link);
            let _ = self.script_engine.evaluate(&script, &ctx);
        }
    }
}
