//! Assorted utilities for dynamically constructing and manipulating [`sqlx`]
//! queries.

use sqlx::{Postgres, QueryBuilder};

use crate::datum::Datum;

/// Representation of a partial, parameterized SQL query. Allows callers to
/// build queries iteratively and dynamically, handling parameter numbering
/// (`$1`, `$2`, `$3`, ...) automatically.
///
/// This is similar to [`sqlx::QueryBuilder`], except that [`QueryFragment`]
/// objects are composable and may be concatenated to each other.
#[derive(Clone, Debug, PartialEq)]
pub struct QueryFragment {
    /// SQL string, split wherever there is a query parameter. For example,
    /// `select * from foo where id = $1 and status = $2` is represented along
    /// the lines of `["select * from foo where id = ", " and status = ", ""]`.
    /// `plain_sql` should always have exactly one more element than `params`.
    plain_sql: Vec<String>,
    params: Vec<Datum>,
}

impl QueryFragment {
    /// Validate invariants. Should be run immediately before returning any
    /// useful output.
    fn gut_checks(&self) {
        assert!(self.plain_sql.len() == self.params.len() + 1);
    }

    /// Parse from a SQL string with no parameters.
    pub fn from_sql(sql: &str) -> Self {
        Self {
            plain_sql: vec![sql.to_owned()],
            params: vec![],
        }
    }

    /// Convenience function to construct an empty value.
    pub fn empty() -> Self {
        Self::from_sql("")
    }

    /// Parse from a parameter value with no additional SQL. (Renders as `$n`,
    /// where`n` is the appropriate parameter index.)
    pub fn from_param(param: Datum) -> Self {
        Self {
            plain_sql: vec!["".to_owned(), "".to_owned()],
            params: vec![param],
        }
    }

    /// Append another query fragment to this one.
    pub fn push(&mut self, mut other: QueryFragment) {
        let tail = self
            .plain_sql
            .pop()
            .expect("already asserted that vec contains at least 1 item");
        let head = other
            .plain_sql
            .first()
            .expect("already asserted that vec contains at least 1 item");
        self.plain_sql.push(format!("{tail}{head}"));
        for value in other.plain_sql.drain(1..) {
            self.plain_sql.push(value);
        }
        self.params.append(&mut other.params);
    }

    /// Combine multiple QueryFragments with a separator, similar to
    /// [`Vec::join`].
    pub fn join<I: IntoIterator<Item = Self>>(fragments: I, sep: Self) -> Self {
        let mut acc = QueryFragment::from_sql("");
        let mut iter = fragments.into_iter();
        let mut fragment = match iter.next() {
            Some(value) => value,
            None => return acc,
        };
        for next_fragment in iter {
            acc.push(fragment);
            acc.push(sep.clone());
            fragment = next_fragment;
        }
        acc.push(fragment);
        acc
    }

    /// Convenience method equivalent to:
    /// `QueryFragment::concat(fragments, QueryFragment::from_sql(""))`
    pub fn concat<I: IntoIterator<Item = Self>>(fragments: I) -> Self {
        Self::join(fragments, Self::from_sql(""))
    }

    /// Checks whether value is empty. A value is considered empty if the
    /// resulting SQL code is 0 characters long.
    pub fn is_empty(&self) -> bool {
        self.gut_checks();
        self.plain_sql.len() == 1
            && self
                .plain_sql
                .first()
                .expect("already checked that len == 1")
                .is_empty()
    }
}

impl From<QueryFragment> for QueryBuilder<'_, Postgres> {
    fn from(value: QueryFragment) -> Self {
        value.gut_checks();
        let mut builder = QueryBuilder::new("");
        let mut param_iter = value.params.into_iter();
        for plain_sql in value.plain_sql {
            builder.push(plain_sql);
            if let Some(param) = param_iter.next() {
                param.push_bind_onto(&mut builder);
            }
        }
        builder
    }
}

/// Helper type to make it easier to build and reason about multiple related SQL
/// queries.
#[derive(Clone, Debug)]
pub struct SelectQuery {
    /// Query fragment following (not including) "select ".
    pub selection: QueryFragment,

    /// Query fragment following (not including) "from ".
    pub source: QueryFragment,

    /// Query fragment following (not including) "where ", or empty if not
    /// applicable.
    pub filters: QueryFragment,

    /// Query fragment following (not including) "order by ", or empty if not
    /// applicable.
    pub order: QueryFragment,

    /// Query fragment following (not including) "limit ", or empty if not
    /// applicable.
    pub limit: QueryFragment,
}

impl From<SelectQuery> for QueryFragment {
    fn from(value: SelectQuery) -> Self {
        let mut result = QueryFragment::from_sql("select ");
        result.push(value.selection);
        result.push(QueryFragment::from_sql(" from "));
        result.push(value.source);
        if !value.filters.is_empty() {
            result.push(QueryFragment::from_sql(" where "));
            result.push(value.filters);
        }
        if !value.order.is_empty() {
            result.push(QueryFragment::from_sql(" order by "));
            result.push(value.order);
        }
        if !value.limit.is_empty() {
            result.push(QueryFragment::from_sql(" limit "));
            result.push(value.limit);
        }
        result
    }
}

impl From<SelectQuery> for QueryBuilder<'_, Postgres> {
    fn from(value: SelectQuery) -> Self {
        QueryFragment::from(value).into()
    }
}
