<template>
  <QuickNav selected="Home" />
  <TopMessage />

  <!-- CONTENT -->
  <section id="Content" class="p-2 pb-24 max-w-3xl mx-auto">
    <h1 id="ViewHeading" class="text-4xl text-center font-light px-4 mb-8">
      Time Safari
    </h1>

    <!-- prompt to install notifications -->
    <div class="mb-8">
      <div
        v-if="!notificationsSupported()"
        class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
      >
        <p style="display: inline; align-items: center">
          This currently doesn't support notifications, so let's fix that.
          <br />
          <!-- Note that that exact verbiage shows in the help. -->

          <span v-if="userAgentInfo.getOS().name === 'iOS'">
            Tap on "Share"<img
              src="../assets/help/apple-share-icon.svg"
              alt="Apple 'share' icon"
              width="30"
              style="display: inline; margin: 0 5px; vertical-align: middle"
            />and then "Add to Home Screen"
            <fa icon="square-plus" title="Apple 'Add' icon" />
            and go click on that new app.
          </span>
          <span
            v-else-if="userAgentInfo.getBrowser()?.name?.startsWith('Chrome')"
          >
            You should see a prompt to install, or you can click on the
            top-right dots
            <fa
              icon="ellipsis-vertical"
              title="vertical ellipsis"
              class="fa-fw"
            />
            and then "Install"<img
              src="../assets/help/install-android-chrome.png"
              alt="Android 'install' icon"
              width="30"
              style="display: inline; margin: 0 5px; vertical-align: middle"
            />
            and go use that app. If you already did these steps, reload this app
            so that it is fully detected.
          </span>
          <span v-else>
            Try
            <a href="https://www.google.com/chrome/" class="text-blue-500"
              >Google Chrome</a
            >
            or look for a way to install as an app from this browser.
          </span>
        </p>
      </div>
    </div>

    <div v-if="showShortcutBvc" class="mb-4">
      <router-link
        :to="{ name: 'quick-action-bvc' }"
        class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
      >
        Bountiful Voluntaryist Community Actions</router-link
      >
    </div>

    <!-- show the actions for recognizing a give -->
    <div class="mb-8">
      <div v-if="isCreatingIdentifier">
        <p class="text-slate-500 text-center italic mt-4 mb-4">
          <fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
        </p>
      </div>
      <div
        v-if="!activeDid && !isCreatingIdentifier"
        class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
      >
        <p class="text-lg mb-3">
          Want to connect with your contacts, or share contributions or
          projects?
        </p>
        <router-link
          :to="{ name: 'start' }"
          class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
        >
          Create An Identifier</router-link
        >
      </div>

      <div
        v-else-if="!isRegistered"
        class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
      >
        Someone must register your identifier before you can record anyone's
        giving.
        <router-link
          :to="{ name: 'contact-qr' }"
          class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
        >
          Show Them Your Identifier Info</router-link
        >
        <br />
        To double-check that you're registered,
        <br />
        <router-link :to="{ name: 'account' }" class="text-blue-500">
          see your Usage Limits on the Account
          <fa icon="circle-user" /> page.</router-link
        >
      </div>

      <div v-else>
        <!-- activeDid && isRegistered -->
        <div class="mb-4">
          <h2 class="text-xl font-bold">Record Something Given By:</h2>
        </div>

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

        <div class="flex justify-between">
          <router-link
            v-if="allContacts.length >= 7"
            :to="{ name: 'contact-gives' }"
            class="block text-center text-md font-bold uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
          >
            Choose From All Contacts
          </router-link>
          <button
            @click="openGiftedPrompts()"
            class="block text-center text-md font-bold bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
          >
            Ideas...
          </button>
        </div>
      </div>
    </div>

    <GiftedDialog
      ref="customDialog"
      message="Received from"
      showGivenToUser="true"
    />
    <GiftedPrompts ref="giftedPrompts" />

    <!-- Results List -->
    <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>
      <InfiniteScroll @reached-bottom="loadMoreGives">
        <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 == feedLastViewedClaimId"
            >
              You've already seen all the following
            </div>

            <div class="grid grid-cols-12">
              <span class="col-span-11 justify-self-start">
                <span>
                  <fa
                    v-if="record.giver.known || record.receiver.known"
                    icon="circle-user"
                    class="col-span-1 pt-1 pl-0 pr-3 text-slate-500"
                  />
                  <fa
                    v-else
                    icon="gift"
                    class="col-span-1 pt-1 pl-3 pr-0 text-slate-500"
                  />
                </span>
                {{ giveDescription(record) }}
                <a @click="onClickLoadClaim(record.jwtId)">
                  <fa
                    icon="circle-info"
                    class="pl-2 text-blue-500 cursor-pointer"
                  ></fa>
                </a>
              </span>
              <span class="col-span-1 justify-self-end shrink">
                <router-link
                  v-if="record.fulfillsPlanHandleId"
                  :to="
                    '/project/' +
                    encodeURIComponent(record.fulfillsPlanHandleId)
                  "
                  class="justify-end"
                >
                  <fa icon="hammer" class="ml-4 pl-2 text-blue-500"></fa>
                </router-link>
              </span>
            </div>
            <div v-if="record.image" class="flex justify-center">
              <a :href="record.image" target="_blank">
                <img :src="record.image" class="h-24 mt-2 rounded-xl" />
              </a>
            </div>
          </li>
        </ul>
      </InfiniteScroll>
      <div v-if="isFeedLoading">
        <p class="text-slate-500 text-center italic mt-4 mb-4">
          <fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
        </p>
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import { UAParser } from "ua-parser-js";
import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator";

import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue";
import GiftedPrompts from "@/components/GiftedPrompts.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
  contactForDid,
  didInfoForContact,
  GiverInputInfo,
  GiveServerRecord,
} from "@/libs/endorserServer";
import { generateSaveAndActivateIdentity } from "@/libs/util";

interface GiveRecordWithContactInfo extends GiveServerRecord {
  giver: {
    displayName: string;
    known: boolean;
  };
  image: string;
  receiver: {
    displayName: string;
    known: boolean;
  };
}

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

  activeDid = "";
  allContacts: Array<Contact> = [];
  allMyDids: Array<string> = [];
  apiServer = "";
  feedData: GiveRecordWithContactInfo[] = [];
  feedPreviousOldestId?: string;
  feedLastViewedClaimId?: string;
  isCreatingIdentifier = false;
  isFeedLoading = true;
  isRegistered = false;
  showShortcutBvc = false;
  userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html

  public async getIdentity(activeDid: string) {
    await accountsDB.open();
    const account = (await accountsDB.accounts
      .where("did")
      .equals(activeDid)
      .first()) as Account;
    const identity = JSON.parse(account?.identity || "null");
    return identity; // may be null
  }

  public async getHeaders(identity: IIdentifier) {
    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)) as Settings;
      this.apiServer = settings?.apiServer || "";
      this.activeDid = settings?.activeDid || "";
      this.allContacts = await db.contacts.toArray();
      this.feedLastViewedClaimId = settings?.lastViewedClaimId;
      this.isRegistered = !!settings?.isRegistered;
      this.showShortcutBvc = !!settings?.showShortcutBvc;

      if (this.allMyDids.length === 0) {
        this.isCreatingIdentifier = true;
        this.activeDid = await generateSaveAndActivateIdentity();
        this.allMyDids = [this.activeDid];
        this.isCreatingIdentifier = false;
      }

      // this returns a Promise but we don't need to wait for it

      await this.updateAllFeed();

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } catch (err: any) {
      console.error("Error retrieving settings or feed.", err);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text:
            err.userMessage ||
            "There was an error retrieving your settings or the latest activity.",
        },
        -1,
      );
    }
  }

  notificationsSupported() {
    return "Notification" in window;
  }

  public async buildHeaders() {
    const headers: HeadersInit = {
      "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,
      ) as Account;
      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. Switch your ID.",
        );
      }

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

  /**
   * Data loader used by infinite scroller
   * @param payload is the flag from the InfiniteScroll indicating if it should load
   **/
  public async loadMoreGives(payload: boolean) {
    if (payload) {
      this.updateAllFeed();
    }
  }

  public async updateAllFeed() {
    this.isFeedLoading = true;
    await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
      .then(async (results) => {
        if (results.data.length > 0) {
          // include the descriptions of the giver and receiver
          const newFeedData: GiveRecordWithContactInfo = results.data.map(
            (record: GiveServerRecord) => {
              // similar code is in endorser-mobile utility.ts
              // claim.claim happen for some claims wrapped in a Verifiable Credential
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
              const claim = (record.fullClaim as any).claim || record.fullClaim;
              // agent.did is for legacy data, before March 2023
              const giverDid =
                claim.agent?.identifier || (claim.agent as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
              // recipient.did is for legacy data, before March 2023
              const recipientDid =
                claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
              return {
                ...record,
                giver: didInfoForContact(
                  giverDid,
                  this.activeDid,
                  contactForDid(giverDid, this.allContacts),
                  this.allMyDids,
                ),
                image: claim.image,
                receiver: didInfoForContact(
                  recipientDid,
                  this.activeDid,
                  contactForDid(recipientDid, this.allContacts),
                  this.allMyDids,
                ),
              };
            },
          );
          this.feedData = this.feedData.concat(newFeedData);
          this.feedPreviousOldestId =
            results.data[results.data.length - 1].jwtId;
          // The following update is only done on the first load.
          if (
            this.feedLastViewedClaimId == null ||
            this.feedLastViewedClaimId < results.data[0].jwtId
          ) {
            await db.open();
            db.settings.update(MASTER_SETTINGS_KEY, {
              lastViewedClaimId: results.data[0].jwtId,
            });
          }
        }
      })
      .catch((e) => {
        console.error("Error with feed load:", e);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Feed Error",
            text: e.userMessage || "There was an error retrieving feed data.",
          },
          -1,
        );
      });
    this.isFeedLoading = false;
  }

  /**
   * Retrieve claims in reverse chronological order
   *
   * @param beforeId the earliest ID (of previous searches) to search earlier
   * @return claims in reverse chronological order
   */
  public async retrieveGives(endorserApiServer: string, beforeId?: string) {
    const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
    const response = await fetch(
      endorserApiServer +
        "/api/v2/report/gives?giftNotTrade=true&" +
        beforeQuery,
      {
        method: "GET",
        headers: await this.buildHeaders(),
      },
    );

    if (!response.ok) {
      throw await response.text();
    }

    const results = await response.json();

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

  giveDescription(giveRecord: GiveRecordWithContactInfo) {
    // similar code is in endorser-mobile utility.ts
    // claim.claim happen for some claims wrapped in a Verifiable Credential
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const claim = (giveRecord.fullClaim as any).claim || giveRecord.fullClaim;

    let gaveAmount = claim.object?.amountOfThisGood
      ? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
      : "";
    if (claim.description) {
      if (gaveAmount) {
        gaveAmount = " (and " + gaveAmount + ")";
      }
      gaveAmount = claim.description + gaveAmount;
    }
    if (!gaveAmount) {
      gaveAmount = "something not described";
    }

    /**
     * Only show giver and/or receiver info first if they're named.
     * - If only giver is named, show "... gave"
     * - If only receiver is named, show "... received"
     */

    const giverInfo = giveRecord.giver;
    const recipientInfo = giveRecord.receiver;
    if (giverInfo.known && recipientInfo.known) {
      // both giver and recipient are named
      return `${giverInfo.displayName} gave to ${recipientInfo.displayName}: ${gaveAmount}`;
    } else if (giverInfo.known) {
      // giver is named but recipient is not
      return `${giverInfo.displayName} gave: ${gaveAmount} (to ${recipientInfo.displayName})`;
    } else if (recipientInfo.known) {
      // recipient is named but giver is not
      return `${recipientInfo.displayName} received: ${gaveAmount} (from ${giverInfo.displayName})`;
    } else {
      // neither giver nor recipient are named
      let peopleInfo;
      if (giverInfo.displayName === recipientInfo.displayName) {
        peopleInfo = `between two who are ${giverInfo.displayName}`;
      } else {
        peopleInfo = `from ${giverInfo.displayName} to ${recipientInfo.displayName}`;
      }
      return gaveAmount + " (" + peopleInfo + ")";
    }
  }

  onClickLoadClaim(jwtId: string) {
    const route = {
      path: "/claim/" + encodeURIComponent(jwtId),
    };
    this.$router.push(route);
  }

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

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

  openDialog(giver?: GiverInputInfo) {
    (this.$refs.customDialog as GiftedDialog).open(giver);
  }

  openGiftedPrompts() {
    (this.$refs.giftedPrompts as GiftedPrompts).open();
  }
}
</script>
