use std::path::PathBuf;

use env::Environment;

use crate::syntax::{
    lex::{InputRedirect, OutputRedirectTarget, Redirect, WordToken},
    parse::{PipelineElement, PipelineExpression},
};

pub mod env;
pub mod eval;

pub enum ExpandedInputRedirect {
    File(PathBuf),
}

pub enum ExpandedOutputRedirect {
    File(PathBuf, bool),
}

pub struct ExpandedPipelineElement {
    pub words: Vec<String>,
    pub envs: Vec<(String, String)>,
    pub stdin: Option<ExpandedInputRedirect>,
    pub stdout: Option<ExpandedOutputRedirect>,
    pub stderr: Option<ExpandedOutputRedirect>,
}

pub struct ExpandedPipeline {
    pub elements: Vec<ExpandedPipelineElement>,
}

fn expand_path(word: &WordToken, env: &Environment) -> PathBuf {
    env.expand(word).join(" ").into()
}

impl ExpandedPipelineElement {
    pub fn from_syntax(env: &Environment, element: &PipelineElement) -> Self {
        // TODO redirects
        let mut words: Vec<_> = element
            .words
            .iter()
            .flat_map(|word| env.expand(word))
            .collect();
        let mut envs = vec![];

        let mut stdin = None;
        let mut stdout = None;
        let mut stderr = None;

        for redirect in &element.redirects {
            match redirect {
                Redirect::Input(input) => match input {
                    InputRedirect::Heredoc(_) => todo!("Heredoc input redirects"),
                    InputRedirect::Filename(filename) => {
                        let path = expand_path(filename, env);
                        stdin = Some(ExpandedInputRedirect::File(path));
                    }
                },
                Redirect::Output(output) => {
                    let source = match output.source {
                        Some("1") | Some("out") | None => &mut stdout,
                        Some("2") | Some("err") => &mut stderr,
                        Some(p) => todo!("Unhandled output redirect source: {p:?}"),
                    };
                    let target = match &output.target {
                        OutputRedirectTarget::Filename(filename) => {
                            ExpandedOutputRedirect::File(expand_path(filename, env), output.append)
                        }
                        OutputRedirectTarget::Descriptor(_) => todo!("Output redirect to FD"),
                    };
                    *source = Some(target);
                }
            }
        }

        words.retain(|word| {
            if let Some((key, value)) = word.split_once('=')
                && !key.ends_with(' ')
                && !value.starts_with(' ')
            {
                envs.push((key.to_owned(), value.to_owned()));
                false
            } else {
                true
            }
        });

        Self {
            words,
            envs,
            stdin,
            stdout,
            stderr,
        }
    }
}

impl ExpandedPipeline {
    pub fn from_syntax(env: &Environment, pipeline: &PipelineExpression) -> Self {
        let elements = pipeline
            .elements
            .iter()
            .map(|e| ExpandedPipelineElement::from_syntax(env, e))
            .collect();
        Self { elements }
    }
}
