// Generated by nautilus-codegen. Do not edit manually.

import { NautilusClient } from '../_internal/_client';
import { NotFoundError } from '../_internal/_errors';
import { PROTOCOL_VERSION } from '../_internal/_protocol';
{%- if has_enums %}
import { {% for e in enum_imports %}{{ e }}{% if not loop.last %}, {% endif %}{% endfor %} } from '../enums';
{%- endif %}
{%- if has_composite_types %}
import { {% for t in composite_type_imports %}{{ t }}{% if not loop.last %}, {% endif %}{% endfor %} } from '../types';
{%- endif %}

/** Maps `table__dbColumn` keys returned by the engine to JS field names. */
const _{{ table_name }}RowMap: Record<string, string> = {
{%- for f in scalar_fields %}
  '{{ table_name }}__{{ f.db_name }}': '{{ f.name }}',
{%- endfor %}
};

const _{{ table_name }}Fields = new Set<string>([
{%- for f in scalar_fields %}
  '{{ f.name }}',
{%- endfor %}
]);

const _{{ table_name }}ArrayFields = new Set<string>([
{%- for f in scalar_fields %}
{%- if f.is_array %}
  '{{ f.name }}',
{%- endif %}
{%- endfor %}
]);

const _{{ table_name }}BooleanFields = new Set<string>([
{%- for f in scalar_fields %}
{%- if f.base_type == "boolean" %}
  '{{ f.name }}',
{%- endif %}
{%- endfor %}
]);

/** Maps JS / logical field names to DB column names (handles @map renames). */
const _{{ table_name }}FieldToDb: Record<string, string> = {
{%- for f in scalar_fields %}
  '{{ f.logical_name }}': '{{ f.db_name }}',
{%- if f.name != f.logical_name %}
  '{{ f.name }}': '{{ f.db_name }}',
{%- endif %}
{%- endfor %}
};

function _coerceBooleanValue(value: unknown): unknown {
  if (typeof value === 'boolean') return value;
  if (typeof value === 'number' && (value === 0 || value === 1)) return value === 1;
  if (typeof value === 'string') {
    const normalized = value.toLowerCase();
    if (normalized === 'true' || normalized === '1') return true;
    if (normalized === 'false' || normalized === '0') return false;
  }
  return value;
}

function _coerce{{ model_name }}(row: Record<string, unknown>): {{ model_name }}Model {
  const out: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(row)) {
    const field = _{{ table_name }}RowMap[k] ?? k;
    if (!_{{ table_name }}Fields.has(field)) continue;
    if (v == null) {
      out[field] = _{{ table_name }}ArrayFields.has(field) ? [] : null;
      continue;
    }
    let coerced: unknown = _{{ table_name }}BooleanFields.has(field)
      ? _coerceBooleanValue(v)
      : v;
{%- for f in scalar_fields %}
{%- if f.base_type == "Date" %}
    if (field === '{{ f.name }}' && typeof v === 'string') coerced = new Date(v);
{%- elif f.base_type == "Buffer" %}
    if (field === '{{ f.name }}' && typeof v === 'string') coerced = Buffer.from(v, 'base64');
{%- endif %}
{%- endfor %}
    out[field] = coerced;
  }
  return out as {{ model_name }}Model;
}

export interface StringFilter {
  contains?:   string;
  startsWith?: string;
  endsWith?:   string;
  in?:         string[];
  notIn?:      string[];
  not?:        string;
  isNull?:     boolean;
}

export interface NumberFilter {
  lt?:     number;
  lte?:    number;
  gt?:     number;
  gte?:    number;
  in?:     number[];
  notIn?:  number[];
  not?:    number;
  isNull?: boolean;
}

export interface DateFilter {
  lt?:     Date;
  lte?:    Date;
  gt?:     Date;
  gte?:    Date;
  in?:     Date[];
  notIn?:  Date[];
  not?:    Date;
  isNull?: boolean;
}

export interface VectorFilter {
  equals?: number[];
  not?:    number[];
  isNull?: boolean;
}

export type VectorMetric = 'l2' | 'innerProduct' | 'cosine';
export type {{ model_name }}VectorFieldKeys = {% if has_vector_fields %}{% for name in vector_field_names %}'{{ name }}'{% if not loop.last %} | {% endif %}{% endfor %}{% else %}never{% endif %};
export interface {{ model_name }}NearestInput {
  field:  {{ model_name }}VectorFieldKeys;
  query:  number[];
  metric: VectorMetric;
}

export type SortOrder = 'asc' | 'desc';

export interface {{ model_name }}Model {
{%- for f in scalar_fields %}
{%- if f.doc_comment %}
  /** {{ f.doc_comment }} */
{%- endif %}
  {{ f.name }}: {{ f.ts_type }};
{%- endfor %}
{%- for f in relation_fields %}
{%- if f.doc_comment %}
  /** {{ f.doc_comment }} */
{%- endif %}
  {{ f.name }}?: {{ f.ts_type }};
{%- endfor %}
}

export interface {{ model_name }}WhereInput {
{%- for field in where_input_fields %}
{%- if field.is_vector %}
  {{ field.name }}?: number[]{% if field.is_nullable %} | null{% endif %} | VectorFilter;
{%- elif field.base_type == "string" %}
  {{ field.name }}?: string{% if field.is_nullable %} | null{% endif %} | StringFilter;
{%- elif field.base_type == "number" %}
  {{ field.name }}?: number{% if field.is_nullable %} | null{% endif %} | NumberFilter;
{%- elif field.base_type == "Date" %}
  {{ field.name }}?: Date{% if field.is_nullable %} | null{% endif %} | DateFilter;
{%- elif field.base_type == "boolean" %}
  {{ field.name }}?: boolean{% if field.is_nullable %} | null{% endif %};
{%- else %}
  {{ field.name }}?: {{ field.ts_type }};
{%- endif %}
{%- endfor %}
  AND?: {{ model_name }}WhereInput | {{ model_name }}WhereInput[];
  OR?:  {{ model_name }}WhereInput[];
  NOT?: {{ model_name }}WhereInput | {{ model_name }}WhereInput[];
}

export interface {{ model_name }}CreateInput {
{%- for field in create_input_fields %}
{%- if field.is_required %}
  {{ field.name }}: {{ field.ts_type }};
{%- else %}
  {{ field.name }}?: {{ field.ts_type }};
{%- endif %}
{%- endfor %}
}

export interface {{ model_name }}UpdateInput {
{%- for field in update_input_fields %}
  {{ field.name }}?: {{ field.ts_type }};
{%- endfor %}
}

export interface {{ model_name }}OrderByInput {
{%- for field in order_by_fields %}
  {{ field.name }}?: SortOrder;
{%- endfor %}
}

export interface {{ model_name }}SelectInput {
{%- for field in scalar_fields %}
  {{ field.name }}?: boolean;
{%- endfor %}
}

export type {{ model_name }}PrimaryKeyKeys = {% if primary_key_fields | length > 0 %}{% for f in primary_key_fields %}'{{ f }}'{% if not loop.last %} | {% endif %}{% endfor %}{% else %}never{% endif %};
export type {{ model_name }}TruthySelectKeys<S extends {{ model_name }}SelectInput> = {
  [K in keyof S]-?: S[K] extends true ? K : never;
}[keyof S] & keyof {{ model_name }}Model;
export type {{ model_name }}Selected<S extends {{ model_name }}SelectInput> = Pick<
  {{ model_name }}Model,
  {{ model_name }}PrimaryKeyKeys | {{ model_name }}TruthySelectKeys<S>
>;

{%- if has_includes %}

export interface {{ model_name }}IncludeInput {
{%- for field in include_fields %}
  {{ field.name }}?: boolean | FindMany{{ field.target_model }}ArgsFrom{{ model_name }};
{%- endfor %}
}

{%- for field in include_fields %}
export interface FindMany{{ field.target_model }}ArgsFrom{{ model_name }} {
  where?:    {{ field.target_model }}WhereInput;
  orderBy?:  {{ field.target_model }}OrderByInput;
{%- if field.is_array %}
  take?:     number;
  skip?:     number;
  cursor?:   Record<string, unknown>;
  distinct?: string[];
{%- endif %}
  include?:  {{ field.target_model }}IncludeInput;
}
{%- endfor %}

{%- endif %}

export interface {{ model_name }}UpsertInput {
  create: {{ model_name }}CreateInput;
  update: {{ model_name }}UpdateInput;
}

export type {{ model_name }}ScalarFieldKeys = {% for f in scalar_fields %}'{{ f.name }}'{% if not loop.last %} | {% endif %}{% endfor %};

export interface {{ model_name }}CountAggregateInput {
  _all?: boolean;
{%- for f in scalar_fields %}
  {{ f.name }}?: boolean;
{%- endfor %}
}

{%- if has_numeric_fields %}
export interface {{ model_name }}AvgAggregateInput {
{%- for f in numeric_fields %}
  {{ f.name }}?: boolean;
{%- endfor %}
}
export interface {{ model_name }}SumAggregateInput {
{%- for f in numeric_fields %}
  {{ f.name }}?: boolean;
{%- endfor %}
}
{%- endif %}

export interface {{ model_name }}MinAggregateInput {
{%- for f in orderable_fields %}
  {{ f.name }}?: boolean;
{%- endfor %}
}

export interface {{ model_name }}MaxAggregateInput {
{%- for f in orderable_fields %}
  {{ f.name }}?: boolean;
{%- endfor %}
}

export interface {{ model_name }}GroupByOutput {
{%- for f in scalar_fields %}
  {{ f.name }}?: {{ f.ts_type }};
{%- endfor %}
  _count?: Record<string, number>;
  _avg?:   Record<string, number>;
  _sum?:   Record<string, unknown>;
  _min?:   Record<string, unknown>;
  _max?:   Record<string, unknown>;
}

export interface {{ model_name }}FindManyArgs {
  where?:    {{ model_name }}WhereInput;
  orderBy?:  {{ model_name }}OrderByInput | {{ model_name }}OrderByInput[];
{%- if has_vector_fields %}
  nearest?:  {{ model_name }}NearestInput;
{%- endif %}
  take?:     number;
  skip?:     number;
  select?:   {{ model_name }}SelectInput;
  include?:  {% if has_includes %}{{ model_name }}IncludeInput{% else %}Record<string, unknown>{% endif %};
  cursor?:   Record<string, unknown>;
  distinct?: string[];
  chunkSize?: number;
}

export interface {{ model_name }}FindFirstArgs {
  where?:   {{ model_name }}WhereInput;
  orderBy?: {{ model_name }}OrderByInput | {{ model_name }}OrderByInput[];
{%- if has_vector_fields %}
  nearest?: {{ model_name }}NearestInput;
{%- endif %}
  select?:  {{ model_name }}SelectInput;
  include?: {% if has_includes %}{{ model_name }}IncludeInput{% else %}Record<string, unknown>{% endif %};
}

export interface {{ model_name }}FindUniqueArgs {
  where:    {{ model_name }}WhereInput;
  select?:  {{ model_name }}SelectInput;
  include?: {% if has_includes %}{{ model_name }}IncludeInput{% else %}Record<string, unknown>{% endif %};
}

function _processWhereFilters(
  where: Record<string, unknown>,
  fieldToDb: Record<string, string>,
): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(where)) {
    if (k === 'AND' || k === 'OR' || k === 'NOT') {
      if (Array.isArray(v)) {
        out[k] = v.map(item => _processWhereFilters(item as Record<string, unknown>, fieldToDb));
      } else if (v && typeof v === 'object') {
        out[k] = _processWhereFilters(v as Record<string, unknown>, fieldToDb);
      } else {
        out[k] = v;
      }
    } else {
      const dbKey = fieldToDb[k] ?? k;
      if (v && typeof v === 'object' && !Array.isArray(v)) {
        const ops: Record<string, unknown> = {};
        for (const [op, opVal] of Object.entries(v as Record<string, unknown>)) {
          const actualOp = op === 'isNull' ? 'isNull' : op;
          if (actualOp === 'equals') {
            out[dbKey] = opVal;
          } else {
            ops[actualOp] = opVal;
          }
        }
        if (Object.keys(ops).length > 0) out[dbKey] = ops;
      } else {
        out[dbKey] = v;
      }
    }
  }
  return out;
}

function _processCreateData(
  data: Record<string, unknown>,
  fieldToDb: Record<string, string>,
): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(data)) {
    const dbKey = fieldToDb[k] ?? k;
    // Coerce Date -> ISO string for the engine.
    out[dbKey] = v instanceof Date ? v.toISOString() : v;
  }
  return out;
}

function _serializeInclude(include: unknown): unknown {
  if (include == null || typeof include === 'boolean') return include;
  if (typeof include !== 'object' || Array.isArray(include)) return include;
  const result: Record<string, unknown> = {};
  for (const [field, spec] of Object.entries(include as Record<string, unknown>)) {
    if (typeof spec === 'boolean') {
      result[field] = spec;
    } else if (spec && typeof spec === 'object') {
      result[field] = _serializeInclude(spec);
    } else {
      result[field] = spec;
    }
  }
  return result;
}

function _firstDataRow(result: Record<string, unknown>): Record<string, unknown> | null {
  const data = result['data'];
  if (!Array.isArray(data)) return null;
  const row = data[0];
  if (row == null || typeof row !== 'object' || Array.isArray(row)) return null;
  return row as Record<string, unknown>;
}

export class {{ delegate_name }} {
  constructor(public client: NautilusClient) {}

  /**
   * Fetch every matching row and resolve a final in-memory array.
   *
   * Prefer this for small or medium result sets, eager-loaded reads, and any
   * downstream path that already wants the whole collection before continuing.
   */
  async findMany(args?: Omit<{{ model_name }}FindManyArgs, 'select'> & { select?: undefined }): Promise<{{ model_name }}Model[]>;
  async findMany<const S extends {{ model_name }}SelectInput>(args: Omit<{{ model_name }}FindManyArgs, 'select'> & { select: S }): Promise<Array<{{ model_name }}Selected<S>>>;
  async findMany(args?: {{ model_name }}FindManyArgs): Promise<Array<{{ model_name }}Model | Partial<{{ model_name }}Model>>> {
    const rpcArgs: Record<string, unknown> = {};
    if (args?.where    != null) rpcArgs['where']   = _processWhereFilters(args.where as Record<string, unknown>, _{{ table_name }}FieldToDb);
    if (args?.orderBy  != null) {
      const ob = args.orderBy;
      rpcArgs['orderBy'] = Array.isArray(ob) ? ob : [ob];
    }
{%- if has_vector_fields %}
    if (args?.nearest  != null) rpcArgs['nearest'] = _serializeNearestInput(args.nearest);
{%- endif %}
    if (args?.take     != null) rpcArgs['take']    = args.take;
    if (args?.skip     != null) rpcArgs['skip']    = args.skip;
    if (args?.select   != null) rpcArgs['select']  = args.select;
    if (args?.include  != null) rpcArgs['include'] = _serializeInclude(args.include);
    if (args?.cursor   != null) rpcArgs['cursor']  = args.cursor;
    if (args?.distinct != null) rpcArgs['distinct'] = args.distinct;
    const request: Record<string, unknown> = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args: rpcArgs,
    };
    if (args?.chunkSize != null) request['chunkSize'] = args.chunkSize;
    const result = (await this.client._rpc('query.findMany', request)) as Record<string, unknown>;
    return ((result['data'] as unknown[]) ?? []).map(r => _coerce{{ model_name }}(r as Record<string, unknown>));
  }

  /**
   * Yield matching rows incrementally as an async iterable.
   *
   * Prefer this for exports, scans, and `for await ... of` pipelines where
   * keeping client memory flatter matters more than receiving one final array.
   * The underlying request keeps a pooled connection busy until iteration
   * finishes or the iterable is closed.
   */
  streamMany(args?: Omit<{{ model_name }}FindManyArgs, 'select'> & { select?: undefined }): AsyncIterable<{{ model_name }}Model>;
  streamMany<const S extends {{ model_name }}SelectInput>(args: Omit<{{ model_name }}FindManyArgs, 'select'> & { select: S }): AsyncIterable<{{ model_name }}Selected<S>>;
  async *streamMany(args?: {{ model_name }}FindManyArgs): AsyncIterable<{{ model_name }}Model | Partial<{{ model_name }}Model>> {
    const chunkSize = args?.chunkSize ?? 128;
    if (!Number.isInteger(chunkSize) || chunkSize < 1) {
      throw new RangeError('streamMany chunkSize must be a positive integer');
    }

    const rpcArgs: Record<string, unknown> = {};
    if (args?.where    != null) rpcArgs['where']   = _processWhereFilters(args.where as Record<string, unknown>, _{{ table_name }}FieldToDb);
    if (args?.orderBy  != null) {
      const ob = args.orderBy;
      rpcArgs['orderBy'] = Array.isArray(ob) ? ob : [ob];
    }
{%- if has_vector_fields %}
    if (args?.nearest  != null) rpcArgs['nearest'] = _serializeNearestInput(args.nearest);
{%- endif %}
    if (args?.take     != null) rpcArgs['take']    = args.take;
    if (args?.skip     != null) rpcArgs['skip']    = args.skip;
    if (args?.select   != null) rpcArgs['select']  = args.select;
    if (args?.include  != null) rpcArgs['include'] = _serializeInclude(args.include);
    if (args?.cursor   != null) rpcArgs['cursor']  = args.cursor;
    if (args?.distinct != null) rpcArgs['distinct'] = args.distinct;

    const payload: Record<string, unknown> = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args: rpcArgs,
      chunkSize,
    };

    for await (const chunk of this.client._streamRpc('query.findMany', payload)) {
      const rows = (chunk as Record<string, unknown>)?.['data'];
      if (!Array.isArray(rows)) continue;
      for (const row of rows) {
        yield _coerce{{ model_name }}(row as Record<string, unknown>);
      }
    }
  }

  async findFirst(args?: Omit<{{ model_name }}FindFirstArgs, 'select'> & { select?: undefined }): Promise<{{ model_name }}Model | null>;
  async findFirst<const S extends {{ model_name }}SelectInput>(args: Omit<{{ model_name }}FindFirstArgs, 'select'> & { select: S }): Promise<{{ model_name }}Selected<S> | null>;
  async findFirst(args?: {{ model_name }}FindFirstArgs): Promise<{{ model_name }}Model | Partial<{{ model_name }}Model> | null> {
    const rpcArgs: Record<string, unknown> = {};
    if (args?.where    != null) rpcArgs['where']   = _processWhereFilters(args.where as Record<string, unknown>, _{{ table_name }}FieldToDb);
    if (args?.orderBy  != null) {
      const ob = args.orderBy;
      rpcArgs['orderBy'] = Array.isArray(ob) ? ob : [ob];
    }
{%- if has_vector_fields %}
    if (args?.nearest  != null) rpcArgs['nearest'] = _serializeNearestInput(args.nearest);
{%- endif %}
    rpcArgs['take'] = 1;
    if (args?.select   != null) rpcArgs['select']  = args.select;
    if (args?.include  != null) rpcArgs['include'] = _serializeInclude(args.include);
    const request: Record<string, unknown> = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args: rpcArgs,
    };
    const result = (await this.client._rpc('query.findFirst', request)) as Record<string, unknown>;
    const row = _firstDataRow(result);
    return row == null ? null : _coerce{{ model_name }}(row);
  }

  async findUnique(args: Omit<{{ model_name }}FindUniqueArgs, 'select'> & { select?: undefined }): Promise<{{ model_name }}Model | null>;
  async findUnique<const S extends {{ model_name }}SelectInput>(args: Omit<{{ model_name }}FindUniqueArgs, 'select'> & { select: S }): Promise<{{ model_name }}Selected<S> | null>;
  async findUnique(args: {{ model_name }}FindUniqueArgs): Promise<{{ model_name }}Model | Partial<{{ model_name }}Model> | null> {
    if (args.select != null || args.include != null) {
      return this.findFirst({ where: args.where, select: args.select, include: args.include });
    }
    const request: Record<string, unknown> = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      filter: _processWhereFilters(args.where as Record<string, unknown>, _{{ table_name }}FieldToDb),
    };
    const result = (await this.client._rpc('query.findUnique', request)) as Record<string, unknown>;
    const row = _firstDataRow(result);
    return row == null ? null : _coerce{{ model_name }}(row);
  }

  async findUniqueOrThrow(args: Omit<{{ model_name }}FindUniqueArgs, 'select'> & { select?: undefined }): Promise<{{ model_name }}Model>;
  async findUniqueOrThrow<const S extends {{ model_name }}SelectInput>(args: Omit<{{ model_name }}FindUniqueArgs, 'select'> & { select: S }): Promise<{{ model_name }}Selected<S>>;
  async findUniqueOrThrow(args: {{ model_name }}FindUniqueArgs): Promise<{{ model_name }}Model | Partial<{{ model_name }}Model>> {
    const record = await this.findUnique(args);
    if (record == null) throw new NotFoundError('findUniqueOrThrow: no {{ model_name }} record found matching the given filter');
    return record;
  }

  async findFirstOrThrow(args?: Omit<{{ model_name }}FindFirstArgs, 'select'> & { select?: undefined }): Promise<{{ model_name }}Model>;
  async findFirstOrThrow<const S extends {{ model_name }}SelectInput>(args: Omit<{{ model_name }}FindFirstArgs, 'select'> & { select: S }): Promise<{{ model_name }}Selected<S>>;
  async findFirstOrThrow(args?: {{ model_name }}FindFirstArgs): Promise<{{ model_name }}Model | Partial<{{ model_name }}Model>> {
    const record = await this.findFirst(args);
    if (record == null) throw new NotFoundError('findFirstOrThrow: no {{ model_name }} record found matching the given filter');
    return record;
  }

  async create(args: {
    data:        {{ model_name }}CreateInput;
    returnData?: boolean;
  }): Promise<{{ model_name }}Model | null> {
{%- if updated_at_fields | length > 0 %}
    const _createData: Record<string, unknown> = { ...(args.data as Record<string, unknown>) };
{%- for field in updated_at_fields %}
    if (!('{{ field }}' in _createData) || _createData['{{ field }}'] == null) _createData['{{ field }}'] = new Date();
{%- endfor %}
{%- endif %}
    const result = (await this.client._rpc('query.create', {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      data:       _processCreateData({% if updated_at_fields | length > 0 %}_createData{% else %}args.data as Record<string, unknown>{% endif %}, _{{ table_name }}FieldToDb),
      returnData: args.returnData ?? true,
    })) as Record<string, unknown>;
    if (!(args.returnData ?? true)) return null;
    const rows = result['data'] as unknown[] | undefined;
    if (!rows?.length) throw new Error('create: engine returned no data');
    return _coerce{{ model_name }}(rows[0] as Record<string, unknown>);
  }

  async createMany(args: {
    data:        {{ model_name }}CreateInput[];
    returnData?: boolean;
  }): Promise<{{ model_name }}Model[]> {
{%- if updated_at_fields | length > 0 %}
    const _batchData = args.data.map(d => {
      const _d: Record<string, unknown> = { ...(d as Record<string, unknown>) };
{%- for field in updated_at_fields %}
      if (!('{{ field }}' in _d) || _d['{{ field }}'] == null) _d['{{ field }}'] = new Date();
{%- endfor %}
      return _d;
    });
{%- endif %}
    const result = (await this.client._rpc('query.createMany', {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      data:       {% if updated_at_fields | length > 0 %}_batchData.map(d => _processCreateData(d, _{{ table_name }}FieldToDb)){% else %}args.data.map(d => _processCreateData(d as Record<string, unknown>, _{{ table_name }}FieldToDb)){% endif %},
      returnData: args.returnData ?? true,
    })) as Record<string, unknown>;
    if (!(args.returnData ?? true)) return [];
    return ((result['data'] as unknown[]) ?? []).map(r => _coerce{{ model_name }}(r as Record<string, unknown>));
  }

  async update(args: {
    where?:      {{ model_name }}WhereInput;
    data:        {{ model_name }}UpdateInput;
    returnData?: boolean;
  }): Promise<{{ model_name }}Model[] | number> {
{%- if updated_at_fields | length > 0 %}
    const _updateData: Record<string, unknown> = { ...(args.data as Record<string, unknown>) };
{%- for field in updated_at_fields %}
    if (!('{{ field }}' in _updateData) || _updateData['{{ field }}'] == null) _updateData['{{ field }}'] = new Date();
{%- endfor %}
{%- endif %}
    const result = (await this.client._rpc('query.update', {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      filter:     _processWhereFilters((args.where ?? {}) as Record<string, unknown>, _{{ table_name }}FieldToDb),
      data:       _processCreateData({% if updated_at_fields | length > 0 %}_updateData{% else %}args.data as Record<string, unknown>{% endif %}, _{{ table_name }}FieldToDb),
      returnData: args.returnData ?? true,
    })) as Record<string, unknown>;
    if (!(args.returnData ?? true)) return (result['count'] as number) ?? 0;
    return ((result['data'] as unknown[]) ?? []).map(r => _coerce{{ model_name }}(r as Record<string, unknown>));
  }

  async delete(args: {
    where:       {{ model_name }}WhereInput;
    returnData?: boolean;
  }): Promise<{{ model_name }}Model | null> {
    const record = await this.findFirst({ where: args.where });
    if (record == null) throw new NotFoundError('delete: no {{ model_name }} record found matching the given filter');
    const pkFilter: Record<string, unknown> = {
{%- for pk in primary_key_fields %}
      '{{ pk }}': (record as Record<string, unknown>)['{{ pk }}'],
{%- endfor %}
    };
    const result = (await this.client._rpc('query.delete', {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      filter:     _processWhereFilters(pkFilter, _{{ table_name }}FieldToDb),
      returnData: args.returnData ?? true,
    })) as Record<string, unknown>;
    if (!(args.returnData ?? true)) return null;
    const rows = result['data'] as unknown[] | undefined;
    return rows?.length ? _coerce{{ model_name }}(rows[0] as Record<string, unknown>) : record;
  }

  async deleteMany(args?: {
    where?:      {{ model_name }}WhereInput;
    returnData?: boolean;
  }): Promise<{{ model_name }}Model[] | number> {
    const returnData = args?.returnData ?? false;
    const result = (await this.client._rpc('query.delete', {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      filter:     _processWhereFilters((args?.where ?? {}) as Record<string, unknown>, _{{ table_name }}FieldToDb),
      returnData,
    })) as Record<string, unknown>;
    if (returnData) return ((result['data'] as unknown[]) ?? []).map(r => _coerce{{ model_name }}(r as Record<string, unknown>));
    return (result['count'] as number) ?? 0;
  }

  async count(args?: {
    where?:  {{ model_name }}WhereInput;
    take?:   number;
    skip?:   number;
    cursor?: Record<string, unknown>;
  }): Promise<number> {
    const rpcArgs: Record<string, unknown> = {};
    if (args?.where  != null) rpcArgs['where']  = _processWhereFilters(args.where as Record<string, unknown>, _{{ table_name }}FieldToDb);
    if (args?.take   != null) rpcArgs['take']   = args.take;
    if (args?.skip   != null) rpcArgs['skip']   = args.skip;
    if (args?.cursor != null) rpcArgs['cursor'] = args.cursor;
    const result = (await this.client._rpc('query.count', {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args:  Object.keys(rpcArgs).length ? rpcArgs : null,
    })) as Record<string, unknown>;
    return (result['count'] as number) ?? 0;
  }

  /**
   * Execute a raw SQL string and return the result rows as generic objects.
   *
   * The SQL is sent to the database as-is with no parameter binding.
   * Prefer {@link rawStmtQuery} when user-supplied values are involved to
   * avoid SQL injection.
   */
  async rawQuery(sql: string): Promise<Record<string, unknown>[]> {
    const result = (await this.client._rpc('query.rawQuery', {
      protocolVersion: PROTOCOL_VERSION,
      sql,
    })) as Record<string, unknown>;
    return (result['data'] as Record<string, unknown>[]) ?? [];
  }

  /**
   * Execute a raw prepared-statement query with bound parameters.
   *
   * Use `$1`, `$2`, … (PostgreSQL) or `?` (MySQL / SQLite) as placeholders.
   * Parameters are bound in the order they appear in the `params` array.
   */
  async rawStmtQuery(sql: string, params?: unknown[]): Promise<Record<string, unknown>[]> {
    const result = (await this.client._rpc('query.rawStmtQuery', {
      protocolVersion: PROTOCOL_VERSION,
      sql,
      params: params ?? [],
    })) as Record<string, unknown>;
    return (result['data'] as Record<string, unknown>[]) ?? [];
  }

  async upsert(args: {
    where:       {{ model_name }}WhereInput;
    data:        {{ model_name }}UpsertInput;
    include?:    {% if has_includes %}{{ model_name }}IncludeInput{% else %}Record<string, unknown>{% endif %};
    returnData?: boolean;
  }): Promise<{{ model_name }}Model | null> {
    const existing = await this.findFirst({ where: args.where });
    if (existing != null) {
      const updated = await this.update({ where: args.where, data: args.data.update, returnData: args.returnData ?? true });
      if (!(args.returnData ?? true)) return null;
      if (args.include) {
        const rows = await this.findMany({ where: args.where, take: 1, include: args.include });
        return rows[0] ?? (Array.isArray(updated) ? updated[0] : null) ?? existing;
      }
      return Array.isArray(updated) ? (updated[0] ?? existing) : existing;
    }
    const created = await this.create({ data: args.data.create, returnData: args.returnData ?? true });
    if (!(args.returnData ?? true)) return null;
    if (created == null) throw new Error('upsert: create returned no data');
    if (args.include) {
      const rows = await this.findMany({ where: args.where, take: 1, include: args.include });
      return rows[0] ?? created;
    }
    return created;
  }

  async groupBy(args: {
    by:      {{ model_name }}ScalarFieldKeys[];
    where?:  {{ model_name }}WhereInput;
    having?: Record<string, unknown>;
    take?:   number;
    skip?:   number;
    count?:  boolean | {{ model_name }}CountAggregateInput;
{%- if has_numeric_fields %}
    avg?:    {{ model_name }}AvgAggregateInput;
    sum?:    {{ model_name }}SumAggregateInput;
{%- endif %}
    min?:    {{ model_name }}MinAggregateInput;
    max?:    {{ model_name }}MaxAggregateInput;
    order?:  Record<string, SortOrder> | Record<string, SortOrder>[];
  }): Promise<{{ model_name }}GroupByOutput[]> {
    const rpcArgs: Record<string, unknown> = { by: [...args.by] };
    if (args.where  != null) rpcArgs['where']  = _processWhereFilters(args.where as Record<string, unknown>, _{{ table_name }}FieldToDb);
    if (args.having != null) rpcArgs['having'] = args.having;
    if (args.take   != null) rpcArgs['take']   = args.take;
    if (args.skip   != null) rpcArgs['skip']   = args.skip;
    if (args.count  != null) rpcArgs['count']  = args.count;
{%- if has_numeric_fields %}
    if (args.avg    != null) rpcArgs['avg']    = args.avg;
    if (args.sum    != null) rpcArgs['sum']    = args.sum;
{%- endif %}
    if (args.min    != null) rpcArgs['min']    = args.min;
    if (args.max    != null) rpcArgs['max']    = args.max;
    if (args.order  != null) {
      const o = args.order;
      rpcArgs['orderBy'] = Array.isArray(o) ? o : [o];
    }
    const result = (await this.client._rpc('query.groupBy', {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args:  rpcArgs,
    })) as Record<string, unknown>;
    return (result['data'] as {{ model_name }}GroupByOutput[]) ?? [];
  }
}

export type { NautilusClient };
