/**
 * MongoDB adapter. Mongo is a document store, not SQL, so it does NOT use the
 * SQL `Driver`/migrations/query-generator. Instead it exposes a small, typed
 * collection API keyed by your models — reads are still coerced through
 * `model.parse`, so results match your schema.
 *
 * The `mongodb` driver is imported lazily (install it to use Mongo); the core
 * `@wrnexus/db` stays dependency-free.
 *
 *   const db = await mongo(process.env.MONGO_URL!, "app");
 *   const repo = db.collection(users);
 *   await repo.insert({ email, name });
 *   const active = await repo.find({ active: true });
 */

import type { Model } from "../schema.ts";

// Minimal shape of the `mongodb` driver we rely on (typed locally so this file
// compiles without `mongodb` installed).
interface MongoCollection {
  find(filter: object, options?: object): { toArray(): Promise<Record<string, unknown>[]> };
  findOne(filter: object): Promise<Record<string, unknown> | null>;
  insertOne(doc: object): Promise<{ insertedId: unknown }>;
  insertMany(docs: object[]): Promise<{ insertedCount: number }>;
  updateMany(
    filter: object,
    update: object,
  ): Promise<{ matchedCount: number; modifiedCount: number }>;
  deleteMany(filter: object): Promise<{ deletedCount: number }>;
  countDocuments(filter: object): Promise<number>;
}
interface MongoDatabase {
  collection(name: string): MongoCollection;
}
interface MongoClientLike {
  connect(): Promise<unknown>;
  db(name?: string): MongoDatabase;
  close(): Promise<void>;
}

export interface FindOptions {
  sort?: Record<string, 1 | -1>;
  limit?: number;
  skip?: number;
}

export interface MongoRepo<T> {
  find(filter?: Record<string, unknown>, options?: FindOptions): Promise<T[]>;
  findOne(filter: Record<string, unknown>): Promise<T | null>;
  insert(doc: Partial<T>): Promise<{ id: unknown }>;
  insertMany(docs: Partial<T>[]): Promise<{ count: number }>;
  update(
    filter: Record<string, unknown>,
    patch: Partial<T>,
  ): Promise<{ matched: number; modified: number }>;
  delete(filter: Record<string, unknown>): Promise<{ deleted: number }>;
  count(filter?: Record<string, unknown>): Promise<number>;
}

export interface MongoDb {
  collection<T>(model: Model<T>): MongoRepo<T>;
  close(): Promise<void>;
}

/** Map Mongo's `_id` to `id` so documents line up with model columns. */
function normalize(doc: Record<string, unknown>): Record<string, unknown> {
  if (doc && doc._id != null && doc.id == null) {
    const { _id, ...rest } = doc;
    return { id: String(_id), ...rest };
  }
  return doc;
}

/** Connect to MongoDB and return a model-aware collection API. */
export async function mongo(url: string, dbName?: string): Promise<MongoDb> {
  // Non-literal specifier so this compiles without `mongodb` installed.
  const specifier: string = "mongodb";
  const mod = (await import(specifier)) as { MongoClient: new (u: string) => MongoClientLike };
  const client = new mod.MongoClient(url);
  await client.connect();
  const database = client.db(dbName);

  return {
    collection<T>(model: Model<T>): MongoRepo<T> {
      const col = database.collection(model.name);
      return {
        async find(filter = {}, options = {}) {
          const docs = await col.find(filter, options).toArray();
          return docs.map((d) => model.parse(normalize(d)));
        },
        async findOne(filter) {
          const doc = await col.findOne(filter);
          return doc ? model.parse(normalize(doc)) : null;
        },
        async insert(doc) {
          const r = await col.insertOne(doc as object);
          return { id: r.insertedId };
        },
        async insertMany(docs) {
          const r = await col.insertMany(docs as object[]);
          return { count: r.insertedCount };
        },
        async update(filter, patch) {
          const r = await col.updateMany(filter, { $set: patch });
          return { matched: r.matchedCount, modified: r.modifiedCount };
        },
        async delete(filter) {
          const r = await col.deleteMany(filter);
          return { deleted: r.deletedCount };
        },
        count(filter = {}) {
          return col.countDocuments(filter);
        },
      };
    },
    close() {
      return client.close();
    },
  };
}
