use std::{collections::HashMap, process::Command};

use crate::syntax::lex::{DoubleQuotedSegment, WordSegment, WordToken};

#[derive(Default, Debug, PartialEq)]
pub struct LocalEnvironment {
    variables: HashMap<String, Vec<String>>,
}

#[derive(Default, Debug, PartialEq)]
pub struct Environment {
    local_stack: Vec<LocalEnvironment>,
    aliases: HashMap<String, String>,
}

#[derive(Default, Debug, PartialEq)]
pub struct Expander {
    words: Vec<String>,
}

impl LocalEnvironment {
    fn lookup(&self, name: &str) -> Option<&Vec<String>> {
        self.variables.get(name)
    }

    fn insert_literal(&mut self, name: impl Into<String>, value: impl Into<String>) {
        self.variables.insert(name.into(), vec![value.into()]);
    }

    fn to_command(&self, command: &mut Command) {
        for (key, value) in self.variables.iter() {
            command.env(key, value.join(" "));
        }
    }
}

impl Environment {
    pub fn push_environment(&mut self) {
        self.local_stack.push(LocalEnvironment {
            variables: HashMap::new(),
        });
    }

    pub fn pop_environment(&mut self) {
        self.local_stack.pop();
    }

    pub fn current(&self) -> Option<&LocalEnvironment> {
        self.local_stack.last()
    }

    pub fn current_mut(&mut self) -> Option<&mut LocalEnvironment> {
        self.local_stack.last_mut()
    }

    fn env_stack(&self) -> impl Iterator<Item = &LocalEnvironment> {
        self.local_stack.iter().rev()
    }

    pub fn lookup_current(&self, name: &str) -> Option<&Vec<String>> {
        self.env_stack().find_map(|e| e.lookup(name))
    }

    pub fn lookup_alias(&self, name: &str) -> Option<&String> {
        self.aliases.get(name)
    }

    pub fn insert_current_literal(&mut self, name: impl Into<String>, value: impl Into<String>) {
        if let Some(current) = self.current_mut() {
            current.insert_literal(name, value);
        } else {
            std::env::set_var(name.into(), value.into());
        }
    }

    pub fn insert_alias(&mut self, name: impl Into<String>, value: impl Into<String>) {
        self.aliases.insert(name.into(), value.into());
    }

    pub fn to_command(&self, command: &mut Command) {
        for env in self.local_stack.iter() {
            env.to_command(command);
        }
    }

    pub fn expand(&self, word: &WordToken) -> Vec<String> {
        let mut this = Expander {
            words: vec!["".into()],
        };
        for segment in word.0.iter() {
            this.expand_segment(segment, self);
        }
        this.build()
    }
}

impl Expander {
    fn last_mut(&mut self) -> &mut String {
        if self.words.is_empty() {
            self.words.push(String::new());
        }
        let len = self.words.len();
        &mut self.words[len - 1]
    }

    fn write_to_last(&mut self, value: &str) {
        self.last_mut().push_str(value);
    }

    fn push_char(&mut self, ch: char) {
        self.last_mut().push(ch);
    }

    fn finish_word(&mut self) {
        self.words.push(String::new());
    }

    pub fn write(&mut self, value: &[String], quoted: bool) {
        let word_count = value.len();
        for (i, words) in value.iter().enumerate() {
            if quoted {
                self.write_to_last(words);
                if i != word_count - 1 {
                    self.finish_word();
                }
            } else {
                let subword_count = words.split_whitespace().count();
                for (j, word) in words.split_whitespace().enumerate() {
                    self.write_to_last(word);
                    if i != word_count - 1 || j != subword_count - 1 {
                        self.finish_word();
                    }
                }
            }
        }
    }

    fn expand_quoted(&mut self, segment: &DoubleQuotedSegment, env: &Environment) {
        match segment {
            &DoubleQuotedSegment::Literal(text) => self.write_to_last(text),
            &DoubleQuotedSegment::Var(name) if let Some(value) = env.lookup_current(name) => {
                self.write(value, true);
            }
            &DoubleQuotedSegment::Var(name) if let Ok(value) = std::env::var(name) => {
                self.write(&[value], true);
            }
            &DoubleQuotedSegment::Var(_) => (),
            DoubleQuotedSegment::Escape(escape) if let Some(ch) = escape.to_char() => {
                self.push_char(ch)
            }
            DoubleQuotedSegment::Escape(_) => todo!(),
        }
    }

    pub fn expand_segment(&mut self, segment: &WordSegment, env: &Environment) {
        match segment {
            &WordSegment::Literal(text) | &WordSegment::SingleQuote(text) => {
                self.write_to_last(text)
            }
            &WordSegment::Var(name) if let Some(value) = env.lookup_current(name) => {
                self.write(value, false);
            }
            &WordSegment::Var(name) if let Ok(value) = std::env::var(name) => {
                self.write(&[value], false);
            }
            &WordSegment::Var(_) => (),
            WordSegment::Escape(escape) if let Some(ch) = escape.to_char() => {
                self.push_char(ch);
            }
            WordSegment::Escape(_) => todo!(),
            WordSegment::DoubleQuote(quoted) => {
                for seg in quoted {
                    self.expand_quoted(seg, env);
                }
            }
        }
    }

    pub fn build(self) -> Vec<String> {
        self.words
    }
}
