import { readFileSync, writeFileSync, existsSync } from "fs";
const { initAuthCreds, BufferJSON, proto } = (
  await import("@whiskeysockets/baileys")
).default;

function bind(conn) {
  if (!conn.chats) conn.chats = {};

  function updateNameToDb(contacts) {
    if (!contacts) return;
    try {
      contacts = contacts.contacts || contacts;
      for (const contact of contacts) {
        const id = conn.decodeJid(contact.id);
        if (!id || id === "status@broadcast") continue;
        let chats = conn.chats[id];
        if (!chats)
          chats = conn.chats[id] = {
            ...contact,
            id: id,
          };
        conn.chats[id] = {
          ...chats,
          ...({
            ...contact,
            id: id,
            ...(id.endsWith("@g.us")
              ? {
                  subject:
                    contact.subject || contact.name || chats.subject || "",
                }
              : {
                  name:
                    contact.notify ||
                    contact.name ||
                    chats.name ||
                    chats.notify ||
                    "",
                }),
          } || {}),
        };
      }
    } catch (e) {
      console.error(e);
    }
  }
  conn.ev.on("contacts.upsert", updateNameToDb);
  conn.ev.on("groups.update", updateNameToDb);
  conn.ev.on("contacts.set", updateNameToDb);
  conn.ev.on("chats.set", async ({ chats }) => {
    try {
      for (let { id, name, readOnly } of chats) {
        id = conn.decodeJid(id);
        if (!id || id === "status@broadcast") continue;
        const isGroup = id.endsWith("@g.us");
        let chats = conn.chats[id];
        if (!chats)
          chats = conn.chats[id] = {
            id: id,
          };
        chats.isChats = !readOnly;
        if (name) chats[isGroup ? "subject" : "name"] = name;
        if (isGroup) {
          const metadata = await conn.groupMetadata(id).catch((_) => null);
          if (name || metadata?.subject)
            chats.subject = name || metadata.subject;
          if (!metadata) continue;
          chats.metadata = metadata;
        }
      }
    } catch (e) {
      console.error(e);
    }
  });
  conn.ev.on(
    "group-participants.update",
    async function updateParticipantsToDb({ id, participants, action }) {
      if (!id) return;
      id = conn.decodeJid(id);
      if (id === "status@broadcast") return;
      if (!(id in conn.chats))
        conn.chats[id] = {
          id: id,
        };
      const chats = conn.chats[id];
      chats.isChats = true;
      const groupMetadata = await conn.groupMetadata(id).catch((_) => null);
      if (!groupMetadata) return;
      chats.subject = groupMetadata.subject;
      chats.metadata = groupMetadata;
    },
  );
  conn.ev.on(
    "groups.update",
    async function groupUpdatePushToDb(groupsUpdates) {
      try {
        for (const update of groupsUpdates) {
          const id = conn.decodeJid(update.id);
          if (!id || id === "status@broadcast") continue;
          const isGroup = id.endsWith("@g.us");
          if (!isGroup) continue;
          let chats = conn.chats[id];
          if (!chats)
            chats = conn.chats[id] = {
              id: id,
            };
          chats.isChats = true;
          const metadata = await conn.groupMetadata(id).catch((_) => null);
          if (metadata) chats.metadata = metadata;
          if (update.subject || metadata?.subject)
            chats.subject = update.subject || metadata.subject;
        }
      } catch (e) {
        console.error(e);
      }
    },
  );
  conn.ev.on("chats.upsert", function chatsUpsertPushToDb(chatsUpsert) {
    try {
      const { id, name } = chatsUpsert;
      if (!id || id === "status@broadcast") return;
      conn.chats[id] = {
        ...(conn.chats[id] || {}),
        ...chatsUpsert,
        isChats: true,
      };
      const isGroup = id.endsWith("@g.us");
      if (isGroup) conn.insertAllGroup().catch((_) => null);
    } catch (e) {
      console.error(e);
    }
  });
  conn.ev.on(
    "presence.update",
    async function presenceUpdatePushToDb({ id, presences }) {
      try {
        const sender = Object.keys(presences)[0] || id;
        const _sender = conn.decodeJid(sender);
        const presence = presences[sender]["lastKnownPresence"] || "composing";
        let chats = conn.chats[_sender];
        if (!chats)
          chats = conn.chats[_sender] = {
            id: sender,
          };
        chats.presences = presence;
        if (id.endsWith("@g.us")) {
          let chats = conn.chats[id];
          if (!chats)
            chats = conn.chats[id] = {
              id: id,
            };
        }
      } catch (e) {
        console.error(e);
      }
    },
  );
}
const KEY_MAP = {
  "pre-key": "preKeys",
  session: "sessions",
  "sender-key": "senderKeys",
  "app-state-sync-key": "appStateSyncKeys",
  "app-state-sync-version": "appStateVersions",
  "sender-key-memory": "senderKeyMemory",
};

function useSingleFileAuthState(filename, logger) {
  let creds;
  let keys = {};
  let saveCount = 0;
  const saveState = (forceSave) => {
    logger?.trace("saving auth state");
    saveCount++;
    if (forceSave || saveCount > 5) {
      writeFileSync(
        filename,
        JSON.stringify(
          {
            creds: creds,
            keys: keys,
          },
          BufferJSON.replacer,
          2,
        ),
      );
      saveCount = 0;
    }
  };
  if (existsSync(filename)) {
    const result = JSON.parse(
      readFileSync(filename, {
        encoding: "utf-8",
      }),
      BufferJSON.reviver,
    );
    creds = result.creds;
    keys = result.keys;
  } else {
    creds = initAuthCreds();
    keys = {};
  }
  return {
    state: {
      creds: creds,
      keys: {
        get: (type, ids) => {
          const key = KEY_MAP[type];
          return ids.reduce((dict, id) => {
            let value = keys[key][id];
            if (value) {
              if (type === "app-state-sync-key") {
                value = proto.AppStateSyncKeyData.fromObject(value);
              }
              dict[id] = value;
            }
            return dict;
          }, {});
        },
        set: (data) => {
          for (const _key in data) {
            const key = KEY_MAP[_key];
            keys[key] = keys[key] || {};
            Object.assign(keys[key], data[_key]);
          }
          saveState();
        },
      },
    },
    saveState: saveState,
  };
}

function loadMessage(jid, id = null) {
  let message = null;
  if (jid && !id) {
    id = jid;
    const filter = (m) => m.key?.id === id;
    const messages = {};
    const messageFind = Object.entries(messages).find(([, msgs]) => {
      return msgs.find(filter);
    });
    message = messageFind?.[1].find(filter);
  } else {
    jid = jid?.decodeJid?.();
    const messages = {};
    if (!(jid in messages)) return null;
    message = messages[jid].find((m) => m.key.id === id);
  }
  return message ? message : null;
}
export default {
  bind: bind,
  useSingleFileAuthState: useSingleFileAuthState,
  loadMessage: loadMessage,
};
