import { and, asc, desc, eq, ilike, isNull, or, sql, type SQL } from 'drizzle-orm';
import type { PgTable, PgColumn } from 'drizzle-orm/pg-core';
import { db } from './index';

export interface BuildListQueryOptions {
  table: PgTable;
  portIdColumn: PgColumn;
  portId: string;
  idColumn: PgColumn;
  updatedAtColumn: PgColumn;
  filters?: SQL[];
  sort?: { column: PgColumn; direction: 'asc' | 'desc' };
  /**
   * Custom ORDER BY clauses, used INSTEAD of `sort`. For cases where
   * the natural ordering needs raw SQL (e.g. natural alphanumeric sort
   * on berth mooring numbers like A1, A2, A10, B1...). Deterministic
   * tail-sort on `updatedAt DESC, id DESC` is still appended.
   */
  customOrderBy?: SQL[];
  page: number;
  pageSize: number;
  searchColumns?: PgColumn[];
  searchTerm?: string;
  includeArchived?: boolean;
  archivedAtColumn?: PgColumn;
}

export interface ListResult<T> {
  data: T[];
  total: number;
}

/**
 * Generic Drizzle paginated query builder with port-scoping.
 *
 * - Port scoping is always the first condition in the AND chain.
 * - `archivedAt IS NULL` by default (unless `includeArchived` is true).
 * - Deterministic secondary sort: `updatedAt DESC, id DESC`.
 */
export async function buildListQuery<T>(opts: BuildListQueryOptions): Promise<ListResult<T>> {
  const {
    table,
    portIdColumn,
    portId,
    idColumn,
    updatedAtColumn,
    filters = [],
    sort,
    customOrderBy,
    page,
    pageSize,
    searchColumns = [],
    searchTerm,
    includeArchived = false,
    archivedAtColumn,
  } = opts;

  const conditions: SQL[] = [eq(portIdColumn, portId)];

  // Exclude archived by default
  if (!includeArchived && archivedAtColumn) {
    conditions.push(isNull(archivedAtColumn));
  }

  // Full-text search across multiple columns via ILIKE
  if (searchTerm && searchColumns.length > 0) {
    const searchConditions = searchColumns.map((col) => ilike(col, `%${searchTerm}%`));
    conditions.push(or(...searchConditions)!);
  }

  // Append caller-supplied filters
  conditions.push(...filters);

  const where = and(...conditions);

  // Count total
  const countResult = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(table)
    .where(where);
  const total = countResult[0]?.count ?? 0;

  // Build order by: customOrderBy (if provided) wins over the default
  // column-based sort. Deterministic secondary sort always trails.
  const orderClauses: SQL[] = [];
  if (customOrderBy && customOrderBy.length > 0) {
    orderClauses.push(...customOrderBy);
  } else if (sort) {
    orderClauses.push(sort.direction === 'asc' ? asc(sort.column) : desc(sort.column));
  }
  orderClauses.push(desc(updatedAtColumn), desc(idColumn));

  // Fetch page
  const offset = (page - 1) * pageSize;
  const data = await db
    .select()
    .from(table)
    .where(where)
    .orderBy(...orderClauses)
    .limit(pageSize)
    .offset(offset);

  return { data: data as T[], total };
}
