use std::{borrow::Cow, path::Path};

use nu_cli::gather_parent_env_vars;
use nu_cmd_lang::create_default_context;
use nu_command::add_shell_command_context;
use nu_engine::eval_block_with_early_return;
use nu_protocol::{
    IntoSpanned,
    PipelineData::Empty,
    PipelineExecutionData, Record, Span, Spanned,
    debugger::WithoutDebug,
    engine::{Closure, EngineState, Stack, StateWorkingSet},
};
use owo_colors::OwoColorize;

use crate::backends::{Error, Result};
use crate::{mod_err, nest_errors};

pub struct Engine {
    engine: EngineState,
    stack: Stack,
}

impl Engine {
    pub fn new(config_dir: &Path) -> Self {
        let mut engine_state = create_default_context();
        engine_state = add_shell_command_context(engine_state);
        gather_parent_env_vars(&mut engine_state, config_dir);

        let stack = Stack::new();

        Engine {
            engine: engine_state,
            stack,
        }
    }

    pub fn fetch(&mut self, contents: &[u8]) -> Result<Spanned<Record>> {
        let mut working_set = StateWorkingSet::new(&self.engine);
        let block = nu_parser::parse(&mut working_set, None, contents, false);

        self.engine
            .merge_delta(working_set.render())
            .map_err(|e| nest_errors!("Failed to run engine", e))?;

        eval_block_with_early_return::<WithoutDebug>(&self.engine, &mut self.stack, &block, Empty)
            .map_err(|e| nest_errors!("Failed to evaluate block", e))
            .and_then(pipeline_to_record)
    }

    pub fn execute_closure(&mut self, closure: &Closure) -> Result<()> {
        let span = self
            .engine
            .get_block(closure.block_id)
            .span
            .ok_or_else(|| mod_err!("Failed to get closure's span"))?;

        eval_block_with_early_return::<WithoutDebug>(
            &self.engine,
            &mut self.stack,
            self.engine.get_block(closure.block_id),
            Empty,
        )
        .map(|_| ())
        .map_err(|e| nest_errors!(span, "Failed to execute closure", e))
    }

    pub fn dry_run_closure(&self, closure: &Closure) -> Result<()> {
        let source = self
            .engine
            .get_block(closure.block_id)
            .span
            .map(|span| self.get_span_contents(span))
            .ok_or_else(|| mod_err!("Failed to get the source for closure"))?;

        #[allow(clippy::print_stderr)]
        {
            source
                .lines()
                .for_each(|l| eprintln!("{} {}", ">".green(), l));
        }

        Ok(())
    }

    pub fn get_span_contents(&self, span: Span) -> Cow<'_, str> {
        let source = self.engine.get_span_contents(span);
        String::from_utf8_lossy(source)
    }
}

fn pipeline_to_record(pipeline_data: PipelineExecutionData) -> Result<Spanned<Record>> {
    let span = pipeline_data.span().unwrap_or(Span::unknown());

    let pipeline_value = pipeline_data
        .body
        .into_value(span)
        .map_err(|e| nest_errors!(span, "Failed to evaluate the value", e))?;

    let pipeline_record = pipeline_value
        .as_record()
        .map_err(|e| mod_err!(span, format!("Failed to convert to record: {e}")))?
        .to_owned();

    Ok(pipeline_record.into_spanned(span))
}
