// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
//
// This file is copied from `shell_words` and modified so that '#' doesn't get
// counted as a comment (to make specifying hexadecimal colours easier).
//
// Source of original file: https://github.com/tmiasko/shell-words/blob/efe8162286fd684e7e4e7552c6ec303446675318/src/lib.rs
//
// Here is the copyright notice of the original file:
//
// Copyright 2018 Tomasz Miąsko
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE>
// or the MIT license <LICENSE-MIT>, at your option.
//
//! Process command line according to parsing rules of Unix shell as specified
//! in [Shell Command Language in POSIX.1-2008][posix-shell].
//!
//! [posix-shell]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html

/// An error returned when shell parsing fails.
use std::{mem, fmt, error::Error};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParseError;

impl fmt::Display for ParseError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		f.write_str("missing closing quote")
	}
}

impl Error for ParseError {}

enum State {
	/// Within a delimiter.
	Delimiter,
	/// After backslash, but before starting word.
	Backslash,
	/// Within an unquoted word.
	Unquoted,
	/// After backslash in an unquoted word.
	UnquotedBackslash,
	/// Within a single quoted word.
	SingleQuoted,
	/// Within a double quoted word.
	DoubleQuoted,
	/// After backslash inside a double quoted word.
	DoubleQuotedBackslash,
}

pub fn split(s: &str) -> Result<Vec<String>, ParseError> {
	use State::{Backslash, Delimiter, DoubleQuoted, DoubleQuotedBackslash, SingleQuoted, Unquoted, UnquotedBackslash};

	let mut words = Vec::new();
	let mut word = String::new();
	let mut chars = s.chars();
	let mut state = Delimiter;

	loop {
		let c = chars.next();
		state = match state {
			Delimiter => match c {
				None => break,
				Some('\'') => SingleQuoted,
				Some('\"') => DoubleQuoted,
				Some('\\') => Backslash,
				Some('\t' | ' ' | '\n') => Delimiter,
				Some(c) => {
					word.push(c);
					Unquoted
				}
			},
			Backslash => match c {
				None => {
					word.push('\\');
					words.push(mem::take(&mut word));
					break;
				}
				Some('\n') => Delimiter,
				Some(c) => {
					word.push(c);
					Unquoted
				}
			},
			Unquoted => match c {
				None => {
					words.push(mem::take(&mut word));
					break;
				}
				Some('\'') => SingleQuoted,
				Some('\"') => DoubleQuoted,
				Some('\\') => UnquotedBackslash,
				Some('\t' | ' ' | '\n') => {
					words.push(mem::take(&mut word));
					Delimiter
				}
				Some(c) => {
					word.push(c);
					Unquoted
				}
			},
			UnquotedBackslash => match c {
				None => {
					word.push('\\');
					words.push(mem::take(&mut word));
					break;
				}
				Some('\n') => Unquoted,
				Some(c) => {
					word.push(c);
					Unquoted
				}
			},
			SingleQuoted => match c {
				None => return Err(ParseError),
				Some('\'') => Unquoted,
				Some(c) => {
					word.push(c);
					SingleQuoted
				}
			},
			DoubleQuoted => match c {
				None => return Err(ParseError),
				Some('\"') => Unquoted,
				Some('\\') => DoubleQuotedBackslash,
				Some(c) => {
					word.push(c);
					DoubleQuoted
				}
			},
			DoubleQuotedBackslash => match c {
				None => return Err(ParseError),
				Some('\n') => DoubleQuoted,
				Some(c @ ('$' | '`' | '"' | '\\')) => {
					word.push(c);
					DoubleQuoted
				}
				Some(c) => {
					word.push('\\');
					word.push(c);
					DoubleQuoted
				}
			},
		}
	}

	Ok(words)
}
