<template>
  <QuickNav selected="Contacts"></QuickNav>
  <section id="Content" class="p-6 pb-24">
    <!-- Heading -->
    <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
      Your Contacts
    </h1>

    <div class="flex justify-between py-2">
      <span />
      <span>
        <router-link
          :to="{ name: 'help' }"
          class="text-xs uppercase bg-blue-500 text-white px-1.5 py-1 rounded-md ml-1"
        >
          Help
        </router-link>
      </span>
    </div>

    <!-- New Contact -->
    <div class="mb-4 flex">
      <span class="self-center bg-slate-500 text-white px-1.5 py-1 rounded-md">
        <router-link :to="{ name: 'contact-qr' }">
          <fa icon="qrcode" class="fa-fw" />
        </router-link>
      </span>
      <input
        type="text"
        placeholder="DID, Name, Public Key"
        class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
        v-model="contactInput"
      />
      <button
        class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
        @click="onClickNewContact()"
      >
        <fa icon="plus" class="fa-fw"></fa>
      </button>
    </div>

    <div class="flex justify-between" v-if="showGiveNumbers">
      <div class="w-full text-right">
        Hours to Add:
        <input
          class="border border rounded border-slate-400 w-24 text-right"
          type="text"
          placeholder="1"
          v-model="hourInput"
        />
        <br />
        <input
          class="border border rounded border-slate-400 w-48"
          type="text"
          placeholder="Description"
          v-model="hourDescriptionInput"
        />
        <br />
        <br />
        <button
          href=""
          class="text-center text-md text-white px-1.5 py-2 rounded-md mb-6"
          v-bind:class="showGiveAmountsClassNames()"
          @click="toggleShowGiveTotals()"
        >
          {{
            showGiveTotals
              ? "Total"
              : showGiveConfirmed
              ? "Confirmed"
              : "Unconfirmed"
          }}
        </button>
        <br />
        (Only hours shown)
        <br />
        (Only recent shown)
      </div>
    </div>

    <!-- Results List -->
    <ul v-if="contacts.length > 0" class="border-t border-slate-300">
      <li
        class="border-b border-slate-300 pt-2.5 pb-4"
        v-for="contact in contacts"
        :key="contact.did"
      >
        <div class="grow overflow-hidden">
          <h2 class="text-base font-semibold">
            <EntityIcon
              :entityId="contact.did"
              :iconSize="24"
              class="inline-block align-text-bottom border border-slate-300 rounded"
            ></EntityIcon>
            {{ contact.name || "(no name)" }}
            <button
              class="text-sm uppercase bg-slate-500 text-white px-1 rounded-md"
              @click="
                contactEdit = contact;
                contactNewName = contact.name;
              "
              title="Edit"
            >
              <fa icon="pen" class="fa-fw" />
            </button>
          </h2>
          <div class="text-sm truncate">{{ contact.did }}</div>
          <div class="text-sm truncate" v-if="contact.publicKeyBase64">
            Public Key (base 64): {{ contact.publicKeyBase64 }}
          </div>

          <div id="ContactActions" class="flex gap-1.5 mt-2">
            <button
              v-if="contact.seesMe"
              class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
              @click="setVisibility(contact, false)"
              title="They can see you"
            >
              <fa icon="eye" class="fa-fw" />
            </button>
            <button
              v-else
              class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
              @click="setVisibility(contact, true)"
              title="They cannot see you"
            >
              <fa icon="eye-slash" class="fa-fw" />
            </button>

            <button
              class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
              @click="checkVisibility(contact)"
              title="Check Visibility"
            >
              <fa icon="rotate" class="fa-fw" />
            </button>

            <button
              @click="register(contact)"
              class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
            >
              <fa
                v-if="contact.registered"
                icon="person-circle-check"
                class="fa-fw"
                title="Registered"
              />
              <fa
                v-else
                icon="person-circle-question"
                class="fa-fw"
                title="Registration Unknown"
              />
            </button>

            <button
              @click="deleteContact(contact)"
              class="text-sm uppercase bg-red-600 text-white px-2 py-1.5 rounded-md"
              title="Delete"
            >
              <fa icon="trash-can" class="fa-fw" />
            </button>

            <div
              v-if="showGiveNumbers && contact.did != activeDid"
              class="ml-auto flex gap-1.5"
            >
              <button
                class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
                @click="onClickAddGive(activeDid, contact.did)"
                title="givenByMeDescriptions[contact.did]"
              >
                To:
                {{
                  /* eslint-disable prettier/prettier */
                  this.showGiveTotals
                    ? ((givenByMeConfirmed[contact.did] || 0)
                      + (givenByMeUnconfirmed[contact.did] || 0))
                    : this.showGiveConfirmed
                        ? (givenByMeConfirmed[contact.did] || 0)
                        : (givenByMeUnconfirmed[contact.did] || 0)
                  /* eslint-enable prettier/prettier */
                }}
                <fa icon="plus" />
              </button>

              <button
                class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400"
                @click="onClickAddGive(contact.did, activeDid)"
                title="givenToMeDescriptions[contact.did]"
              >
                From:
                {{
                  /* eslint-disable prettier/prettier */
                  this.showGiveTotals
                    ? ((givenToMeConfirmed[contact.did] || 0)
                        + (givenToMeUnconfirmed[contact.did] || 0))
                    : this.showGiveConfirmed
                        ? (givenToMeConfirmed[contact.did] || 0)
                        : (givenToMeUnconfirmed[contact.did] || 0)
                  /* eslint-enable prettier/prettier */
                }}
                <fa icon="plus" />
              </button>

              <router-link
                :to="{
                  name: 'contact-amounts',
                  query: { contactDid: contact.did },
                }"
                class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
                title="See all given activity"
              >
                <fa icon="file-lines" class="fa-fw" />
              </router-link>
            </div>
          </div>
        </div>
      </li>
    </ul>
    <p v-else>There are no contacts.</p>

    <div v-if="contactEdit !== null" class="dialog-overlay">
      <div class="dialog">
        <h1 class="text-xl font-bold text-center mb-4">Edit Name</h1>
        <input
          type="text"
          class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
          placeholder="Name"
          v-model="contactNewName"
        />
        <button
          class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded -ml-1.5 border-l border-blue-400"
          @click="onClickSaveName(contactEdit, contactNewName)"
        >
          <fa icon="save" />
        </button>
        <span class="inline-block w-2" />
        <button
          class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded -ml-1.5 border-l border-blue-400"
          @click="onClickCancelName()"
        >
          <fa icon="ban" />
        </button>
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import { AxiosError } from "axios";
import * as didJwt from "did-jwt";
import * as R from "ramda";

import { NotificationIface } from "@/constants/app";
import { IIdentifier } from "@veramo/core";
import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import {
  accessToken,
  getContactPayloadFromJwtUrl,
  SimpleSigner,
} from "@/libs/crypto";
import {
  GiveServerRecord,
  GiveVerifiableCredential,
  RegisterVerifiableCredential,
  SERVICE_ID,
} from "@/libs/endorserServer";
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { Account } from "@/db/tables/accounts";

// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;

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

  activeDid = "";
  apiServer = "";
  contacts: Array<Contact> = [];
  contactEndorserUrl = localStorage.getItem("contactEndorserUrl") || "";
  contactInput = "";
  contactEdit: Contact | null = null;
  contactNewName = "";
  // { "did:...": concatenated-descriptions } entry for each contact
  givenByMeDescriptions: Record<string, string> = {};
  // { "did:...": amount } entry for each contact
  givenByMeConfirmed: Record<string, number> = {};
  // { "did:...": amount } entry for each contact
  givenByMeUnconfirmed: Record<string, number> = {};
  // { "did:...": concatenated-descriptions } entry for each contact
  givenToMeDescriptions: Record<string, string> = {};
  // { "did:...": amount } entry for each contact
  givenToMeConfirmed: Record<string, number> = {};
  // { "did:...": amount } entry for each contact
  givenToMeUnconfirmed: Record<string, number> = {};
  hourDescriptionInput = "";
  hourInput = "0";
  showGiveNumbers = false;
  showGiveTotals = true;
  showGiveConfirmed = true;

  async created() {
    await db.open();
    const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
    this.activeDid = settings?.activeDid || "";
    this.apiServer = settings?.apiServer || "";

    this.showGiveNumbers = !!settings?.showContactGivesInline;
    if (this.showGiveNumbers) {
      this.loadGives();
    }
    const allContacts = await db.contacts.toArray();
    this.contacts = R.sort(
      (a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
      allContacts,
    );

    if (this.contactEndorserUrl) {
      await this.newContactFromScan(this.contactEndorserUrl);
      localStorage.removeItem("contactEndorserUrl");
      this.contactEndorserUrl = "";
    }
  }

  public async getIdentity(activeDid: string) {
    await accountsDB.open();
    const accounts = await accountsDB.accounts.toArray();
    const account = R.find((acc) => acc.did === activeDid, accounts) as Account;
    const identity = JSON.parse(account?.identity || "null");

    if (!identity) {
      throw new Error(
        "Attempted to load Give records with no identity available.",
      );
    }
    return identity;
  }

  public async getHeaders(identity: IIdentifier) {
    const token = await accessToken(identity);
    const headers = {
      "Content-Type": "application/json",
      Authorization: "Bearer " + token,
    };
    return headers;
  }

  public async getHeadersAndIdentity(activeDid: string) {
    const identity = await this.getIdentity(activeDid);
    const headers = await this.getHeaders(identity);

    return { headers, identity };
  }

  async loadGives() {
    const handleResponse = (
      resp: { status: number; data: { data: GiveServerRecord[] } },
      descriptions: Record<string, string>,
      confirmed: Record<string, number>,
      unconfirmed: Record<string, number>,
      useRecipient: boolean,
    ) => {
      if (resp.status === 200) {
        const allData = resp.data.data;
        for (const give of allData) {
          const otherDid = useRecipient ? give.recipientDid : give.agentDid;
          if (give.unit === "HUR") {
            if (give.amountConfirmed) {
              const prevAmount = confirmed[otherDid] || 0;
              confirmed[otherDid] = prevAmount + give.amount;
            } else {
              const prevAmount = unconfirmed[otherDid] || 0;
              unconfirmed[otherDid] = prevAmount + give.amount;
            }
            if (!descriptions[otherDid] && give.description) {
              descriptions[otherDid] = give.description;
            }
          }
        }
      } else {
        console.error(
          "Got bad response status & data of",
          resp.status,
          resp.data,
        );
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Server Error",
            text:
              "Got an error retrieving your " +
              (useRecipient ? "given" : "received") +
              " time from the server.",
          },
          -1,
        );
      }
    };

    try {
      const { headers } = await this.getHeadersAndIdentity(this.activeDid);
      const givenByUrl =
        this.apiServer +
        "/api/v2/report/gives?agentDid=" +
        encodeURIComponent(this.activeDid);
      const givenToUrl =
        this.apiServer +
        "/api/v2/report/gives?recipientDid=" +
        encodeURIComponent(this.activeDid);

      const [givenByMeResp, givenToMeResp] = await Promise.all([
        this.axios.get(givenByUrl, { headers }),
        this.axios.get(givenToUrl, { headers }),
      ]);

      const givenByMeDescriptions = {};
      const givenByMeConfirmed = {};
      const givenByMeUnconfirmed = {};
      handleResponse(
        givenByMeResp,
        givenByMeDescriptions,
        givenByMeConfirmed,
        givenByMeUnconfirmed,
        true,
      );
      this.givenByMeDescriptions = givenByMeDescriptions;
      this.givenByMeConfirmed = givenByMeConfirmed;
      this.givenByMeUnconfirmed = givenByMeUnconfirmed;

      const givenToMeDescriptions = {};
      const givenToMeConfirmed = {};
      const givenToMeUnconfirmed = {};
      handleResponse(
        givenToMeResp,
        givenToMeDescriptions,
        givenToMeConfirmed,
        givenToMeUnconfirmed,
        false,
      );
      this.givenToMeDescriptions = givenToMeDescriptions;
      this.givenToMeConfirmed = givenToMeConfirmed;
      this.givenToMeUnconfirmed = givenToMeUnconfirmed;
    } catch (error) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Server Error",
          text: error as string,
        },
        -1,
      );
    }
  }

  async onClickNewContact(): Promise<void> {
    if (!this.contactInput) {
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "No Contact",
          text: "There was no contact info to add.",
        },
        -1,
      );
      return;
    }
    let did = this.contactInput;
    let name, publicKeyBase64;
    const commaPos1 = this.contactInput.indexOf(",");
    if (commaPos1 > -1) {
      did = this.contactInput.substring(0, commaPos1).trim();
      name = this.contactInput.substring(commaPos1 + 1).trim();
      const commaPos2 = this.contactInput.indexOf(",", commaPos1 + 1);
      if (commaPos2 > -1) {
        name = this.contactInput.substring(commaPos1 + 1, commaPos2).trim();
        publicKeyBase64 = this.contactInput.substring(commaPos2 + 1).trim();
      }
    }
    // help with potential mistakes while this sharing requires copy-and-paste
    if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) {
      // it must be all hex (compressed public key), so convert
      publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
    }
    const newContact = { did, name, publicKeyBase64 };
    return this.addContact(newContact);
  }

  async newContactFromScan(url: string): Promise<void> {
    const payload = getContactPayloadFromJwtUrl(url);
    if (!payload) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "No Contact Info",
          text: "The contact info could not be parsed.",
        },
        -1,
      );
      return;
    } else {
      return this.addContact({
        did: payload.iss,
        name: payload.own.name,
        publicKeyBase64: payload.own.publicEncKey,
      } as Contact);
    }
  }

  async addContact(newContact: Contact) {
    if (!newContact.did) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Incomplete Contact",
          text: "Cannot add a contact without a DID.",
        },
        -1,
      );
      return;
    }
    return db.contacts
      .add(newContact)
      .then(() => {
        const allContacts = this.contacts.concat([newContact]);
        this.contacts = R.sort(
          (a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
          allContacts,
        );
        this.$notify(
          {
            group: "alert",
            type: "success",
            title: "Contact added",
            text: newContact.name + " was added.",
          },
          -1,
        );
      })
      .catch((err) => {
        console.error("Error when adding contact to storage:", err);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Contact Not Added",
            text: "An error prevented importing.",
          },
          -1,
        );
      });
  }

  async deleteContact(contact: Contact) {
    if (
      confirm(
        "Are you sure you want to delete " +
          this.nameForDid(this.contacts, contact.did) +
          " with DID " +
          contact.did +
          " ?",
      )
    ) {
      await db.open();
      await db.contacts.delete(contact.did);
      this.contacts = R.without([contact], this.contacts);
    }
  }

  async register(contact: Contact) {
    if (
      confirm(
        "Are you sure you want to use one of your registrations for " +
          this.nameForDid(this.contacts, contact.did) +
          (contact.registered
            ? " -- especially since they are already marked as registered"
            : "") +
          "?",
      )
    ) {
      this.$notify(
        {
          group: "alert",
          type: "toast",
          text: "",
          title: "Registration submitted...",
        },
        1000,
      );

      const identity = await this.getIdentity(this.activeDid);

      const vcClaim: RegisterVerifiableCredential = {
        "@context": "https://schema.org",
        "@type": "RegisterAction",
        agent: { identifier: identity.did },
        object: SERVICE_ID,
        participant: { identifier: contact.did },
      };
      // Make a payload for the claim
      const vcPayload = {
        vc: {
          "@context": ["https://www.w3.org/2018/credentials/v1"],
          type: ["VerifiableCredential"],
          credentialSubject: vcClaim,
        },
      };
      // Create a signature using private key of identity
      if (identity.keys[0].privateKeyHex !== null) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const privateKeyHex: string = identity.keys[0].privateKeyHex!;
        const signer = await SimpleSigner(privateKeyHex);
        const alg = undefined;
        // Create a JWT for the request
        const vcJwt: string = await didJwt.createJWT(vcPayload, {
          alg: alg,
          issuer: identity.did,
          signer: signer,
        });

        // Make the xhr request payload
        const payload = JSON.stringify({ jwtEncoded: vcJwt });
        const url = this.apiServer + "/api/v2/claim";
        const headers = await this.getHeaders(identity);

        try {
          const resp = await this.axios.post(url, payload, { headers });
          if (resp.data?.success?.embeddedRecordError) {
            let message = "There was some problem with the registration.";
            if (typeof resp.data.success.embeddedRecordError == "string") {
              message += " " + resp.data.success.embeddedRecordError;
            }
            this.$notify(
              {
                group: "alert",
                type: "danger",
                title: "Registration Still Unknown",
                text: message,
              },
              -1,
            );
          } else if (resp.data?.success?.handleId) {
            contact.registered = true;
            db.contacts.update(contact.did, { registered: true });

            this.$notify(
              {
                group: "alert",
                type: "info",
                title: "Registration Success",
                text: contact.name + " has been registered.",
              },
              -1,
            );
          }
        } catch (error) {
          let userMessage = "There was an error. See logs for more info.";
          const serverError = error as AxiosError;
          if (serverError) {
            if (serverError.message) {
              userMessage = serverError.message; // Info for the user
            } else {
              userMessage = JSON.stringify(serverError.toJSON());
            }
          } else {
            userMessage = error as string;
          }
          // Now set that error for the user to see.
          this.$notify(
            {
              group: "alert",
              type: "danger",
              title: "Server Error",
              text: userMessage,
            },
            -1,
          );
        }
      }
    }
  }

  async setVisibility(contact: Contact, visibility: boolean) {
    const url =
      this.apiServer +
      "/api/report/" +
      (visibility ? "canSeeMe" : "cannotSeeMe");
    const identity = await this.getIdentity(this.activeDid);
    const headers = await this.getHeaders(identity);
    const payload = JSON.stringify({ did: contact.did });

    try {
      const resp = await this.axios.post(url, payload, { headers });
      if (resp.status === 200) {
        contact.seesMe = visibility;
        db.contacts.update(contact.did, { seesMe: visibility });
      } else {
        console.error(
          "Got some bad server response when setting visibility: ",
          resp,
        );
        const message =
          resp.data.error?.message || "Bad server response of " + resp.status;
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Server Error",
            text: message,
          },
          -1,
        );
      }
    } catch (err) {
      console.error("Got some server error when setting visibility:", err);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Server Error",
          text: "Check connectivity and try again.",
        },
        -1,
      );
    }
  }

  async checkVisibility(contact: Contact) {
    const url =
      this.apiServer +
      "/api/report/canDidExplicitlySeeMe?did=" +
      encodeURIComponent(contact.did);
    const identity = await this.getIdentity(this.activeDid);
    const headers = await this.getHeaders(identity);

    try {
      const resp = await this.axios.get(url, { headers });
      if (resp.status === 200) {
        const visibility = resp.data;
        contact.seesMe = visibility;
        db.contacts.update(contact.did, { seesMe: visibility });

        this.$notify(
          {
            group: "alert",
            type: "info",
            title: "Refreshed",
            text:
              this.nameForContact(contact, true) +
              " can " +
              (visibility ? "" : "not ") +
              "see your activity.",
          },
          -1,
        );
      } else {
        console.log("Got bad server response when checking visibility: ", resp);
        const message = resp.data.error?.message || "Got bad server response.";
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Server Error",
            text: message,
          },
          -1,
        );
      }
    } catch (err) {
      console.log("Caught error from server request to check visibility:", err);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Server Error",
          text: "Check connectivity and try again.",
        },
        -1,
      );
    }
  }

  // from https://stackoverflow.com/a/175787/845494
  //
  private isNumeric(str: string): boolean {
    return !isNaN(+str);
  }

  private nameForDid(contacts: Array<Contact>, did: string): string {
    const contact = R.find((con) => con.did == did, contacts);
    return this.nameForContact(contact);
  }

  private nameForContact(contact?: Contact, capitalize?: boolean): string {
    return contact?.name || (capitalize ? "T" : "t") + "this unnamed user";
  }

  async onClickAddGive(fromDid: string, toDid: string): Promise<void> {
    const identity = await this.getIdentity(this.activeDid);

    // if they have unconfirmed amounts, ask to confirm those first
    if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {
      const isare = this.givenToMeUnconfirmed[fromDid] == 1 ? "is" : "are";
      const hours = this.givenToMeUnconfirmed[fromDid] == 1 ? "hour" : "hours";
      if (
        confirm(
          "There " +
            isare +
            " " +
            this.givenToMeUnconfirmed[fromDid] +
            " unconfirmed " +
            hours +
            " from them." +
            " Would you like to confirm some of those hours?",
        )
      ) {
        this.$router.push({
          name: "contact-amounts",
          query: { contactDid: fromDid },
        });
        return;
      }
    }
    if (!this.isNumeric(this.hourInput)) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Input Error",
          text: "This is not a valid number of hours: " + this.hourInput,
        },
        -1,
      );
    } else if (!parseFloat(this.hourInput)) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Input Error",
          text: "Giving 0 hours does nothing.",
        },
        -1,
      );
    } else if (!identity) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Status Error",
          text: "No identity is available.",
        },
        -1,
      );
    } else {
      // ask to confirm amount
      let toFrom;
      if (fromDid == identity?.did) {
        toFrom = "from you to " + this.nameForDid(this.contacts, toDid);
      } else {
        toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you";
      }
      let description;
      if (this.hourDescriptionInput) {
        description = " with description '" + this.hourDescriptionInput + "'";
      } else {
        description = " with no description";
      }
      if (
        confirm(
          "Are you sure you want to record " +
            this.hourInput +
            " hour" +
            (this.hourInput == "1" ? "" : "s") +
            " " +
            toFrom +
            description +
            "?",
        )
      ) {
        this.createAndSubmitGive(
          identity,
          fromDid,
          toDid,
          parseFloat(this.hourInput),
          this.hourDescriptionInput,
        );
      }
    }
  }

  private async createAndSubmitGive(
    identity: IIdentifier,
    fromDid: string,
    toDid: string,
    amount: number,
    description: string,
  ): Promise<void> {
    // Make a claim
    const vcClaim: GiveVerifiableCredential = {
      "@context": "https://schema.org",
      "@type": "GiveAction",
      agent: { identifier: fromDid },
      object: { amountOfThisGood: amount, unitCode: "HUR" },
      recipient: { identifier: toDid },
    };
    if (description) {
      vcClaim.description = description;
    }
    // Make a payload for the claim
    const vcPayload = {
      vc: {
        "@context": ["https://www.w3.org/2018/credentials/v1"],
        type: ["VerifiableCredential"],
        credentialSubject: vcClaim,
      },
    };
    // Create a signature using private key of identity
    if (identity.keys[0].privateKeyHex !== null) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const privateKeyHex: string = identity.keys[0].privateKeyHex!;
      const signer = await SimpleSigner(privateKeyHex);
      const alg = undefined;
      // Create a JWT for the request
      const vcJwt: string = await didJwt.createJWT(vcPayload, {
        alg: alg,
        issuer: identity.did,
        signer: signer,
      });

      // Make the xhr request payload

      const payload = JSON.stringify({ jwtEncoded: vcJwt });
      const url = this.apiServer + "/api/v2/claim";
      const headers = await this.getHeaders(identity);

      try {
        const resp = await this.axios.post(url, payload, { headers });
        if (resp.data?.success?.handleId) {
          this.$notify(
            {
              group: "alert",
              type: "success",
              title: "Done",
              text: "Successfully logged time to the server.",
            },
            -1,
          );

          if (fromDid === identity.did) {
            const newList = R.clone(this.givenByMeUnconfirmed);
            newList[toDid] = (newList[toDid] || 0) + amount;
            this.givenByMeUnconfirmed = newList;
          } else {
            const newList = R.clone(this.givenToMeConfirmed);
            newList[fromDid] = (newList[fromDid] || 0) + amount;
            this.givenToMeConfirmed = newList;
          }
        }
      } catch (error) {
        let userMessage = "There was an error. See logs for more info.";
        const serverError = error as AxiosError;
        if (serverError) {
          if (serverError.message) {
            userMessage = serverError.message; // Info for the user
          } else {
            userMessage = JSON.stringify(serverError.toJSON());
          }
        } else {
          userMessage = error as string;
        }
        // Now set that error for the user to see.
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Server Error",
            text: userMessage,
          },
          -1,
        );
      }
    }
  }

  private async onClickCancelName() {
    this.contactEdit = null;
    this.contactNewName = "";
  }

  private async onClickSaveName(contact: Contact, newName: string) {
    contact.name = newName;
    return db.contacts
      .update(contact.did, { name: newName })
      .then(() => (this.contactEdit = null));
  }

  public toggleShowGiveTotals() {
    if (this.showGiveTotals) {
      this.showGiveTotals = false;
      this.showGiveConfirmed = true;
    } else if (this.showGiveConfirmed) {
      this.showGiveTotals = false; // stays the same
      this.showGiveConfirmed = false;
    } else {
      this.showGiveTotals = true;
      this.showGiveConfirmed = true;
    }
  }

  public showGiveAmountsClassNames() {
    return {
      "bg-slate-500": this.showGiveTotals,
      "bg-green-600": !this.showGiveTotals && this.showGiveConfirmed,
      "bg-yellow-600": !this.showGiveTotals && !this.showGiveConfirmed,
    };
  }
}
</script>

<style>
.dialog-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1.5rem;
}
.dialog {
  background-color: white;
  padding: 1rem;
  border-radius: 0.5rem;
  width: 100%;
  max-width: 500px;
}

/*
  Tooltip, generated on "title" attributes on "fa" icons
  Kudos to https://www.w3schools.com/css/css_tooltip.asp
*/
/* Tooltip container */
.tooltip {
  position: relative;
  display: inline-block;
  border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
}
/* Tooltip text */
.tooltip .tooltiptext {
  visibility: hidden;
  width: 200px;
  background-color: black;
  color: #fff;
  text-align: center;
  padding: 5px 0;
  border-radius: 6px;

  position: absolute;
  z-index: 1;
}
/* How do we share with the above so code isn't duplicated? */
.tooltip .tooltiptext-left {
  visibility: hidden;
  width: 200px;
  background-color: black;
  color: #fff;
  text-align: center;
  padding: 5px 0;
  border-radius: 6px;

  position: absolute;
  z-index: 1;

  bottom: 0%;
  right: 105%;
  margin-left: -60px;
}

/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
  visibility: visible;
}
.tooltip:hover .tooltiptext-left {
  visibility: visible;
}
</style>
