export class TableQueryBuilder {
  private options: TableQueryOptions;

  constructor(options: TableQueryOptions) {
    this.options = options;
  }

  private async buildCTE(): Promise<QueryResult> {
    if (!this.options.withCTE?.length) {
      return { query: "" };
    }

    const cteParts: string[] = [];

    for (const cte of this.options.withCTE) {
      let cteQuery: string;

      if (cte.query) {
        const builder = new TableQueryBuilder(cte.query);
        const { query } = await builder.build();
        cteQuery = query;
      } else if (cte.rawQuery) {
        cteQuery = cte.rawQuery;
      } else {
        continue;
      }

      cteParts.push(`${cte.name} AS (\n${cteQuery}\n)`);
    }

    return {
      query: cteParts.length ? `WITH ${cteParts.join(",\n")}` : "",
    };
  }

  private buildSelect(): string {
    const selectFields = this.options.select?.length
      ? this.options.select.join(", ")
      : "*";
    return `SELECT ${selectFields}`;
  }

  private buildJoins(): string {
    if (!this.options.joins?.length) return "";
    return this.options.joins.join("\n");
  }

  private buildWhere(): string {
    const conditions: string[] = [...(this.options.where || [])];

    if (!conditions.length) return "";

    return `(${conditions.join(" AND ")})`;
  }

  private buildOrWhere(): string {
    const conditions: string[] = [...(this.options.orWhere || [])];

    if (!conditions.length) return "";
    return `(${conditions.join(" OR ")})`;
  }

  private buildWhereString(): string {
    const where = this.buildWhere();
    const orWhere = this.buildOrWhere();

    if (!where && !orWhere) return "";
    return `WHERE ${[where, orWhere].filter(Boolean).join(" AND ")}`;
  }

  private buildOrderBy(): string {
    if (!this.options.orderBy?.length) return "";
    return `ORDER BY ${this.options.orderBy.join(", ")}`;
  }

  private buildGroupBy(): string {
    if (!this.options.groupBy?.length) return "";
    return `GROUP BY ${this.options.groupBy.join(", ")}`;
  }

  private buildLimit(): string {
    if (!this.options.limit) return "";

    const [limit, offset] = this.options.limit;

    return `LIMIT ${limit} OFFSET ${offset}`;
  }

  async build(): Promise<QueryResult> {
    const cte = await this.buildCTE();

    const parts = [
      cte.query,
      this.buildSelect(),
      `FROM ${this.options.table}`,
      this.buildJoins(),
      this.buildWhereString(),
      this.buildGroupBy(),
      this.buildOrderBy(),
      this.buildLimit(),
    ].filter(Boolean);

    return {
      query: parts.join("\n"),
    };
  }
}
