import { sql } from "./internal/sql.js";
function buildSelectQuery(tableName, columns, constraint, filters, order, reverse, start) {
  let query = sql`SELECT ${sql.join(
    Object.keys(columns).map((c) => sql.ident(c)),
    sql`,`
  )} FROM ${sql.ident(tableName)}`;
  const constraints = constraintsToSQL(constraint, columns);
  if (start) {
    constraints.push(gatherStartConstraints(start, reverse, order, columns));
  }
  if (filters) {
    constraints.push(filtersToSQL(filters));
  }
  if (constraints.length > 0) {
    query = sql`${query} WHERE ${sql.join(constraints, sql` AND `)}`;
  }
  return sql`${query} ${orderByToSQL(order, !!reverse)}`;
}
function constraintsToSQL(constraint, columns) {
  if (!constraint) {
    return [];
  }
  const constraints = [];
  for (const [key, value] of Object.entries(constraint)) {
    constraints.push(
      sql`${sql.ident(key)} = ${toSQLiteType(value, columns[key].type)}`
    );
  }
  return constraints;
}
function orderByToSQL(order, reverse) {
  if (reverse) {
    return sql`ORDER BY ${sql.join(
      order.map(
        (s) => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(
          s[1] === "asc" ? "desc" : "asc"
        )}`
      ),
      sql`, `
    )}`;
  } else {
    return sql`ORDER BY ${sql.join(
      order.map(
        (s) => sql`${sql.ident(s[0])} ${sql.__dangerous__rawValue(s[1])}`
      ),
      sql`, `
    )}`;
  }
}
function filtersToSQL(filters) {
  switch (filters.type) {
    case "simple":
      return simpleConditionToSQL(filters);
    case "and":
      return filters.conditions.length > 0 ? sql`(${sql.join(
        filters.conditions.map(
          (condition) => filtersToSQL(condition)
        ),
        sql` AND `
      )})` : sql`TRUE`;
    case "or":
      return filters.conditions.length > 0 ? sql`(${sql.join(
        filters.conditions.map(
          (condition) => filtersToSQL(condition)
        ),
        sql` OR `
      )})` : sql`FALSE`;
  }
}
function simpleConditionToSQL(filter) {
  const { op } = filter;
  if (op === "IN" || op === "NOT IN") {
    switch (filter.right.type) {
      case "literal":
        return sql`${valuePositionToSQL(
          filter.left
        )} ${sql.__dangerous__rawValue(
          filter.op
        )} (SELECT value FROM json_each(${JSON.stringify(
          filter.right.value
        )}))`;
      case "static":
        throw new Error(
          "Static parameters must be replaced before conversion to SQL"
        );
    }
  }
  return sql`${valuePositionToSQL(filter.left)} ${sql.__dangerous__rawValue(
    // SQLite's LIKE operator is case-insensitive by default, so we
    // convert ILIKE to LIKE and NOT ILIKE to NOT LIKE.
    filter.op === "ILIKE" ? "LIKE" : filter.op === "NOT ILIKE" ? "NOT LIKE" : filter.op
  )} ${valuePositionToSQL(filter.right)}`;
}
function valuePositionToSQL(value) {
  switch (value.type) {
    case "column":
      return sql.ident(value.name);
    case "literal":
      return sql`${toSQLiteType(value.value, getJsType(value.value))}`;
    case "static":
      throw new Error(
        "Static parameters must be replaced before conversion to SQL"
      );
  }
}
function getJsType(value) {
  if (value === null) {
    return "null";
  }
  return typeof value === "string" ? "string" : typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : "json";
}
function toSQLiteType(v, type) {
  switch (type) {
    case "boolean":
      return v === null ? null : v ? 1 : 0;
    case "number":
    case "string":
    case "null":
      return v;
    case "json":
      return JSON.stringify(v);
  }
}
function gatherStartConstraints(start, reverse, order, columnTypes) {
  const constraints = [];
  const { row: from, basis } = start;
  for (let i = 0; i < order.length; i++) {
    const group = [];
    const [iField, iDirection] = order[i];
    for (let j = 0; j <= i; j++) {
      if (j === i) {
        const constraintValue = toSQLiteType(
          from[iField],
          columnTypes[iField].type
        );
        if (iDirection === "asc") {
          if (!reverse) {
            group.push(
              sql`(${constraintValue} IS NULL OR ${sql.ident(iField)} > ${constraintValue})`
            );
          } else {
            group.push(
              sql`(${sql.ident(iField)} IS NULL OR ${sql.ident(iField)} < ${constraintValue})`
            );
          }
        } else {
          if (!reverse) {
            group.push(
              sql`(${sql.ident(iField)} IS NULL OR ${sql.ident(iField)} < ${constraintValue})`
            );
          } else {
            group.push(
              sql`(${constraintValue} IS NULL OR ${sql.ident(iField)} > ${constraintValue})`
            );
          }
        }
      } else {
        const [jField] = order[j];
        group.push(
          sql`${sql.ident(jField)} IS ${toSQLiteType(
            from[jField],
            columnTypes[jField].type
          )}`
        );
      }
    }
    constraints.push(sql`(${sql.join(group, sql` AND `)})`);
  }
  if (basis === "at") {
    constraints.push(
      sql`(${sql.join(
        order.map(
          (s) => sql`${sql.ident(s[0])} IS ${toSQLiteType(
            from[s[0]],
            columnTypes[s[0]].type
          )}`
        ),
        sql` AND `
      )})`
    );
  }
  return sql`(${sql.join(constraints, sql` OR `)})`;
}
export {
  buildSelectQuery,
  constraintsToSQL,
  filtersToSQL,
  orderByToSQL,
  toSQLiteType
};
//# sourceMappingURL=query-builder.js.map
