<template>
  <QuickNav selected="Contacts"></QuickNav>
  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Back -->
    <div class="text-lg text-center font-light relative px-7">
      <h1
        class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
        @click="$router.back()"
      >
        <fa icon="chevron-left" class="fa-fw"></fa>
      </h1>
    </div>

    <!-- Heading -->
    <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
      Contact Import
    </h1>

    <span
      v-if="contactsImporting.length > sameCount"
      class="flex justify-center"
    >
      <input type="checkbox" v-model="makeVisible" class="mr-2" />
      Make my activity visible to these contacts.
    </span>

    <div v-if="sameCount > 0">
      <span v-if="sameCount == 1"
        >One contact is the same as an existing contact</span
      >
      <span v-else
        >{{ sameCount }} contacts are the same as existing contacts</span
      >
    </div>

    <!-- Results List -->
    <ul
      v-if="contactsImporting.length > sameCount"
      class="border-t border-slate-300"
    >
      <li v-for="(contact, index) in contactsImporting" :key="contact.did">
        <div
          v-if="
            !contactsExisting[contact.did] ||
            !R.isEmpty(contactDifferences[contact.did])
          "
          class="grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4"
        >
          <h2 class="text-base font-semibold">
            <input type="checkbox" v-model="contactsSelected[index]" />
            {{ contact.name || AppString.NO_CONTACT_NAME }}
            -
            <span v-if="contactsExisting[contact.did]" class="text-orange-500"
              >Existing</span
            >
            <span v-else class="text-green-500">New</span>
          </h2>
          <div class="text-sm truncate">
            {{ contact.did }}
          </div>
          <div v-if="contactDifferences[contact.did]">
            <div>
              <div class="grid grid-cols-3 gap-2">
                <div class="font-bold">Field</div>
                <div class="font-bold">Old Value</div>
                <div class="font-bold">New Value</div>
              </div>
              <div
                v-for="(value, contactField) in contactDifferences[contact.did]"
                :key="contactField"
                class="grid grid-cols-3 border"
              >
                <div class="border p-1">{{ contactField }}</div>
                <div class="border p-1">{{ value.old }}</div>
                <div class="border p-1">{{ value.new }}</div>
              </div>
            </div>
          </div>
        </div>
      </li>
      <fa icon="spinner" v-if="importing" class="animate-spin" />
      <button
        v-else
        class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
        @click="importContacts"
      >
        Import Selected Contacts
      </button>
    </ul>
    <p v-else>There are no contacts to import.</p>
  </section>
</template>

<script lang="ts">
import { JWTVerified } from "did-jwt";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";

import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue";
import OfferDialog from "../components/OfferDialog.vue";
import { AppString, NotificationIface } from "../constants/app";
import { db, retrieveSettingsForActiveAccount } from "../db/index";
import { Contact } from "../db/tables/contacts";
import * as libsUtil from "../libs/util";
import { decodeAndVerifyJwt } from "../libs/crypto/vc/index";
import { setVisibilityUtil } from "../libs/endorserServer";

@Component({
  components: { EntityIcon, OfferDialog, QuickNav },
})
export default class ContactImportView extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;

  AppString = AppString;
  libsUtil = libsUtil;
  R = R;

  activeDid = "";
  apiServer = "";
  contactsExisting: Record<string, Contact> = {}; // user's contacts already in the system, keyed by DID
  contactsImporting: Array<Contact> = []; // contacts from the import
  contactsSelected: Array<boolean> = []; // whether each contact in contactsImporting is selected
  contactDifferences: Record<
    string,
    Record<string, { new: string; old: string }>
  > = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key
  importing = false;
  makeVisible = true;
  sameCount = 0;

  async created() {
    const settings = await retrieveSettingsForActiveAccount();
    this.activeDid = settings.activeDid || "";
    this.apiServer = settings.apiServer || "";

    // Retrieve the imported contacts from the query parameter
    const importedContacts = (this.$route as Router).query[
      "contacts"
    ] as string;
    if (importedContacts) {
      await this.setContactsSelected(JSON.parse(importedContacts));
    }

    // match everything after /contact-import/ in the window.location.pathname
    const jwt = window.location.pathname.match(
      /\/contact-import\/(ey.+)$/,
    )?.[1];
    if (jwt) {
      // decode the JWT
      // eslint-disable-next-line prettier/prettier
      const parsedJwt: Omit<JWTVerified, "didResolutionResult" | "signer" | "jwt"> = await decodeAndVerifyJwt(jwt);
      await this.setContactsSelected(parsedJwt.payload.contacts as Contact[]);
    }
  }

  async setContactsSelected(contacts: Array<Contact>) {
    this.contactsImporting = contacts;
    this.contactsSelected = new Array(this.contactsImporting.length).fill(true);

    await db.open();
    const baseContacts = await db.contacts.toArray();
    // set the existing contacts, keyed by DID, if they exist in contactsImporting
    for (let i = 0; i < this.contactsImporting.length; i++) {
      const contactIn = this.contactsImporting[i];
      const existingContact = baseContacts.find(
        (contact) => contact.did === contactIn.did,
      );
      if (existingContact) {
        this.contactsExisting[contactIn.did] = existingContact;

        const differences: Record<string, { new: string; old: string }> = {};
        Object.keys(contactIn).forEach((key) => {
          if (contactIn[key] !== existingContact[key]) {
            differences[key] = {
              old: existingContact[key],
              new: contactIn[key],
            };
          }
        });
        this.contactDifferences[contactIn.did] = differences;
        if (R.isEmpty(differences)) {
          this.sameCount++;
        }

        // don't automatically import previous data
        this.contactsSelected[i] = false;
      }
    }
  }

  async importContacts() {
    this.importing = true;
    let importedCount = 0,
      updatedCount = 0;
    for (let i = 0; i < this.contactsImporting.length; i++) {
      if (this.contactsSelected[i]) {
        const contact = this.contactsImporting[i];
        const existingContact = this.contactsExisting[contact.did];
        if (existingContact) {
          await db.contacts.update(contact.did, contact);
          updatedCount++;
        } else {
          // without explicit clone on the Proxy, we get: DataCloneError: Failed to execute 'add' on 'IDBObjectStore': #<Object> could not be cloned.
          await db.contacts.add(R.clone(contact));
          importedCount++;
        }
      }
    }
    if (this.makeVisible) {
      const failedVisibileToContacts = [];
      for (let i = 0; i < this.contactsImporting.length; i++) {
        const contact = this.contactsImporting[i];
        if (contact) {
          const visResult = await setVisibilityUtil(
            this.activeDid,
            this.apiServer,
            this.axios,
            db,
            contact,
            true,
          );
          if (!visResult.success) {
            failedVisibileToContacts.push(contact);
          }
        }
      }
      if (failedVisibileToContacts.length) {
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Visibility Error",
            text: `Failed to set visibility for ${failedVisibileToContacts.length} contact${
              failedVisibileToContacts.length == 1 ? "" : "s"
            }. You must set them individually: ${failedVisibileToContacts.map((c) => c.name).join(", ")}`,
          },
          -1,
        );
      }
    }

    this.importing = false;

    this.$notify(
      {
        group: "alert",
        type: "success",
        title: "Imported",
        text:
          `${importedCount} contact${importedCount == 1 ? "" : "s"} imported.` +
          (updatedCount ? ` ${updatedCount} updated.` : ""),
      },
      3000,
    );
    (this.$router as Router).push({ name: "contacts" });
  }
}
</script>