import type { BindValue, Database } from "@db/sqlite";

/**
 * Construct a SQL query with placeholders for values.
 *
 * This function takes a template string and interpolates it with the given
 * values, replacing placeholders with `?`.
 *
 * @example
 * const query = sqlPartial`SELECT * FROM events WHERE id = ${1} OR id = ${2}`;
 * // query = {
 * //   query: 'SELECT * FROM events WHERE id = ? OR id = ?',
 * //   values: ['1', '2']
 * // }
 *
 * @param {TemplateStringsArray} segments A template string
 * @param {...BindValue[]} values Values to interpolate
 * @returns {{ query: string, values: BindValue[] }} A SQL query with placeholders
 */
export function sqlPartial(
  segments: TemplateStringsArray,
  ...values: BindValue[]
) {
  return {
    query: segments.reduce(
      (acc, str, i) => acc + str + (i < values.length ? "?" : ""),
      "",
    ),
    values: values,
  };
}

/**
 * Construct a SQL query with placeholders for values and return a function
 * that executes that query on a database.
 *
 * This is a convenience wrapper around `sqlPartial` and `sqlPartialRunner`.
 *
 * @example
 * const run = sql`SELECT * FROM events WHERE id = ${1} OR id = ${2}`,
 * const results = run(db);
 *
 * @param {TemplateStringsArray} segments A template string
 * @param {...BindValue[]} values Values to interpolate
 * @returns {Function} A function that takes a Database and returns the query results
 */
export function sql(segments: TemplateStringsArray, ...values: BindValue[]) {
  return sqlPartialRunner(sqlPartial(segments, ...values));
}

/**
 * Combine multiple partial queries into a single query.
 *
 * This function takes any number of partial queries with values and combines
 * them into a single query.
 *
 * @example
 * const query1 = { query: 'SELECT * FROM foo', values: [] };
 * const query2 = { query: 'WHERE bar = ?', values: ['5'] };
 * const query = mixQuery(query1, query2);
 * // query = {
 * //   query: 'SELECT * FROM foo WHERE bar = ?',
 * //   values: ['5']
 * // }
 *
 * @param {...{ query: string, values: BindValue[] }} queries Partial queries
 * @returns {{ query: string, values: BindValue[] }} A combined query
 */
export function mixQuery(...queries: { query: string; values: BindValue[] }[]) {
  const { query, values } = queries.reduce(
    (acc, { query, values }) => ({
      query: `${acc.query} ${query}`,
      values: [...acc.values, ...values],
    }),
    { query: "", values: [] },
  );
  return { query, values };
}

/**
 * Executes a SQL query against a database.
 *
 * This function takes a query object containing a SQL query string with placeholders
 * and an array of values. It returns a function that, when given a Database instance,
 * prepares the query and executes it, returning all results.
 *
 * @param {Object} query An object containing the SQL query string and corresponding values
 * @returns {Function} A function that takes a Database instance and returns the query results
 */

export function sqlPartialRunner(query: {
  query: string;
  values: BindValue[];
}) {
  const run = (db: Database) => db.prepare(query.query).all(...query.values);
  return run;
}