// Sequelize class shim — replaces `new Sequelize({...})` and `sequelize.transaction()`
import { getDB, modelRegistry, sharedModels } from './model';
import { helperToSQL } from './helpers';
import { sequelizeWhere } from './operators';

export class Sequelize {
  // models registry — shared across all instances via the sharedModels singleton from model.ts
  models: Record<string, any> = sharedModels;

  constructor(_options?: any) {
    // No-op in CF — D1 is injected via env binding
  }

  static useCLS(_namespace: any) {
    // No-op — CLS not available/needed in CF Workers
  }

  async query(sql: string, options?: any): Promise<any> {
    const db = getDB();
    let finalSql = sql;
    const bindValues: any[] = [];

    // Handle named replacements: { replacements: { key: value } }
    if (options?.replacements && typeof options.replacements === 'object' && !Array.isArray(options.replacements)) {
      const replacements = options.replacements as Record<string, any>;
      finalSql = sql.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
        const val = replacements[name];
        if (val === null || val === undefined) return 'NULL';
        if (typeof val === 'string') return `'${val.replace(/'/g, "''")}'`;
        return String(val);
      });
    }
    // Handle positional replacements: { replacements: [val1, val2] }
    else if (Array.isArray(options?.replacements)) {
      const positional = options.replacements as any[];
      let idx = 0;
      finalSql = sql.replace(/\?/g, () => {
        const val = positional[idx++];
        if (val === null || val === undefined) return 'NULL';
        if (typeof val === 'string') return `'${val.replace(/'/g, "''")}'`;
        return String(val);
      });
    }
    // Handle bind params: { bind: [...] } — use D1's prepared statement bind
    else if (options?.bind) {
      const bind = Array.isArray(options.bind) ? options.bind : Object.values(options.bind);
      // Replace $1, $2... or named $name with ?
      finalSql = sql.replace(/\$[0-9]+|\$[a-zA-Z_][a-zA-Z0-9_]*/g, '?');
      bindValues.push(...bind);
    }

    const stmt = bindValues.length > 0
      ? db.prepare(finalSql).bind(...bindValues)
      : db.prepare(finalSql);

    const result = await stmt.all();

    if (options?.type === 'SELECT') {
      return result.results || [];
    }
    return [result.results || [], result.meta];
  }

  async transaction(fn?: any): Promise<any> {
    // D1 doesn't support real transactions in the same way
    // Execute callback directly with a fake transaction object
    const fakeTx = {
      commit: async () => {},
      rollback: async () => {},
      LOCK: { UPDATE: 'UPDATE', SHARE: 'SHARE' },
    };

    if (typeof fn === 'function') {
      try {
        return await fn(fakeTx);
      } catch (err) {
        throw err;
      }
    }

    // Called without fn — return transaction object (caller manages commit/rollback)
    return fakeTx;
  }

  // Instance versions of static helpers (used as `sequelize.fn()`, `sequelize.literal()`, etc.)
  fn(name: string, ...args: any[]) {
    return { __fn: true, name, args };
  }

  literal(sql: string) {
    return { __literal: true, sql };
  }

  col(name: string) {
    return { __col: true, name };
  }

  // sequelize.where(fn/literal, value) — used in WHERE clauses
  where(lhs: any, rhs: any): any {
    return sequelizeWhere(lhs, rhs);
  }

  // Static versions — used as Sequelize.fn(), Sequelize.literal(), etc.
  static fn(name: string, ...args: any[]) {
    return { __fn: true, name, args };
  }

  static literal(sql: string) {
    return { __literal: true, sql };
  }

  static col(name: string) {
    return { __col: true, name };
  }

  static where(lhs: any, rhs: any): any {
    return sequelizeWhere(lhs, rhs);
  }
}
