///|
/// What a `FROM` or a `JOIN` names. A bare table is `Tbl`; `Sub` is a derived table
/// (a whole `Select`, whose bound values thread into the enclosing statement's
/// params at the position the subquery renders); `Alias` names either of those, which
/// PostgreSQL and MySQL require of a derived table; `Lateral` lets a derived table
/// reference the columns of the sources to its left (PostgreSQL and MySQL 8.0.14+;
/// SQLite has no LATERAL).
pub(all) enum Source {
  Tbl(String)
  Sub(Select)
  Alias(Source, String)
  Lateral(Source)
}

///|
/// What can stand where a table is expected. A `String` is the table name — so
/// `select("users")` and `"teams".join(…)` keep reading as they always did — and a
/// `Source` is anything richer (a derived table, an alias, a LATERAL).
pub(open) trait AsSource {
  fn to_source(Self) -> Source
}

///|
pub impl AsSource for String with fn to_source(self) {
  Tbl(self)
}

///|
pub impl AsSource for Source with fn to_source(self) {
  self
}

///|
/// Render a source, appending any bound values a derived table carries to `params`
/// so they stay in the SQL text's left-to-right order.
fn source_sql(src : Source, params : Array[Value]) -> String {
  match src {
    Tbl(name) => name
    Sub(q) => {
      let (sub_sql, sub_params) = q.build()
      for p in sub_params {
        params.push(p)
      }
      "(" + sub_sql + ")"
    }
    Alias(inner, name) => source_sql(inner, params) + " AS " + name
    Lateral(inner) => "LATERAL " + source_sql(inner, params)
  }
}

///|
/// A single JOIN clause: its keyword (`JOIN` / `LEFT JOIN` / …), the joined source,
/// and either a raw `ON` predicate (an identifier-level expression, never a bound
/// value) or a `USING` column list. A `CROSS JOIN` carries neither.
struct JoinClause {
  kind : String
  src : Source
  on : String
  using_ : Array[String]
}

///|
/// A common table expression: `name AS (subquery)`. CTEs render in a single leading
/// `WITH` clause and their bound parameters come first in the final params list,
/// matching their leading position in the SQL text. When `recursive`, the whole
/// clause is a `WITH RECURSIVE` (the keyword modifies the clause, not the entry, so
/// one recursive CTE turns the leading `WITH` recursive for all of them).
struct Cte {
  name : String
  sub : Select
  recursive : Bool
}

///|
pub extend Source with AsSource::{to_source}