use crate::{OutDest, Span, VarId, ast::Expression, engine::StateWorkingSet};
use serde::{Deserialize, Serialize};
use std::fmt::Display;

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
pub enum RedirectionSource {
    Stdout,
    Stderr,
    StdoutAndStderr,
}

impl Display for RedirectionSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            RedirectionSource::Stdout => "stdout",
            RedirectionSource::Stderr => "stderr",
            RedirectionSource::StdoutAndStderr => "stdout and stderr",
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RedirectionTarget {
    File {
        expr: Expression,
        append: bool,
        span: Span,
    },
    Pipe {
        span: Span,
    },
}

impl RedirectionTarget {
    pub fn span(&self) -> Span {
        match self {
            RedirectionTarget::File { span, .. } | RedirectionTarget::Pipe { span } => *span,
        }
    }

    pub fn expr(&self) -> Option<&Expression> {
        match self {
            RedirectionTarget::File { expr, .. } => Some(expr),
            RedirectionTarget::Pipe { .. } => None,
        }
    }

    pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
        self.expr().is_some_and(|e| e.has_in_variable(working_set))
    }

    pub fn replace_span(
        &mut self,
        working_set: &mut StateWorkingSet,
        replaced: Span,
        new_span: Span,
    ) {
        match self {
            RedirectionTarget::File { expr, .. } => {
                expr.replace_span(working_set, replaced, new_span)
            }
            RedirectionTarget::Pipe { .. } => {}
        }
    }

    pub fn replace_in_variable(
        &mut self,
        working_set: &mut StateWorkingSet<'_>,
        new_var_id: VarId,
    ) {
        match self {
            RedirectionTarget::File { expr, .. } => {
                expr.replace_in_variable(working_set, new_var_id)
            }
            RedirectionTarget::Pipe { .. } => {}
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PipelineRedirection {
    Single {
        source: RedirectionSource,
        target: RedirectionTarget,
    },
    Separate {
        out: RedirectionTarget,
        err: RedirectionTarget,
    },
}
impl PipelineRedirection {
    pub fn replace_in_variable(
        &mut self,
        working_set: &mut StateWorkingSet<'_>,
        new_var_id: VarId,
    ) {
        match self {
            PipelineRedirection::Single { source: _, target } => {
                target.replace_in_variable(working_set, new_var_id)
            }
            PipelineRedirection::Separate { out, err } => {
                out.replace_in_variable(working_set, new_var_id);
                err.replace_in_variable(working_set, new_var_id);
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineElement {
    pub pipe: Option<Span>,
    pub expr: Expression,
    pub redirection: Option<PipelineRedirection>,
}

impl PipelineElement {
    pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
        self.expr.has_in_variable(working_set)
            || self.redirection.as_ref().is_some_and(|r| match r {
                PipelineRedirection::Single { target, .. } => target.has_in_variable(working_set),
                PipelineRedirection::Separate { out, err } => {
                    out.has_in_variable(working_set) || err.has_in_variable(working_set)
                }
            })
    }

    pub fn replace_span(
        &mut self,
        working_set: &mut StateWorkingSet,
        replaced: Span,
        new_span: Span,
    ) {
        self.expr.replace_span(working_set, replaced, new_span);
        if let Some(expr) = self.redirection.as_mut() {
            match expr {
                PipelineRedirection::Single { target, .. } => {
                    target.replace_span(working_set, replaced, new_span)
                }
                PipelineRedirection::Separate { out, err } => {
                    out.replace_span(working_set, replaced, new_span);
                    err.replace_span(working_set, replaced, new_span);
                }
            }
        }
    }

    pub fn pipe_redirection(
        &self,
        working_set: &StateWorkingSet,
    ) -> (Option<OutDest>, Option<OutDest>) {
        self.expr.expr.pipe_redirection(working_set)
    }

    pub fn replace_in_variable(
        &mut self,
        working_set: &mut StateWorkingSet<'_>,
        new_var_id: VarId,
    ) {
        self.expr.replace_in_variable(working_set, new_var_id);
        if let Some(redirection) = &mut self.redirection {
            redirection.replace_in_variable(working_set, new_var_id);
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pipeline {
    pub elements: Vec<PipelineElement>,
}

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

impl Pipeline {
    pub fn new() -> Self {
        Self { elements: vec![] }
    }

    pub fn from_vec(expressions: Vec<Expression>) -> Pipeline {
        Self {
            elements: expressions
                .into_iter()
                .enumerate()
                .map(|(idx, expr)| PipelineElement {
                    pipe: if idx == 0 { None } else { Some(expr.span) },
                    expr,
                    redirection: None,
                })
                .collect(),
        }
    }

    pub fn len(&self) -> usize {
        self.elements.len()
    }

    pub fn is_empty(&self) -> bool {
        self.elements.is_empty()
    }

    pub fn pipe_redirection(
        &self,
        working_set: &StateWorkingSet,
    ) -> (Option<OutDest>, Option<OutDest>) {
        if let Some(first) = self.elements.first() {
            first.pipe_redirection(working_set)
        } else {
            (None, None)
        }
    }
}
