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

    <div class="mb-8">
      <h2 class="text-xl font-bold mb-4">Notiwind Alert Test Suite</h2>

      <button
        @click="
          this.$notify(
            {
              group: 'alert',
              type: 'toast',
              text: 'I\'m a toast. Don\'t mind me.',
            },
            5000,
          )
        "
        class="font-bold uppercase bg-slate-400 text-white px-3 py-2 rounded-md mr-2"
      >
        Toast (self-dismiss)
      </button>

      <button
        @click="
          this.$notify(
            {
              group: 'alert',
              type: 'info',
              title: 'Information Alert',
              text: 'Just wanted you to know.',
            },
            -1,
          )
        "
        class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
      >
        Info
      </button>

      <button
        @click="
          this.$notify(
            {
              group: 'alert',
              type: 'success',
              title: 'Success Alert',
              text: 'Congratulations!',
            },
            -1,
          )
        "
        class="font-bold uppercase bg-emerald-600 text-white px-3 py-2 rounded-md mr-2"
      >
        Success
      </button>

      <button
        @click="
          this.$notify(
            {
              group: 'alert',
              type: 'warning',
              title: 'Warning Alert',
              text: 'You might wanna look at this.',
            },
            -1,
          )
        "
        class="font-bold uppercase bg-amber-600 text-white px-3 py-2 rounded-md mr-2"
      >
        Warning
      </button>

      <button
        @click="
          this.$notify(
            {
              group: 'alert',
              type: 'danger',
              title: 'Danger Alert',
              text: 'Something terrible has happened!',
            },
            -1,
          )
        "
        class="font-bold uppercase bg-rose-600 text-white px-3 py-2 rounded-md mr-2"
      >
        Danger
      </button>

      <button
        @click="
          this.$notify(
            {
              group: 'modal',
              type: 'notification-permission',
            },
            -1,
          )
        "
        class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
      >
        Notification Permission
      </button>
    </div>

    <div class="mb-8">
      <h2 class="text-xl font-bold">Quick Action</h2>
      <p class="mb-4">Record a gift from a contact:</p>

      <ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
        <li @click="openDialog()">
          <EntityIcon
            :entityId="Anonymous"
            :iconSize="64"
            class="mx-auto border border-slate-300 rounded-md mb-1"
          ></EntityIcon>
          <h3
            class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
          >
            Anonymous
          </h3>
        </li>
        <li
          v-for="contact in allContacts"
          :key="contact.did"
          @click="openDialog(contact)"
        >
          <EntityIcon
            :entityId="contact.did"
            :iconSize="64"
            class="mx-auto border border-slate-300 rounded-md mb-1"
          ></EntityIcon>
          <h3
            class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
          >
            {{ contact.name || contact.did }}
          </h3>
        </li>
      </ul>

      <!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
      <router-link
        v-if="allContacts.length > 7"
        :to="{ name: 'contact-gives' }"
        class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
      >
        Show More Contacts&hellip;
      </router-link>

      <!-- If there are no contacts, show this instead: -->
      <div
        class="rounded border border-dashed border-slate-300 bg-slate-100 px-4 py-3 text-center italic text-slate-500"
        v-if="allContacts.length === 0"
      >
        (No contacts to show.)
      </div>
    </div>

    <GiftedDialog
      ref="customDialog"
      @dialog-result="handleDialogResult"
      message="Received from"
    >
    </GiftedDialog>

    <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
      <h2 class="text-xl font-bold mb-4">Latest Activity</h2>
      <div :class="{ hidden: isHiddenSpinner }">
        <p class="text-slate-500 text-center italic mt-4 mb-4">
          <fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
        </p>
      </div>
      <ul class="border-t border-slate-300">
        <li
          class="border-b border-slate-300 py-2"
          v-for="record in feedData"
          :key="record.jwtId"
        >
          <div
            class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
            v-if="record.jwtId == feedLastViewedId"
          >
            You've seen all claims below:
          </div>
          <div class="flex">
            <fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
            <!-- icon values: "coins" = money; "clock" = time; "gift" = others -->
            <span class="">{{ this.giveDescription(record) }}</span>
          </div>
        </li>
      </ul>
    </div>
  </section>
</template>

<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue";
import { db, accountsDB } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
import { Contact } from "@/db/tables/contacts";
import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";

@Component({
  components: { GiftedDialog, QuickNav, EntityIcon },
})
export default class HomeView extends Vue {
  activeDid = "";
  allContacts: Array<Contact> = [];
  allMyDids: Array<string> = [];
  apiServer = "";
  feedAllLoaded = false;
  feedData = [];
  feedPreviousOldestId = null;
  feedLastViewedId = null;
  isHiddenSpinner = true;
  numAccounts = 0;

  async beforeCreate() {
    await accountsDB.open();
    this.numAccounts = await accountsDB.accounts.count();
  }

  public async getIdentity(activeDid) {
    await accountsDB.open();
    const account = await accountsDB.accounts
      .where("did")
      .equals(activeDid)
      .first();
    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) {
    const token = await accessToken(identity);
    const headers = {
      "Content-Type": "application/json",
      Authorization: "Bearer " + token,
    };
    return headers;
  }

  async created() {
    try {
      await accountsDB.open();
      const allAccounts = await accountsDB.accounts.toArray();
      this.allMyDids = allAccounts.map((acc) => acc.did);

      await db.open();
      const settings = await db.settings.get(MASTER_SETTINGS_KEY);
      this.apiServer = settings?.apiServer || "";
      this.activeDid = settings?.activeDid || "";
      this.allContacts = await db.contacts.toArray();
      this.feedLastViewedId = settings?.lastViewedClaimId;
      this.updateAllFeed();
    } catch (err) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text:
            err.userMessage ||
            "There was an error retrieving the latest sweet, sweet action.",
        },
        -1,
      );
    }
  }

  public async buildHeaders() {
    const headers = { "Content-Type": "application/json" };

    if (this.activeDid) {
      await accountsDB.open();
      const allAccounts = await accountsDB.accounts.toArray();
      const account = allAccounts.find((acc) => acc.did === this.activeDid);
      const identity = JSON.parse(account?.identity || "null");

      if (!identity) {
        throw new Error(
          "An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
        );
      }

      headers["Authorization"] = "Bearer " + (await accessToken(identity));
    } else {
      // it's OK without auth... we just won't get any identifiers
    }
    return headers;
  }

  public async updateAllFeed() {
    this.isHiddenSpinner = false;
    await this.retrieveClaims(this.apiServer, null, this.feedPreviousOldestId)
      .then(async (results) => {
        if (results.data.length > 0) {
          this.feedData = this.feedData.concat(results.data);
          this.feedAllLoaded = results.hitLimit;
          this.feedPreviousOldestId =
            results.data[results.data.length - 1].jwtId;
          if (
            this.feedLastViewedId == null ||
            this.feedLastViewedId < results.data[0].jwtId
          ) {
            await db.open();
            db.settings.update(MASTER_SETTINGS_KEY, {
              lastViewedClaimId: results.data[0].jwtId,
            });
            // but not for this page because we need to remember what it was before
          }
        }
      })
      .catch((e) => {
        console.log("Error with feed load:", e);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Export Error",
            text: e.userMessage || "There was an error retrieving feed data.",
          },
          -1,
        );
      });

    this.isHiddenSpinner = true;
  }

  public async retrieveClaims(endorserApiServer, identifier, beforeId) {
    const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
    const response = await fetch(
      endorserApiServer + "/api/v2/report/gives?" + beforeQuery,
      {
        method: "GET",
        headers: await this.buildHeaders(),
      },
    );

    if (response.status !== 200) {
      throw await response.text();
    }

    const results = await response.json();

    if (results.data) {
      return results;
    } else {
      throw JSON.stringify(results);
    }
  }

  giveDescription(giveRecord) {
    let claim = giveRecord.fullClaim;
    if (claim.claim) {
      claim = claim.claim;
    }
    // agent.did is for legacy data, before March 2023
    const giverDid = claim.agent?.identifier || claim.agent?.did;
    const giverInfo = didInfo(
      giverDid,
      this.activeDid,
      this.allMyDids,
      this.allContacts,
    );
    const gaveAmount = claim.object?.amountOfThisGood
      ? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
      : claim.description || "something unknown";
    // recipient.did is for legacy data, before March 2023
    const gaveRecipientId = claim.recipient?.identifier || claim.recipient?.did;
    const gaveRecipientInfo = gaveRecipientId
      ? " to " +
        didInfo(
          gaveRecipientId,
          this.activeDid,
          this.allMyDids,
          this.allContacts,
        )
      : "";
    return giverInfo + " gave " + gaveAmount + gaveRecipientInfo;
  }

  displayAmount(code, amt) {
    return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1);
  }

  currencyShortWordForCode(unitCode, single) {
    return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
  }

  openDialog(giver) {
    this.$refs.customDialog.open(giver);
  }

  handleDialogResult(result) {
    if (result.action === "confirm") {
      return new Promise((resolve) => {
        this.recordGive(result.giver?.did, result.description, result.hours);
        resolve();
      });
    } else {
      // action was "cancel" so do nothing
    }
  }

  /**
   *
   * @param giverDid may be null
   * @param description may be an empty string
   * @param hours may be 0
   */
  public async recordGive(giverDid, description, hours) {
    if (!this.activeDid) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "You must select an identity before you can record a give.",
        },
        -1,
      );
      return;
    }

    if (!description && !hours) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "You must enter a description or some number of hours.",
        },
        -1,
      );
      return;
    }

    try {
      const identity = await this.getIdentity(this.activeDid);
      const result = await createAndSubmitGive(
        this.axios,
        this.apiServer,
        identity,
        giverDid,
        this.activeDid,
        description,
        hours,
      );

      if (this.isGiveCreationError(result)) {
        const errorMessage = this.getGiveCreationErrorMessage(result);
        console.log("Error with give result:", result);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: errorMessage || "There was an error recording the give.",
          },
          -1,
        );
      } else {
        this.$notify(
          {
            group: "alert",
            type: "success",
            title: "Success",
            text: "That gift was recorded.",
          },
          -1,
        );
      }
    } catch (error) {
      console.log("Error with give caught:", error);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text:
            this.getGiveErrorMessage(error) ||
            "There was an error recording the give.",
        },
        -1,
      );
    }
  }

  // Helper functions for readability

  isGiveCreationError(result) {
    return result.status !== 201 || result.data?.error;
  }

  getGiveCreationErrorMessage(result) {
    return result.data?.error?.message;
  }

  getGiveErrorMessage(error) {
    return error.userMessage || error.response?.data?.error?.message;
  }
}
</script>