import type { Atom } from "./db"

export type WhereClause = Atom | Record<string, Atom>

type QueryExtras = {
    limit?: number
    offset?: number
    order?: string
}

// `query` types:
// - string: WHERE clause, ex "name = 'bob'"
// - number: id, ex 502
// - object: key-value pairs, ex { name: "bob", age: 20 }
//
// special object keys:
// - limit: number
// - offset: number
// - order: string
//
// examples:
//   buildWhere("id = 502") // "WHERE id = 502"
//   buildWhere("id = $id", { id: 502 }) // "WHERE id = 502"
//   buildWhere({ id: 502 }) // "WHERE id = $id"
//   buildWhere({ name: "bob" }) // "WHERE name = $name"
//   buildWhere({ name: "bob", age: 20 }) // "WHERE name = $name AND age = $age"
//   buildWhere({ name: "bob", age: 20, limit: 10 }) // "WHERE name = $name AND age = $age LIMIT 10"
//   buildWhere({ name: "bob", age: 20, limit: 10, offset: 5 }) // "WHERE name = $name AND age = $age LIMIT 10 OFFSET 5"
//   buildWhere("id = $id", { id: 502, limit: 10, offset: 5 }) // "WHERE id = $id LIMIT 10 OFFSET 5"
export function buildWhere(query: WhereClause, args?: WhereClause): string {
    if (!query) throw new Error("Invalid query")

    if (typeof query === "object" && Array.isArray(query)) {
        throw new Error("Invalid query")
    }

    let after: string[] = []

    if (typeof query === "string" && args && typeof args === "object" && !Array.isArray(args)) {
        const conditions: string[] = []
        Object.keys(args).forEach((key) => {
            if (key === "limit") {
                after.push(`LIMIT ${(args as QueryExtras).limit}`)
            } else if (key === "offset") {
                after.push(`OFFSET ${(args as QueryExtras).offset}`)
            } else if (key === "order") {
                after.push(`ORDER BY ${(args as QueryExtras).order}`)
            } else if (!query.includes(`$${key}`)) {
                conditions.push(`${key} = $${key}`)
            }
        })
        const whereClause = [query, ...conditions].filter(Boolean).join(" AND ")
        return whereClause ? `WHERE ${whereClause} ${after.join(" ")}`.trim() : after.join(" ")
    }

    switch (typeof query) {
        case "string":
            return `WHERE ${query}`
        case "object":
            return buildObjectWhere(query as Record<string, any>, after)
        default:
            throw new Error("Invalid query")
    }
}

function buildObjectWhere(query: Record<string, any>, after: string[]): string {
    const conditions: string[] = []

    Object.keys(query).forEach((key) => {
        if (key === "limit") {
            after.push(`LIMIT ${query[key]}`)
        } else if (key === "offset") {
            after.push(`OFFSET ${query[key]}`)
        } else if (key === "order") {
            after.push(`ORDER BY ${query[key]}`)
        } else {
            conditions.push(`${key} = $${key}`)
        }
    })

    const whereClause = conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""
    const afterClause = after.join(" ")

    return [whereClause, afterClause].filter(Boolean).join(" ")
}
