import { OPSqliteOpenFactory } from '@powersync/op-sqlite';
import { PowerSyncContext, PowerSyncDatabase } from '@powersync/react-native';
import { type ReactNode, useEffect, useState } from 'react';
import { AppSchema } from './config';
import { SyncConnector } from './connector';

let powerSyncInstance: PowerSyncDatabase | null = null;
let connectingPromise: Promise<void> | null = null;
let initPromise: Promise<void> | null = null;

function ensureDatabase(): PowerSyncDatabase {
  if (!powerSyncInstance) {
    const factory = new OPSqliteOpenFactory({ dbFilename: 'palace-powersync.sqlite' });
    powerSyncInstance = new PowerSyncDatabase({ database: factory, schema: AppSchema });
  }
  return powerSyncInstance;
}

async function ensureInitialized(): Promise<PowerSyncDatabase> {
  const db = ensureDatabase();
  if (!initPromise) {
    initPromise = db.init();
  }
  await initPromise;
  return db;
}

export function getDatabase(): PowerSyncDatabase {
  return ensureDatabase();
}

export function isSyncConnected(): boolean {
  return powerSyncInstance?.connected ?? false;
}

export async function connectSync(): Promise<void> {
  if (connectingPromise) return connectingPromise;

  const db = await ensureInitialized();
  if (db.connected) return;

  connectingPromise = (async () => {
    try {
      const connector = new SyncConnector();
      await connector.fetchCredentials();
      await db.connect(connector);
      const timeout = new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error('Sync timeout after 30s')), 30000)
      );
      await Promise.race([db.waitForFirstSync(), timeout]);
    } finally {
      connectingPromise = null;
    }
  })();

  return connectingPromise;
}

export async function disconnectSync(): Promise<void> {
  const db = getDatabase();
  if (db.connected) {
    await db.disconnect();
  }
}

export async function disconnectAndClear(): Promise<void> {
  const db = getDatabase();
  await db.disconnectAndClear();
}

export function DatabaseProvider({ children }: { children: ReactNode }) {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    ensureInitialized().then(() => setReady(true));
  }, []);

  if (!ready) return null;

  return <PowerSyncContext.Provider value={getDatabase()}>{children}</PowerSyncContext.Provider>;
}
