use crate::commands::change_directory::change_directory;
use crate::error::{AppError, AppErrorKind, AppResult};
use crate::types::state::AppState;
use crate::utils::unix::expand_shell_string;
use std::path::PathBuf;

/// What to do with the captured output of the last `shell`/`capture` command.
#[derive(Debug, Clone)]
pub enum PostProcessor {
    /// Treat the output as a path and change into it (or its parent, if a file).
    ChangeDirectory,
}

impl PostProcessor {
    /// Parses a `stdout` command argument (e.g. `"cd"`) into a `PostProcessor`.
    pub fn from_str(args: &str) -> Option<Self> {
        match args {
            "cd" => Some(PostProcessor::ChangeDirectory),
            _ => None,
        }
    }
}

fn assert_one_line(stdout: &str) -> AppResult {
    match stdout.lines().count() {
        1 => Ok(()),
        _ => Err(AppError::new(AppErrorKind::StateError, "The last `capture` stdout does not have exactly one line as expected for this stdout processor".to_string()))
    }
}

fn as_one_existing_directory(stdout: &str) -> AppResult<PathBuf> {
    assert_one_line(stdout)?;
    let path = expand_shell_string(stdout);
    if path.exists() {
        if path.is_file() {
            if let Some(parent) = path.parent() {
                Ok(parent.to_path_buf())
            } else {
                Err(AppError::new(AppErrorKind::StateError, "The last `capture` output is a file but without a valid directory as parent in the file system".to_string()))
            }
        } else {
            Ok(path.to_path_buf())
        }
    } else {
        Err(AppError::new(
            AppErrorKind::StateError,
            "The last `capture` output line is not an existing path".to_string(),
        ))
    }
}

/// Implements `stdout`: applies `processor` to the captured output of the last `shell`/`capture`
/// command.
pub fn post_process_std_out(processor: &PostProcessor, app_state: &mut AppState) -> AppResult {
    let last_stdout = &app_state.state.last_stdout;
    if let Some(stdout) = last_stdout {
        let stdout = stdout.trim();
        match processor {
            PostProcessor::ChangeDirectory => {
                change_directory(app_state, as_one_existing_directory(stdout)?.as_path())
            }
        }
    } else {
        Err(AppError::new(
            AppErrorKind::StateError,
            "No result from a former `shell` available".to_string(),
        ))
    }
}
