<template>
  <QuickNav />
  <TopMessage />

  <!-- CONTENT -->
  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Back -->
    <div class="text-lg text-center font-light relative px-7">
      <h1 :class="backButtonClasses" @click="$router.back()">
        <font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
      </h1>
    </div>

    <!-- Heading -->
    <h1 id="ViewHeading" class="text-4xl text-center font-light px-4 mb-4">
      End of BVC Saturday Meeting
    </h1>

    <div>
      <h2 class="text-2xl m-2">Confirm</h2>
      <div v-if="loadingConfirms" class="flex justify-center">
        <font-awesome icon="spinner" class="fa-spin-pulse" />
      </div>
      <div v-else-if="claimsToConfirm.length === 0">
        There are no claims yet today for you to confirm.
      </div>
      <ul class="border-t border-slate-300 m-2">
        <li
          v-for="record in claimsToConfirm"
          :key="record.id"
          class="border-b border-slate-300 py-2"
        >
          <div class="grid grid-cols-12">
            <span class="col-span-11 justify-self-start">
              <span>
                <input
                  type="checkbox"
                  :checked="claimsToConfirmSelected.includes(record.id)"
                  class="mr-2 h-6 w-6"
                  @click="
                    claimsToConfirmSelected.includes(record.id)
                      ? claimsToConfirmSelected.splice(
                          claimsToConfirmSelected.indexOf(record.id),
                          1,
                        )
                      : claimsToConfirmSelected.push(record.id)
                  "
                />
              </span>
              {{
                claimSpecialDescription(
                  record,
                  activeDid,
                  allMyDids,
                  allContacts,
                )
              }}
              <a @click="onClickLoadClaim(record.id)">
                <font-awesome
                  icon="file-lines"
                  class="pl-2 text-blue-500 cursor-pointer"
                />
              </a>
            </span>
          </div>
        </li>
      </ul>
    </div>
    <div v-if="claimCountWithHidden > 0" class="border-b border-slate-300 pb-2">
      <span>
        {{ claimCountWithHiddenText }}
        If you don't see expected info above for someone, ask them to check that
        their activity is visible (
        <font-awesome icon="eye" class="fa-fw" />
        <font-awesome icon="arrow-up" class="fa-fw" />
        ) to you on
        <a
          class="text-blue-500 underline cursor-pointer"
          @click="copyContactsLinkToClipboard"
        >
          this page </a
        >.
      </span>
    </div>
    <div v-if="claimCountByUser > 0" class="border-b border-slate-300 pb-2">
      <span>
        {{ claimCountByUserText }}
        which you don't need to confirm.
      </span>
    </div>

    <div>
      <h2 class="text-2xl m-2">Anything else?</h2>
      <div class="m-2 flex">
        <input v-model="someoneGave" type="checkbox" class="h-6 w-6" />
        <span class="pb-2 pl-2 pr-2">The group provided</span>
        <span v-if="someoneGave">
          <input
            v-model="description"
            type="text"
            size="20"
            class="border border-slate-400 h-6 px-2"
          />
          <br />
          (Everyone likes personalized messages! 😁 ... and for a pic:
          <input v-model="supplyGiftDetails" type="checkbox" />)
        </span>
        <!-- This is to match input height to avoid shifting when hiding & showing. -->
        <span v-else class="h-6">...</span>
      </div>
    </div>

    <div v-if="canSubmit" class="flex justify-center mt-4">
      <button :class="submitButtonClasses" @click="record()">
        Sign & Send
      </button>
    </div>
    <div v-else class="flex justify-center mt-4">
      <button :class="disabledButtonClasses">Choose What To Confirm</button>
    </div>
  </section>
</template>

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

import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue";
import { NotificationIface, APP_SERVER } from "../constants/app";
import { Contact } from "../db/tables/contacts";
import {
  GenericCredWrapper,
  GenericVerifiableCredential,
  CreateAndSubmitClaimResult,
} from "../interfaces";
import {
  BVC_MEETUPS_PROJECT_CLAIM_ID,
  claimSpecialDescription,
  containsHiddenDid,
  createAndSubmitConfirmation,
  createAndSubmitGive,
  getHeaders,
} from "../libs/endorserServer";
import { logger } from "../utils/logger";

import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import {
  NOTIFY_ERROR_RETRIEVING_CLAIMS,
  NOTIFY_SENDING_STATUS,
  NOTIFY_CONFIRMATION_SEND_ERROR,
  NOTIFY_ALL_CONFIRMATIONS_ERROR,
  NOTIFY_GIVE_SEND_ERROR,
  NOTIFY_CLAIMS_SEND_ERROR,
  NOTIFY_COPIED_TO_CLIPBOARD,
  createConfirmationSuccessMessage,
  createCombinedSuccessMessage,
} from "@/constants/notifications";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
@Component({
  methods: { claimSpecialDescription },
  components: {
    QuickNav,
    TopMessage,
  },
  mixins: [PlatformServiceMixin],
})
export default class QuickActionBvcEndView extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;
  $router!: Router;

  // Notification helper
  notify!: ReturnType<typeof createNotifyHelpers>;

  activeDid = "";
  allContacts: Array<Contact> = [];
  allMyDids: Array<string> = [];
  apiServer = "";
  claimCountByUser = 0;
  claimCountWithHidden = 0;
  claimsToConfirm: GenericCredWrapper<GenericVerifiableCredential>[] = [];
  claimsToConfirmSelected: string[] = [];
  description = "breakfast";
  loadingConfirms = true;
  someoneGave = false;
  supplyGiftDetails = false;

  // Method used in template
  claimSpecialDescription = claimSpecialDescription;

  // Computed properties for template optimization
  get hasSelectedClaims() {
    return this.claimsToConfirmSelected.length > 0;
  }

  get canSubmit() {
    return this.hasSelectedClaims || (this.someoneGave && this.description);
  }

  get claimCountWithHiddenText() {
    if (this.claimCountWithHidden === 0) return "";
    return this.claimCountWithHidden === 1
      ? "There is 1 other claim with hidden details."
      : `There are ${this.claimCountWithHidden} other claims with hidden details.`;
  }

  get claimCountByUserText() {
    if (this.claimCountByUser === 0) return "";
    return this.claimCountByUser === 1
      ? "There is 1 other claim by you"
      : `There are ${this.claimCountByUser} other claims by you`;
  }

  // Template streamlining: Extract long class strings to computed properties
  get backButtonClasses() {
    return "text-lg text-center px-2 py-1 absolute -left-2 -top-1";
  }

  get submitButtonClasses() {
    return "block text-center text-md font-bold 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-2 py-3 rounded-md w-56";
  }

  get disabledButtonClasses() {
    return "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 w-56";
  }

  async created() {
    this.loadingConfirms = true;

    // Initialize notification helper
    this.notify = createNotifyHelpers(this.$notify);

    const settings = await this.$settings();
    this.apiServer = settings.apiServer || "";
    this.activeDid = settings.activeDid || "";

    this.allContacts = await this.$contacts();

    let currentOrPreviousSat = DateTime.now().setZone("America/Denver");
    if (currentOrPreviousSat.weekday < 6) {
      // it's not Saturday or Sunday,
      // so move back one week before setting to the Saturday
      currentOrPreviousSat = currentOrPreviousSat.minus({ week: 1 });
    }
    const eventStartDateObj = currentOrPreviousSat
      .set({ weekday: 6 })
      .set({ hour: 9 })
      .startOf("hour");

    // Hack, but full ISO pushes the length to 340 which crashes verifyJWT!
    const todayOrPreviousStartDate =
      eventStartDateObj.toISO({
        suppressMilliseconds: true,
      }) || "";

    this.allMyDids = (await this.$getAllAccounts()).map(
      (account) => account.did,
    );
    const headers = await getHeaders(this.activeDid);
    try {
      const response = await fetch(
        this.apiServer +
          "/api/claim/?" +
          "issuedAt_greaterThanOrEqualTo=" +
          encodeURIComponent(todayOrPreviousStartDate) +
          "&excludeConfirmations=true",
        { headers },
      );

      if (!response.ok) {
        logger.error("Bad response", response);
        throw new Error("Bad response when retrieving claims.");
      }
      await response.json().then((data) => {
        const dataByOthers = R.reject(
          (claim: GenericCredWrapper<GenericVerifiableCredential>) =>
            claim.issuer === this.activeDid,
          data,
        );
        const dataByOthersWithoutHidden = R.reject(
          containsHiddenDid,
          dataByOthers,
        );
        this.claimsToConfirm = dataByOthersWithoutHidden;
        this.claimCountByUser = data.length - dataByOthers.length;
        this.claimCountWithHidden =
          dataByOthers.length - dataByOthersWithoutHidden.length;
      });
    } catch (error) {
      logger.error("Error:", error);
      this.notify.error(NOTIFY_ERROR_RETRIEVING_CLAIMS.message, TIMEOUTS.LONG);
    }
    this.loadingConfirms = false;
  }

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

  copyContactsLinkToClipboard() {
    const deepLinkUrl = `${APP_SERVER}/deep-link/did/${this.activeDid}`;
    useClipboard()
      .copy(deepLinkUrl)
      .then(() => {
        this.notify.success(
          NOTIFY_COPIED_TO_CLIPBOARD.message("Your info link"),
          TIMEOUTS.SHORT,
        );
      })
      .catch((error) => {
        logger.error("Failed to copy to clipboard:", error);
        this.notify.error(
          "Failed to copy link to clipboard. Please try again.",
          TIMEOUTS.SHORT,
        );
      });
  }

  async record() {
    try {
      if (this.claimsToConfirmSelected.length > 0) {
        this.notify.toast(
          NOTIFY_SENDING_STATUS.title,
          NOTIFY_SENDING_STATUS.message,
          TIMEOUTS.SHORT,
        );
      }

      // in parallel, make a confirmation for each selected claim and send them all to the server
      const confirmResults: PromiseSettledResult<CreateAndSubmitClaimResult>[] =
        await Promise.allSettled(
          this.claimsToConfirmSelected.map(async (jwtId) => {
            const record = this.claimsToConfirm.find(
              (claim) => claim.id === jwtId,
            );
            if (!record) {
              return { success: false, error: "Record not found." };
            }
            return createAndSubmitConfirmation(
              this.activeDid,
              record.claim as GenericVerifiableCredential,
              record.id,
              record.handleId,
              this.apiServer,
              axios,
            );
          }),
        );
      // check for any rejected confirmations
      const confirmsSucceeded = confirmResults.filter(
        // 'fulfilled' is the status in a successful PromiseFulfilledResult
        (result) => result.status === "fulfilled" && result.value.success,
      );
      if (confirmsSucceeded.length < this.claimsToConfirmSelected.length) {
        logger.error("Error sending confirmations:", confirmResults);
        const errorMessage =
          confirmsSucceeded.length === 0
            ? NOTIFY_ALL_CONFIRMATIONS_ERROR.message
            : NOTIFY_CONFIRMATION_SEND_ERROR.message;
        this.notify.error(errorMessage, TIMEOUTS.LONG);
      }

      // now send the give for the description
      let giveSucceeded = false;
      if (this.someoneGave && !this.supplyGiftDetails) {
        const giveResult = await createAndSubmitGive(
          axios,
          this.apiServer,
          this.activeDid,
          undefined,
          this.activeDid,
          this.description,
          undefined,
          undefined,
          undefined,
          undefined,
          false,
          undefined,
          BVC_MEETUPS_PROJECT_CLAIM_ID,
        );
        giveSucceeded = giveResult.success;
        if (!giveSucceeded) {
          logger.error("Error sending give:", giveResult);
          const errorMessage =
            (giveResult as CreateAndSubmitClaimResult)?.error ||
            NOTIFY_GIVE_SEND_ERROR.message;
          this.notify.error(errorMessage, TIMEOUTS.LONG);
        }
      }
      if (this.someoneGave && this.supplyGiftDetails) {
        // we'll give a success message for the confirmations and go to the gifted details page
        if (confirmsSucceeded.length > 0) {
          const actions = createConfirmationSuccessMessage(
            confirmsSucceeded.length,
          );
          this.notify.success(actions, TIMEOUTS.STANDARD);
        }
        (this.$router as Router).push({
          name: "gifted-details",
          query: {
            description: this.description,
            destinationPathAfter: "/",
            providerProjectId: BVC_MEETUPS_PROJECT_CLAIM_ID,
            recipientDid: this.activeDid,
          },
        });
      } else {
        // just go ahead and print a message for all the activity
        if (confirmsSucceeded.length > 0 || giveSucceeded) {
          const actions = createCombinedSuccessMessage(
            confirmsSucceeded.length,
            giveSucceeded,
          );
          this.notify.success(actions, TIMEOUTS.STANDARD);
          (this.$router as Router).push({ path: "/" });
        } else {
          // errors should have already shown
        }
      }

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } catch (error: any) {
      logger.error("Error sending claims.", error);
      const errorMessage =
        error.userMessage || NOTIFY_CLAIMS_SEND_ERROR.message;
      this.notify.error(errorMessage, TIMEOUTS.LONG);
    }
  }
}
</script>
