import { escapePostgresIdentifier } from "@databases/escape-identifier";
import baseSql, { SQLItemType } from "@databases/sql";
import { assert, unreachable } from "../../shared/src/asserts.js";
import { isPgStringType, isPgNumberType } from "../../zero-cache/src/types/pg-data-type.js";
const Z2S_COLLATION = "ucs_basic";
function formatPg(sql2) {
  const format = new ReusingFormat(escapePostgresIdentifier);
  return sql2.format((items) => formatFn(items, format));
}
function formatPgInternalConvert(sql2) {
  const format = new SQLConvertFormat(escapePostgresIdentifier);
  return sql2.format((items) => formatFn(items, format));
}
const sqlConvert = Symbol("fromJson");
function isSqlConvert(value) {
  return value !== null && typeof value === "object" && sqlConvert in value;
}
function sqlConvertSingularLiteralArg(value) {
  const arg = {
    [sqlConvert]: "literal",
    type: value === null ? "null" : typeof value,
    value,
    plural: false
  };
  return sql.value(arg);
}
function sqlConvertPluralLiteralArg(type, value) {
  const arg = {
    [sqlConvert]: "literal",
    type,
    value,
    plural: true
  };
  return sql.value(arg);
}
function sqlConvertColumnArg(serverColumnSchema, value, plural, isComparison) {
  return sql.value({
    [sqlConvert]: "column",
    type: serverColumnSchema.type,
    isEnum: serverColumnSchema.isEnum,
    value,
    plural: plural || serverColumnSchema.isArray,
    isComparison
  });
}
class ReusingFormat {
  #seen = /* @__PURE__ */ new Map();
  escapeIdentifier;
  constructor(escapeIdentifier) {
    this.escapeIdentifier = escapeIdentifier;
  }
  formatValue = (value) => {
    if (this.#seen.has(value)) {
      return {
        placeholder: `$${this.#seen.get(value)}`,
        value: PREVIOUSLY_SEEN_VALUE
      };
    }
    this.#seen.set(value, this.#seen.size + 1);
    return { placeholder: `$${this.#seen.size}`, value };
  };
}
function stringify(arg) {
  if (arg.value === null) {
    return null;
  }
  if (arg.plural) {
    return JSON.stringify(arg.value);
  }
  if (arg[sqlConvert] === "literal" && arg.type === "string") {
    return arg.value;
  }
  if (arg[sqlConvert] === "column" && (arg.isEnum || isPgStringType(arg.type))) {
    return arg.value;
  }
  return JSON.stringify(arg.value);
}
class SQLConvertFormat {
  #seen = /* @__PURE__ */ new Map();
  escapeIdentifier;
  constructor(escapeIdentifier) {
    this.escapeIdentifier = escapeIdentifier;
  }
  formatValue = (value) => {
    assert(isSqlConvert(value), "JsonPackedFormat can only take JsonPackArgs.");
    const key = value.value;
    if (this.#seen.has(key)) {
      return {
        placeholder: createPlaceholder(this.#seen.get(key), value),
        value: PREVIOUSLY_SEEN_VALUE
      };
    }
    this.#seen.set(key, this.#seen.size + 1);
    return {
      placeholder: createPlaceholder(this.#seen.size, value),
      value: stringify(value)
    };
  };
}
function createPlaceholder(index, arg) {
  if (arg.type === "null") {
    assert(arg.value === null, "Args of type 'null' must have value null");
    assert(!arg.plural, "Args of type 'null' must not be plural");
    return `$${index}`;
  }
  if (arg[sqlConvert] === "literal") {
    const collate = arg.type === "string" ? ` COLLATE "${Z2S_COLLATION}"` : "";
    const { value } = arg;
    if (Array.isArray(value)) {
      const elType = pgTypeForLiteralType(arg.type);
      return formatPlural(index, `value::${elType}${collate}`);
    }
    return `$${index}::text::${pgTypeForLiteralType(arg.type)}${collate}`;
  }
  const common = formatCommonToSingularAndPlural(index, arg);
  return arg.plural ? formatPlural(index, common) : common;
}
function formatCommonToSingularAndPlural(index, arg) {
  const valuePlaceholder = arg.plural ? "value" : `$${index}`;
  switch (arg.type) {
    case "date":
    case "timestamp":
    case "timestamp without time zone":
      return `to_timestamp(${valuePlaceholder}::text::bigint / 1000.0) AT TIME ZONE 'UTC'`;
    case "timestamptz":
    case "timestamp with time zone":
      return `to_timestamp(${valuePlaceholder}::text::bigint / 1000.0)`;
    // uuid doesn't support collation, so we compare as text
    case "uuid":
      return arg.isComparison ? `${valuePlaceholder}::text COLLATE "${Z2S_COLLATION}"` : `${valuePlaceholder}::text::uuid`;
  }
  if (arg.isEnum) {
    return arg.isComparison ? `${valuePlaceholder}::text COLLATE "${Z2S_COLLATION}"` : `${valuePlaceholder}::text::"${arg.type}"`;
  }
  if (isPgStringType(arg.type)) {
    return arg.isComparison ? `${valuePlaceholder}::text COLLATE "${Z2S_COLLATION}"` : `${valuePlaceholder}::text::${arg.type}`;
  }
  if (isPgNumberType(arg.type)) {
    return arg.isComparison ? `${valuePlaceholder}::text::double precision` : `${valuePlaceholder}::text::${arg.type}`;
  }
  return `${valuePlaceholder}::text::${arg.type}`;
}
function formatPlural(index, select) {
  return `ARRAY(
          SELECT ${select} FROM jsonb_array_elements_text($${index}::text::jsonb)
        )`;
}
function pgTypeForLiteralType(type) {
  switch (type) {
    case "boolean":
      return "boolean";
    case "number":
      return "double precision";
    case "string":
      return "text";
    default:
      unreachable();
  }
}
const sql = baseSql.default;
const PREVIOUSLY_SEEN_VALUE = Symbol("PREVIOUSLY_SEEN_VALUE");
function formatFn(items, { escapeIdentifier, formatValue }) {
  let text = "";
  const values = [];
  const localIdentifiers = /* @__PURE__ */ new Map();
  for (const item of items) {
    switch (item.type) {
      // If this is just raw text, we add it directly to the query text.
      case SQLItemType.RAW: {
        text += item.text;
        break;
      }
      // If we got a value SQL item, add a placeholder and add the value to our
      // placeholder values array.
      case SQLItemType.VALUE: {
        const { placeholder, value } = formatValue(item.value, values.length);
        text += placeholder;
        if (value !== PREVIOUSLY_SEEN_VALUE) {
          values.push(value);
        }
        break;
      }
      // If we got an identifier type, escape the strings and get a local
      // identifier for non-string identifiers.
      case SQLItemType.IDENTIFIER: {
        const names = item.names.length === 1 && typeof item.names[0] === "string" && item.names[0].includes(".") ? item.names[0].split(".") : item.names;
        text += names.map((name) => {
          if (typeof name === "string") return escapeIdentifier(name);
          if (!localIdentifiers.has(name))
            localIdentifiers.set(name, `__local_${localIdentifiers.size}__`);
          return escapeIdentifier(localIdentifiers.get(name));
        }).join(".");
        break;
      }
    }
  }
  if (text.trim()) {
    const lines = text.split("\n");
    const min = Math.min(
      ...lines.filter((l) => l.trim() !== "").map((l) => /^\s*/.exec(l)[0].length)
    );
    if (min) {
      text = lines.map((line) => line.substr(min)).join("\n");
    }
  }
  return {
    text: text.trim(),
    values
  };
}
export {
  Z2S_COLLATION,
  formatPg,
  formatPgInternalConvert,
  sql,
  sqlConvertColumnArg,
  sqlConvertPluralLiteralArg,
  sqlConvertSingularLiteralArg
};
//# sourceMappingURL=sql.js.map
