<template>
  <QuickNav />
  <!-- CONTENT -->
  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Breadcrumb -->
    <div id="ViewBreadcrumb" class="mb-8">
      <h1 class="text-lg text-center font-light relative px-7">
        <!-- Back -->
        <button
          @click="$router.go(-1)"
          class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
        >
          <fa icon="chevron-left" class="fa-fw" />
        </button>
        Verifiable Claim Details
      </h1>
    </div>

    <!-- Details -->
    <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
      <div class="block flex gap-4 overflow-hidden">
        <div class="overflow-hidden">
          <h2 class="text-md font-bold">
            {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }}
          </h2>
          <div class="text-sm">
            <div>
              {{ veriClaim.id }}
              <button
                @click="
                  libsUtil.doCopyTwoSecRedo(
                    veriClaim.id as string,
                    () => (showIdCopy = !showIdCopy),
                  )
                "
                class="ml-2 mr-2"
              >
                <fa icon="copy" class="text-slate-400 fa-fw" />
              </button>
              <span v-show="showIdCopy">Copied ID</span>
            </div>
            <div>
              <fa icon="message" class="fa-fw text-slate-400" />
              {{ veriClaim.claim?.description }}
            </div>
            <div>
              <fa icon="user" class="fa-fw text-slate-400" />
              {{ veriClaim.issuer }}
              <span v-if="!serverUtil.isEmptyOrHiddenDid(veriClaim.issuer)">
                <button
                  @click="
                    libsUtil.doCopyTwoSecRedo(
                      veriClaim.issuer as string,
                      () => (showDidCopy = !showDidCopy),
                    )
                  "
                  class="ml-2 mr-2"
                >
                  <fa icon="copy" class="text-slate-400 fa-fw" />
                </button>
                <span v-show="showDidCopy">Copied DID</span>
              </span>
            </div>
            <div>
              <fa icon="calendar" class="fa-fw text-slate-400" />
              {{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
            </div>

            <!-- Fullfills Links -->

            <!-- fullfills links for a give -->
            <div v-if="detailsForGive?.fulfillsPlanHandleId">
              <router-link
                :to="
                  '/project/' +
                  encodeURIComponent(detailsForGive?.fulfillsPlanHandleId)
                "
                class="text-blue-500 mt-2"
              >
                Fulfills a bigger plan...
              </router-link>
            </div>
            <!-- if there's another, it's probably fulfilling an offer, too -->
            <div
              v-if="
                detailsForGive?.fulfillsType &&
                detailsForGive?.fulfillsType !== 'PlanAction' &&
                detailsForGive?.fulfillsHandleId
              "
            >
              <!-- router-link to /claim/ only changes URL path -->
              <a
                @click="
                  showDifferentClaimPage(detailsForGive?.fulfillsHandleId)
                "
                class="text-blue-500 mt-4"
              >
                Fulfills
                {{
                  capitalizeAndInsertSpacesBeforeCaps(
                    detailsForGive.fulfillsType,
                  )
                }}...
              </a>
            </div>

            <!-- fullfills links for an offer -->
            <div v-if="detailsForOffer?.fulfillsPlanHandleId">
              <router-link
                :to="
                  '/project/' +
                  encodeURIComponent(detailsForOffer?.fulfillsPlanHandleId)
                "
                class="text-blue-500 mt-4"
              >
                Offered to a bigger plan...
              </router-link>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="flex columns-3">
      <button
        class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
        v-if="
          libsUtil.isGiveRecordTheUserCanConfirm(
            veriClaim,
            activeDid,
            confirmerIdList,
          )
        "
        @click="confirmConfirmClaim()"
      >
        Confirm
        <fa icon="circle-check" class="ml-2 text-white cursor-pointer" />
      </button>

      <span class="px-4 py-2">
        <router-link
          v-if="libsUtil.isGiveAction(veriClaim)"
          :to="'/confirm-gift/' + encodeURIComponent(veriClaim.id)"
          class="col-span-1 text-blue-500"
        >
          Confirmation Details...
        </router-link>
      </span>

      <button
        v-if="libsUtil.canFulfillOffer(veriClaim)"
        @click="openFulfillGiftDialog()"
        class="col-span-1 block w-fit text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
      >
        Affirm Delivery
        <fa icon="hand-holding-heart" class="ml-2 text-white cursor-pointer" />
      </button>
    </div>
    <GiftedDialog ref="customGiveDialog" />

    <div v-if="libsUtil.isGiveAction(veriClaim)">
      <h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2>

      <span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
      <span v-else-if="totalConfirmers() === 1">
        One person has confirmed this.
      </span>
      <span v-else> {{ totalConfirmers() }} people have confirmed this. </span>

      <div v-if="totalConfirmers() > 0">
        <div
          v-if="
            confirmerIdList.length === 0 && confsVisibleToIdList.length === 0
          "
        >
          Nobody that you know confirmed this claim, nor do they have any
          confirmers in their network.
        </div>

        <div
          v-if="confirmerIdList.length === 0 && confsVisibleToIdList.length > 0"
        >
          <!-- Only show if this person has links to confirmers (below). -->
          Nobody that you know has issued or confirmed this claim.
        </div>
        <div v-if="confirmerIdList.length > 0">
          The following people have issued or confirmed this claim.
          <ul class="ml-4">
            <li
              v-for="confirmerId in confirmerIdList"
              :key="confirmerId"
              class="list-disc ml-4"
            >
              <div class="flex gap-4">
                <div class="grow overflow-hidden">
                  <div class="text-sm">
                    {{ didInfo(confirmerId) }}
                    <span v-if="!serverUtil.isEmptyOrHiddenDid(confirmerId)">
                      <button
                        @click="
                          copyToClipboard(
                            'The DID of ' + confirmerId,
                            confirmerId,
                          )
                        "
                      >
                        <fa icon="copy" class="text-slate-400 fa-fw" />
                      </button>
                    </span>
                  </div>
                </div>
              </div>
            </li>
          </ul>
        </div>

        <!--
         Never need to show this message:
         "Nobody that you know can see someone who has confirmed this claim."

         If there is nobody in the confirmerIdList then we'll show the combined "nobody" message.
         If there is somebody in the confirmerIdList then that's all they need to show.
         -->

        <!-- Now show anyone linked to confirmers. -->
        <div v-if="confsVisibleToIdList.length > 0">
          The following people can connect you with people who have issued or
          confirmed this claim.
          <ul class="ml-4">
            <li
              v-for="confsVisibleTo in confsVisibleToIdList"
              :key="confsVisibleTo"
              class="list-disc ml-4"
            >
              <div class="flex gap-4">
                <div class="grow overflow-hidden">
                  <div class="text-sm">
                    {{ didInfo(confsVisibleTo) }}
                    <span v-if="!serverUtil.isEmptyOrHiddenDid(confsVisibleTo)">
                      <button
                        @click="
                          copyToClipboard(
                            'The DID of ' + confsVisibleTo,
                            confsVisibleTo,
                          )
                        "
                      >
                        <fa icon="copy" class="text-slate-400 fa-fw" />
                      </button>
                    </span>
                  </div>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </div>

      <!-- explain if user cannot confirm -->
      <!-- Note that these conditions are mirrored in userCanConfirm(). -->
      <div v-if="confirmerIdList.includes(activeDid)">
        You have confirmed this claim.
      </div>
      <div v-else-if="veriClaim.issuer == activeDid">
        You cannot confirm this because you issued this claim, so you already
        count as confirming it.
      </div>
      <div v-else-if="serverUtil.containsHiddenDid(veriClaim.claim)">
        You cannot confirm this because it contains hidden identifiers.
      </div>
    </div>

    <div>
      <h2 class="font-bold uppercase text-xl mt-8 mb-2">
        {{ serverUtil.containsHiddenDid(veriClaim) ? "Visible " : "" }}Details
      </h2>
      <div
        v-if="
          serverUtil.containsHiddenDid(veriClaim) &&
          R.isEmpty(veriClaimDidsVisible)
        "
        class="mb-2"
      >
        Some of the details are not visible to you; they show as "HIDDEN". They
        are not visible to any of your direct contacts, either.
        <span v-if="canShare">
          If you'd like to ask any of your contacts to take a look and see if
          their contacts can see more details,
          <a @click="onClickShareClaim()" class="text-blue-500"
            >click to send them this info</a
          >
          and see if they are willing to make an introduction.
        </span>
        <span v-else>
          If you'd like to ask any of your contacts to take a look and see if
          their contacts can see more details,
          <a
            @click="copyToClipboard('Location', windowLocation)"
            class="text-blue-500"
            >share this page with them</a
          >
          and see if they are willing to make an introduction.
        </span>
      </div>

      <div v-if="!R.isEmpty(veriClaimDidsVisible)">
        Some of the details are not visible to you but they are visible to some
        of your contacts.
        <span v-if="canShare">
          If you'd like an introduction,
          <a @click="onClickShareClaim()" class="text-blue-500"
            >click to share the information with them and ask if they'll tell
            you more about the participants.</a
          >
        </span>
        <span v-else>
          If you'd like an introduction,
          <a
            @click="copyToClipboard('Location', windowLocation)"
            class="text-blue-500"
            >share this page with them and ask if they'll tell you more about
            about the participants.</a
          >
        </span>

        <div
          v-for="(visibleDidPath, index) of Object.keys(veriClaimDidsVisible)"
          :key="index"
          class="list-disc p-4"
        >
          <div class="text-sm">
            <fa icon="minus" class="fa-fw" />
            The {{ visibleDidPath }} is visible to:
          </div>
          <div class="ml-12 p-1">
            <ul>
              <li
                v-for="(visDid, idx2) of veriClaimDidsVisible[visibleDidPath]"
                :key="idx2"
                class="list-disc"
              >
                <div class="text-sm mt-2">
                  <span>
                    {{ didInfo(visDid) }}
                    <span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)">
                      <button
                        @click="copyToClipboard('The DID of ' + visDid, visDid)"
                      >
                        <fa icon="copy" class="text-slate-400 fa-fw" />
                      </button>
                    </span>
                    <span v-if="veriClaim.publicUrls?.[visDid]"
                      >, found at
                      <fa icon="globe" class="fa-fw text-slate-400" />&nbsp;<a
                        :href="veriClaim.publicUrls?.[visDid]"
                        class="text-blue-500"
                        >{{
                          veriClaim.publicUrls[visDid].substring(
                            veriClaim.publicUrls[visDid].indexOf("//") + 2,
                          )
                        }}
                      </a>
                    </span>
                  </span>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
      <!-- Keep the dump contents directly between > and < to avoid weird spacing. -->
      <pre
        class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md"
        >{{ veriClaimDump }}</pre
      >
    </div>

    <h2 class="font-bold uppercase text-xl mt-8 mb-2">Full Claim</h2>
    <p class="mb-4">
      The full claim includes the claim as it was originally issued, including
      the signature (ie. the proof of issuance by that person).
    </p>
    <div v-if="!fullClaim">
      <p v-if="fullClaimMessage" class="mb-4">
        {{ fullClaimMessage }}
      </p>
      <button
        v-else
        class="block w-full text-center text-md 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 px-1.5 py-2 rounded-md mb-2"
        @click="showFullClaim(veriClaim.id as string)"
      >
        Load Full Claim Details
      </button>
    </div>
    <div v-else>
      <pre>{{ fullClaimDump }}</pre>
    </div>

    <a
      :href="apiServer + '/api/claim/' + veriClaim.id"
      target="_blank"
      class="block w-full text-center text-md 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 px-1.5 py-2 rounded-md mb-2"
    >
      View on the Public Server
    </a>
  </section>
</template>

<script lang="ts">
import { AxiosError } from "axios";
import * as yaml from "js-yaml";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";

import GiftedDialog from "@/components/GiftedDialog.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts";
import { GiverReceiverInputInfo } from "@/libs/endorserServer";

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

  activeDid = "";
  allMyDids: Array<string> = [];
  allContacts: Array<Contact> = [];
  apiServer = "";

  canShare = false;
  confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer
  confsVisibleErrorMessage = "";
  confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer
  detailsForGive = null;
  detailsForOffer = null;
  fullClaim = null;
  fullClaimDump = "";
  fullClaimMessage = "";
  numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
  showDidCopy = false;
  showIdCopy = false;
  veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
  veriClaimDump = "";
  veriClaimDidsVisible = {};
  windowLocation = window.location.href;

  R = R;
  yaml = yaml;
  libsUtil = libsUtil;
  serverUtil = serverUtil;

  resetThisValues() {
    this.confirmerIdList = [];
    this.confsVisibleErrorMessage = "";
    this.confsVisibleToIdList = [];
    this.detailsForGive = null;
    this.detailsForOffer = null;
    this.fullClaim = null;
    this.fullClaimDump = "";
    this.fullClaimMessage = "";
    this.numConfsNotVisible = 0;
    this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
    this.veriClaimDump = "";
  }

  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.allContacts = await db.contacts.toArray();

    await accountsDB.open();
    const accounts = accountsDB.accounts;
    const accountsArr: Array<Account> = await accounts?.toArray();
    this.allMyDids = accountsArr.map((acc) => acc.did);

    const pathParam = window.location.pathname.substring("/claim/".length);
    let claimId;
    if (pathParam) {
      claimId = decodeURIComponent(pathParam);
      await this.loadClaim(claimId, this.activeDid);
    } else {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "No claim ID was provided.",
        },
        -1,
      );
    }

    // When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare
    // then use this truer check: navigator.canShare && navigator.canShare()
    this.canShare = !!navigator.share;
  }

  // insert a space before any capital letters except the initial letter
  // (and capitalize initial letter, just in case)
  capitalizeAndInsertSpacesBeforeCaps(text: string) {
    return !text
      ? ""
      : text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
  }

  totalConfirmers() {
    return (
      this.numConfsNotVisible +
      this.confirmerIdList.length +
      this.confsVisibleToIdList.length
    );
  }

  // Isn't there a better way to make this available to the template?
  didInfo(did: string) {
    return serverUtil.didInfo(
      did,
      this.activeDid,
      this.allMyDids,
      this.allContacts,
    );
  }

  async loadClaim(claimId: string, userDid: string) {
    const urlPath = libsUtil.isGlobalUri(claimId)
      ? "/api/claim/byHandle/"
      : "/api/claim/";
    const url = this.apiServer + urlPath + encodeURIComponent(claimId);
    const headers = await serverUtil.getHeaders(userDid);

    try {
      const resp = await this.axios.get(url, { headers });
      if (resp.status === 200) {
        this.veriClaim = resp.data;
        this.veriClaimDump = yaml.dump(this.veriClaim);
        this.veriClaimDidsVisible = libsUtil.findAllVisibleToDids(
          this.veriClaim,
          true,
        );
      } else {
        // actually, axios typically throws an error so we never get here
        console.error("Error getting claim:", resp);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "There was a problem retrieving that claim.",
          },
          -1,
        );
        return;
      }

      // retrieve more details on Give, Offer, or Plan
      if (this.veriClaim.claimType === "GiveAction") {
        const giveUrl =
          this.apiServer +
          "/api/v2/report/gives?handleId=" +
          encodeURIComponent(this.veriClaim.handleId as string);
        const giveHeaders = await serverUtil.getHeaders(userDid);
        const giveResp = await this.axios.get(giveUrl, {
          headers: giveHeaders,
        });
        if (giveResp.status === 200) {
          this.detailsForGive = giveResp.data.data[0];
        } else {
          console.error("Error getting detailed give info:", giveResp);
        }
      } else if (this.veriClaim.claimType === "Offer") {
        const offerUrl =
          this.apiServer +
          "/api/v2/report/offers?handleId=" +
          encodeURIComponent(this.veriClaim.handleId as string);
        const offerHeaders = await serverUtil.getHeaders(userDid);
        const offerResp = await this.axios.get(offerUrl, {
          headers: offerHeaders,
        });
        if (offerResp.status === 200) {
          this.detailsForOffer = offerResp.data.data[0];
        } else {
          console.error("Error getting detailed offer info:", offerResp);
        }
      }

      // retrieve the list of confirmers
      const confirmUrl =
        this.apiServer +
        "/api/report/issuersWhoClaimedOrConfirmed?claimId=" +
        encodeURIComponent(serverUtil.stripEndorserPrefix(claimId));
      const confirmHeaders = await serverUtil.getHeaders(userDid);
      const response = await this.axios.get(confirmUrl, {
        headers: confirmHeaders,
      });
      if (response.status === 200) {
        const resultList1 = response.data.result || [];
        //const publicUrls = resultList.publicUrls || [];
        delete resultList1.publicUrls;
        const resultList2 = R.reject(serverUtil.isHiddenDid, resultList1);
        const resultList3 = R.reject(
          (did: string) => did === this.veriClaim.issuer,
          resultList2,
        );
        this.confirmerIdList = resultList3;
        this.numConfsNotVisible = resultList1.length - resultList2.length;
        if (resultList3.length === resultList2.length) {
          // the issuer was not in the "visible" list so they must be hidden
          // so subtract them from the non-visible confirmers count
          this.numConfsNotVisible = this.numConfsNotVisible - 1;
        }
        this.confsVisibleToIdList =
          response.data.result.resultVisibleToDids || [];
      } else {
        this.confsVisibleErrorMessage =
          "Had problems retrieving confirmations.";
      }
    } catch (error: unknown) {
      const serverError = error as AxiosError;
      console.error("Error retrieving claim:", serverError);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "Something went wrong retrieving claim data.",
        },
        -1,
      );
    }
  }

  async showFullClaim(claimId: string) {
    const url =
      this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId);
    const headers = await serverUtil.getHeaders(this.activeDid);

    try {
      const resp = await this.axios.get(url, { headers });
      if (resp.status === 200) {
        this.fullClaim = resp.data;
        this.fullClaimDump = yaml.dump(this.fullClaim);
      } else {
        // actually, axios typically throws an error so we never get here
        console.error("Error getting full claim:", resp);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "There was a problem getting that claim. See logs for more info.",
          },
          -1,
        );
      }
    } catch (error: unknown) {
      console.error("Error retrieving full claim:", error);
      const serverError = error as AxiosError;
      if (serverError.response?.status === 403) {
        this.fullClaimMessage =
          "You are not authorized to view the full contents of this claim." +
          " To see all the details, ask the issuer to allow you to see their claims." +
          " If you cannot see the issuer's DID, ask someone in the Confirmations section above." +
          " If there are no connections, you will have to ask people in your" +
          " network for their help, some other way; send them to this page and" +
          " see if they can make a connection for you.";
      } else {
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "Something went wrong retrieving that claim. See logs for more info.",
          },
          -1,
        );
      }
    }
  }

  confirmConfirmClaim() {
    this.$notify(
      {
        group: "modal",
        type: "confirm",
        title: "Confirm",
        text: "Do you personally confirm that this is true?",
        onYes: async () => {
          await this.confirmClaim();
        },
      },
      -1,
    );
  }

  // similar code is found in ProjectViewView
  async confirmClaim() {
    // similar logic is found in endorser-mobile
    const goodClaim = serverUtil.removeSchemaContext(
      serverUtil.removeVisibleToDids(
        serverUtil.addLastClaimOrHandleAsIdIfMissing(
          this.veriClaim.claim,
          this.veriClaim.id,
          this.veriClaim.handleId,
        ),
      ),
    );
    const confirmationClaim: serverUtil.GenericVerifiableCredential = {
      "@context": "https://schema.org",
      "@type": "AgreeAction",
      object: goodClaim,
    };
    const result = await serverUtil.createAndSubmitClaim(
      confirmationClaim,
      this.activeDid,
      this.apiServer,
      this.axios,
    );
    if (result.type === "success") {
      this.$notify(
        {
          group: "alert",
          type: "success",
          title: "Success",
          text: "Confirmation submitted.",
        },
        5000,
      );
    } else {
      console.error("Got error submitting the confirmation:", result);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "There was a problem submitting the confirmation. See logs for more info.",
        },
        -1,
      );
    }
  }

  showDifferentClaimPage(claimId: string) {
    const route = {
      path: "/claim/" + encodeURIComponent(claimId),
    };
    this.$router.push(route).then(async () => {
      this.resetThisValues();
      await this.loadClaim(claimId, this.activeDid);
    });
  }

  openFulfillGiftDialog() {
    const giver: GiverReceiverInputInfo = {
      did: libsUtil.offerGiverDid(this.veriClaim),
    };
    (this.$refs.customGiveDialog as GiftedDialog).open(
      giver,
      undefined,
      this.veriClaim.handleId,
      "Offer fulfilled by " + (giver?.name || "someone not named"),
    );
  }

  copyToClipboard(name: string, text: string) {
    useClipboard()
      .copy(text)
      .then(() => {
        this.$notify(
          {
            group: "alert",
            type: "toast",
            title: "Copied",
            text: (name || "That") + " was copied to the clipboard.",
          },
          2000,
        );
      });
  }

  onClickShareClaim() {
    window.navigator.share({
      title: "Help Connect Me",
      text: "I'm trying to find the full details of this claim. Can you help me?",
      url: this.windowLocation,
    });
  }
}
</script>
