import * as Y from "yjs";
import * as promise from "lib0/promise";
import { ObservableV2 } from "lib0/observable";
import { capConnectionOptions, SQLiteConnection, SQLiteDBConnection } from "@capacitor-community/sqlite";
import { Buffer } from "buffer";

const DB_PREFIX = "cap-sqlite-persistence-";

export class CapSQLitePersistence extends ObservableV2<{
  synced: (persistence: CapSQLitePersistence) => void;
}> {
  doc: Y.Doc;
  config: Omit<capConnectionOptions, "database">;
  whenSynced: Promise<this>;
  name: string;
  db: SQLiteDBConnection | undefined;
  con: SQLiteConnection
  synced: boolean = false;
  _dbref = 0;
  _dbsize = 0;
  _destroyed = false;

  constructor(config: Omit<capConnectionOptions, "database">, doc: Y.Doc, name: string, sqlite: SQLiteConnection) {
    super();
    this.doc = doc;
    this.config = config;
    this.name = name;
    this.con = sqlite
    this.whenSynced = promise.create((resolve) =>
      this.on("synced", () => resolve(this))
    );

    this.destroy = this.destroy.bind(this);
    this._storeUpdate.bind(this);
    doc.on("update", this._storeUpdate.bind(this));
    doc.on("destroy", this.destroy.bind(this));
  }

  async initDb() {
    try {
      const dbName = DB_PREFIX + this.name
      console.log((await this.con.isConnection(dbName, this.config.readonly ?? false)).result)
      if ((await this.con.isConnection(dbName, this.config.readonly ?? false)).result) {
        this.db = await this.con.retrieveConnection(dbName, this.config.readonly ?? false)
      }
      else {
        this.db = await this.con.createConnection(dbName, this.config.encrypted ?? false, this.config.mode ?? "", this.config.version ?? 1, this.config.readonly ?? false)
      }

      await this.db.open()
      await this.db.execute("CREATE TABLE IF NOT EXISTS updates (id INTEGER PRIMARY KEY, content BLOB)")

      const s = Y.encodeStateAsUpdate(this.doc);
      if (s.length > 0) {
        this._storeUpdate(s, null);
      }
      const { values } = await this.db.query("SELECT content FROM updates")

      if (values != undefined) {
        for (const row of values) {
          Y.applyUpdate(this.doc, new Uint8Array(row.content));
        }
      }
      this.synced = true;
      this.emit("synced", [this]);
      console.log("initialisation finished")
    }
    catch (e) {
      console.log("error loading update", e)
      throw e;
    }
  }

  static async create(config: Omit<capConnectionOptions, "database">, doc: Y.Doc, name: string, sqlite: SQLiteConnection) {
    const c = new CapSQLitePersistence(config, doc, name, sqlite)
    await c.initDb()
    return c;
  }

  _storeUpdate(update: Uint8Array, origin: any) {
    if (!this.db || origin == this) {
      !this.db && console.error("trying to store update without db");
      return;
    }
    return this.db
      .run(`INSERT INTO updates (content) VALUES (?)`, [Buffer.from(update)])
      .catch((e) => console.error("error storing update", e));
  }

  destroy() {
    this.doc.off("update", this._storeUpdate);
    this.doc.off("destroy", this.destroy);
    this._destroyed = true;
    return this.db?.close().catch((e) => {
      console.log("can't close", e)
    });
  }
}
