use {
    super::{ExprNode, SelectItemNode},
    crate::{
        ast::SelectItem,
        ast_builder::ExprWithAliasNode,
        parse_sql::parse_select_items,
        result::{Error, Result},
        translate::{NO_PARAMS, translate_select_item},
    },
};

#[derive(Clone, Debug)]
pub enum SelectItemList<'a> {
    Text(String),
    SelectItems(Vec<SelectItemNode<'a>>),
}

impl From<&str> for SelectItemList<'_> {
    fn from(exprs: &str) -> Self {
        SelectItemList::Text(exprs.to_owned())
    }
}

impl From<Vec<&str>> for SelectItemList<'_> {
    fn from(select_items: Vec<&str>) -> Self {
        SelectItemList::SelectItems(select_items.into_iter().map(Into::into).collect())
    }
}

impl<'a> From<ExprNode<'a>> for SelectItemList<'a> {
    fn from(expr_node: ExprNode<'a>) -> Self {
        SelectItemList::SelectItems(vec![expr_node.into()])
    }
}

impl<'a> From<Vec<ExprNode<'a>>> for SelectItemList<'a> {
    fn from(expr_nodes: Vec<ExprNode<'a>>) -> Self {
        SelectItemList::SelectItems(expr_nodes.into_iter().map(Into::into).collect())
    }
}

impl<'a> From<ExprWithAliasNode<'a>> for SelectItemList<'a> {
    fn from(expr_node: ExprWithAliasNode<'a>) -> Self {
        SelectItemList::SelectItems(vec![expr_node.into()])
    }
}

impl<'a> From<Vec<ExprWithAliasNode<'a>>> for SelectItemList<'a> {
    fn from(expr_nodes: Vec<ExprWithAliasNode<'a>>) -> Self {
        SelectItemList::SelectItems(expr_nodes.into_iter().map(Into::into).collect())
    }
}

impl<'a> TryFrom<SelectItemList<'a>> for Vec<SelectItem> {
    type Error = Error;

    fn try_from(select_items: SelectItemList<'a>) -> Result<Self> {
        match select_items {
            SelectItemList::Text(items) => parse_select_items(items)?
                .iter()
                .map(|item| translate_select_item(item, NO_PARAMS))
                .collect::<Result<Vec<_>>>(),
            SelectItemList::SelectItems(items) => items
                .into_iter()
                .map(TryInto::try_into)
                .collect::<Result<Vec<_>>>(),
        }
    }
}

#[cfg(test)]
mod tests {
    use {
        crate::{
            ast::SelectItem,
            ast_builder::{SelectItemList, col, expr},
            parse_sql::parse_select_items,
            result::Result,
            translate::{NO_PARAMS, translate_select_item},
        },
        pretty_assertions::assert_eq,
    };

    fn test(actual: SelectItemList, expected: &str) {
        let parsed = parse_select_items(expected).expect(expected);
        let expected = parsed
            .iter()
            .map(|item| translate_select_item(item, NO_PARAMS))
            .collect::<Result<Vec<SelectItem>>>();

        assert_eq!(actual.try_into(), expected);
    }

    #[test]
    fn select_item_list() {
        let actual = "id, name".into();
        let expected = "id, name";
        test(actual, expected);

        let actual = vec!["id", "name"].into();
        let expected = "id, name";
        test(actual, expected);

        let actual = col("id").into();
        let expected = "id";
        test(actual, expected);

        let actual = vec![col("id"), "name".into()].into();
        let expected = "id, name";
        test(actual, expected);

        let actual = col("id").sub(1).alias_as("new_id").into();
        let expected = "id - 1 AS new_id";
        test(actual, expected);

        let actual = vec![
            col("age").avg().alias_as("avg_age"),
            expr("name || ':foo'").alias_as("res"),
        ]
        .into();
        let expected = "AVG(age) AS avg_age, name || ':foo' AS res";
        test(actual, expected);
    }
}
