<template>
  <QuickNav />
  <TopMessage />

  <!-- CONTENT -->
  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Back -->
    <div
      v-if="!hideBackButton"
      class="text-lg text-center font-light relative px-7"
    >
      <h1
        class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
        @click="cancelBack()"
      >
        <fa icon="chevron-left" class="fa-fw"></fa>
      </h1>
    </div>

    <!-- Heading -->
    <h1 class="text-4xl text-center font-light px-4 mb-4">What Was Given</h1>

    <h1 class="text-xl font-bold text-center mb-4">
      <span>From {{ giverName }}</span>
      <span>
        to
        {{
          givenToProject
            ? projectName
            : givenToRecipient
              ? recipientName
              : "someone unidentified"
        }}</span
      >
    </h1>
    <textarea
      class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
      placeholder="What was received"
      v-model="description"
    />
    <div class="flex flex-row justify-center">
      <span
        class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
        @click="changeUnitCode()"
      >
        {{ libsUtil.UNIT_SHORT[unitCode] || unitCode }}
      </span>
      <div
        class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
        @click="amountInput === '0' ? null : decrement()"
      >
        <fa icon="chevron-left" />
      </div>
      <input
        type="number"
        class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
        v-model="amountInput"
      />
      <div
        class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
        @click="increment()"
      >
        <fa icon="chevron-right" />
      </div>
    </div>

    <div class="flex justify-center mt-4" data-testid="imagery">
      <span v-if="imageUrl" class="flex justify-between">
        <a :href="imageUrl" target="_blank">
          <img :src="imageUrl" class="h-24 rounded-xl" />
        </a>
        <fa
          icon="trash-can"
          @click="confirmDeleteImage"
          class="text-red-500 fa-fw ml-8 mt-10"
        />
      </span>
      <span v-else>
        <fa
          icon="camera"
          class="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-2 rounded-md"
          @click="openImageDialog"
        />
      </span>
    </div>
    <ImageMethodDialog ref="imageDialog" />

    <div class="h-7 mt-4 flex">
      <input
        v-if="projectId && !givenToRecipient"
        type="checkbox"
        class="h-6 w-6 mr-2"
        v-model="givenToProject"
      />
      <fa
        v-else
        icon="square"
        class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
        @click="notifyUserOfProject()"
      />
      <label class="text-sm mt-1">
        {{
          projectId
            ? "This was given to " + projectName
            : "No project was chosen"
        }}
      </label>
    </div>

    <div class="h-7 mt-4 flex">
      <input
        v-if="recipientDid && !givenToProject"
        type="checkbox"
        class="h-6 w-6 mr-2"
        v-model="givenToRecipient"
      />
      <fa
        v-else
        icon="square"
        class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
        @click="notifyUserOfRecipient()"
      />
      <label class="text-sm mt-1">
        {{
          recipientDid
            ? "This was given to " + recipientName
            : "No recipient was chosen."
        }}
      </label>
    </div>

    <div class="mt-4 flex">
      <input type="checkbox" class="h-6 w-6 mr-2" v-model="isTrade" />
      <label class="text-sm mt-1">This was a trade (not a gift)</label>
    </div>

    <div class="mt-4 flex">
      <router-link
        :to="{
          name: 'claim-add-raw',
          query: {
            claim: constructGiveParam(),
          },
        }"
        class="text-blue-500"
      >
        Edit & Submit Raw
      </router-link>
    </div>

    <p class="text-center mb-2 mt-6 italic">
      Sign & Send to publish to the world
      <fa
        icon="circle-info"
        class="pl-2 text-blue-500 cursor-pointer"
        @click="explainData()"
      />
    </p>
    <div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
      <button
        class="block w-full text-center text-lg 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 px-2 py-3 rounded-md"
        @click="confirm"
      >
        Sign &amp; Send
      </button>
      <button
        class="block w-full text-center text-md 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-1.5 py-2 rounded-md"
        @click="cancel"
      >
        Cancel
      </button>
    </div>
  </section>
</template>

<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";

import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import {
  createAndSubmitGive,
  didInfo,
  editAndSubmitGive,
  GenericCredWrapper,
  getHeaders,
  getPlanFromCache,
  GiveVerifiableCredential,
  hydrateGive,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { Contact } from "@/db/tables/contacts";

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

  activeDid = "";
  apiServer = "";

  amountInput = "0";
  description = "";
  destinationPathAfter = "";
  givenToProject = false;
  givenToRecipient = false;
  giverDid: string | undefined;
  giverName = "";
  hideBackButton = false;
  imageUrl = "";
  isTrade = false;
  message = "";
  offerId = "";
  prevCredToEdit?: GenericCredWrapper<GiveVerifiableCredential>;
  projectId = "";
  projectName = "a project";
  recipientDid = "";
  recipientName = "";
  unitCode = "HUR";

  libsUtil = libsUtil;

  async mounted() {
    try {
      this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"]
        ? (JSON.parse(
            (this.$route as Router).query["prevCredToEdit"],
          ) as GenericCredWrapper<GiveVerifiableCredential>)
        : undefined;
    } catch (error) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Retrieval Error",
          text: "The previous record isn't available for editing. If you submit, you'll create a new record.",
        },
        6000,
      );
    }

    const prevAmount = this.prevCredToEdit?.claim?.object?.amountOfThisGood;
    this.amountInput =
      (this.$route as Router).query["amountInput"] ||
      (prevAmount ? String(prevAmount) : "") ||
      this.amountInput;
    this.description =
      (this.$route as Router).query["description"] ||
      this.prevCredToEdit?.claim?.description ||
      this.description;
    this.destinationPathAfter = (this.$route as Router).query[
      "destinationPathAfter"
    ];
    this.giverDid = ((this.$route as Router).query["giverDid"] ||
      this.prevCredToEdit?.claim?.agent?.identifier ||
      this.giverDid) as string;
    this.giverName =
      ((this.$route as Router).query["giverName"] as string) || "";
    this.hideBackButton =
      (this.$route as Router).query["hideBackButton"] === "true";
    this.message = ((this.$route as Router).query["message"] as string) || "";
    // find any offer ID
    const fulfills = this.prevCredToEdit?.claim?.fulfills;
    const fulfillsArray = Array.isArray(fulfills)
      ? fulfills
      : fulfills
        ? [fulfills]
        : [];
    const offer = fulfillsArray.find((rec) => rec["@type"] === "Offer");
    this.offerId = ((this.$route as Router).query["offerId"] ||
      offer?.identifier ||
      this.offerId) as string;

    // find any project ID
    const project = fulfillsArray.find((rec) => rec["@type"] === "PlanAction");
    this.projectId = ((this.$route as Router).query["projectId"] ||
      project?.identifier ||
      this.projectId) as string;

    this.recipientDid = ((this.$route as Router).query["recipientDid"] ||
      this.prevCredToEdit?.claim?.recipient?.identifier) as string;
    this.recipientName =
      ((this.$route as Router).query["recipientName"] as string) || "";
    this.unitCode = ((this.$route as Router).query["unitCode"] ||
      this.prevCredToEdit?.claim?.object?.unitCode ||
      this.unitCode) as string;

    this.imageUrl =
      ((this.$route as Router).query["imageUrl"] as string) ||
      this.prevCredToEdit?.claim?.image ||
      localStorage.getItem("imageUrl") ||
      this.imageUrl;

    // this is an endpoint for sharing project info to highlight something given
    // https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
    if ((this.$route as Router).query["shareTitle"]) {
      this.description =
        ((this.$route as Router).query["shareTitle"] as string) +
        (this.description ? "\n" + this.description : "");
    }
    if ((this.$route as Router).query["shareText"]) {
      this.description =
        (this.description ? this.description + "\n" : "") +
        ((this.$route as Router).query["shareText"] as string);
    }
    if ((this.$route as Router).query["shareUrl"]) {
      this.imageUrl = (this.$route as Router).query["shareUrl"] as string;
    }

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

      let allContacts: Contact[] = [];
      let allMyDids: string[] = [];
      if (
        (this.giverDid && !this.giverName) ||
        (this.recipientDid && !this.recipientName)
      ) {
        allContacts = await db.contacts.toArray();

        await accountsDB.open();
        const allAccounts = await accountsDB.accounts.toArray();
        allMyDids = allAccounts.map((acc) => acc.did);
        if (this.giverDid && !this.giverName) {
          this.giverName = didInfo(
            this.giverDid,
            this.activeDid,
            allMyDids,
            allContacts,
          );
        }
        if (this.recipientDid && !this.recipientName) {
          this.recipientName = didInfo(
            this.recipientDid,
            this.activeDid,
            allMyDids,
            allContacts,
          );
        }
      }
      this.givenToProject = !!this.projectId;
      this.givenToRecipient = !this.givenToProject && !!this.recipientDid;

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

    if (this.projectId) {
      // console.log("Getting project name from cache", this.projectId);
      const project = await getPlanFromCache(
        this.projectId,
        this.axios,
        this.apiServer,
        this.activeDid,
      );
      this.projectName = project?.name
        ? "the project: " + project.name
        : "a project";
    }
  }

  changeUnitCode() {
    const units = Object.keys(this.libsUtil.UNIT_SHORT);
    const index = units.indexOf(this.unitCode);
    this.unitCode = units[(index + 1) % units.length];
  }

  increment() {
    this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
  }

  decrement() {
    this.amountInput = `${Math.max(
      0,
      (parseFloat(this.amountInput) || 1) - 1,
    )}`;
  }

  cancel() {
    this.deleteImage(); // not awaiting, so they'll go back immediately
    if (this.destinationPathAfter) {
      (this.$router as Router).push({ path: this.destinationPathAfter });
    } else {
      (this.$router as Router).back();
    }
  }

  cancelBack() {
    this.deleteImage(); // not awaiting, so they'll go back immediately
    (this.$router as Router).back();
  }

  openImageDialog() {
    (this.$refs.imageDialog as ImageMethodDialog).open((imgUrl) => {
      this.imageUrl = imgUrl;
    }, "GiveAction");
  }

  confirmDeleteImage() {
    this.$notify(
      {
        group: "modal",
        type: "confirm",
        title: "Are you sure you want to delete the image?",
        text: "",
        onYes: this.deleteImage,
      },
      -1,
    );
  }

  async deleteImage() {
    if (!this.imageUrl) {
      return;
    }
    try {
      const headers = await getHeaders(this.activeDid);
      const response = await this.axios.delete(
        DEFAULT_IMAGE_API_SERVER +
          "/image/" +
          encodeURIComponent(this.imageUrl),
        { headers },
      );
      if (response.status === 204) {
        // don't bother with a notification
        // (either they'll simply continue or they're canceling and going back)
      } else {
        console.error("Problem deleting image:", response);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "There was a problem deleting the image.",
          },
          5000,
        );
        // keep the imageUrl in localStorage so the user can try again if they want
        return;
      }

      localStorage.removeItem("imageUrl");
      this.imageUrl = "";
    } catch (error) {
      console.error("Error deleting image:", error);
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      if ((error as any).response.status === 404) {
        console.log("The image was already deleted:", error);

        localStorage.removeItem("imageUrl");
        this.imageUrl = "";

        // it already doesn't exist so we won't say anything to the user
      } else {
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "There was an error deleting the image.",
          },
          5000,
        );
      }
    }
  }

  async confirm() {
    if (!this.activeDid) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "You must select an identifier before you can record a give.",
        },
        2000,
      );
      return;
    }
    if (parseFloat(this.amountInput) < 0) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          text: "You may not send a negative number.",
          title: "",
        },
        2000,
      );
      return;
    }
    if (!this.description && !parseFloat(this.amountInput)) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: `You must enter a description or some number of ${
            this.libsUtil.UNIT_LONG[this.unitCode]
          }.`,
        },
        2000,
      );
      return;
    }

    this.$notify(
      {
        group: "alert",
        type: "toast",
        text: "Recording the give...",
        title: "",
      },
      1000,
    );

    // this is asynchronous, but we don't need to wait for it to complete
    await this.recordGive();
  }

  notifyUserOfProject() {
    if (!this.projectId) {
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "Error",
          text: "To assign to a project, you must open this dialog through a project.",
        },
        3000,
      );
    } else {
      // must be because givenToRecipient is true
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "Error",
          text: "You cannot assign both to a project and to a recipient.",
        },
        3000,
      );
    }
  }

  notifyUserOfRecipient() {
    if (!this.recipientDid) {
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "Error",
          text: "To assign to a recipient, you must open this dialog from a contact.",
        },
        3000,
      );
    } else {
      // must be because givenToProject is true
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "Error",
          text: "You cannot assign both to a recipient and to a project.",
        },
        3000,
      );
    }
  }

  /**
   *
   * @param giverDid may be null
   * @param description may be an empty string
   * @param amountInput may be 0
   * @param unitCode may be omitted, defaults to "HUR"
   */
  public async recordGive() {
    try {
      const recipientDid = this.givenToRecipient
        ? this.recipientDid
        : undefined;
      const projectId = this.givenToProject ? this.projectId : undefined;
      let result;
      if (this.prevCredToEdit) {
        // don't create from a blank one in case some properties were set from a different interface
        result = await editAndSubmitGive(
          this.axios,
          this.apiServer,
          this.prevCredToEdit,
          this.activeDid,
          this.giverDid,
          recipientDid,
          this.description,
          parseFloat(this.amountInput),
          this.unitCode,
          projectId,
          this.offerId,
          this.isTrade,
          this.imageUrl,
        );
      } else {
        result = await createAndSubmitGive(
          this.axios,
          this.apiServer,
          this.activeDid,
          this.giverDid,
          recipientDid,
          this.description,
          parseFloat(this.amountInput),
          this.unitCode,
          projectId,
          this.offerId,
          this.isTrade,
          this.imageUrl,
        );
      }

      if (
        result.type === "error" ||
        this.isGiveCreationError(result.response)
      ) {
        const errorMessage = this.getGiveCreationErrorMessage(result);
        console.error("Error with give creation result:", result);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: errorMessage || "There was an error creating the give.",
          },
          -1,
        );
      } else {
        this.$notify(
          {
            group: "alert",
            type: "success",
            title: "Success",
            text: `That ${this.isTrade ? "trade" : "gift"} was recorded.`,
          },
          5000,
        );
        localStorage.removeItem("imageUrl");
        if (this.destinationPathAfter) {
          (this.$router as Router).push({ path: this.destinationPathAfter });
        } else {
          (this.$router as Router).back();
        }
      }
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } catch (error: any) {
      console.error("Error with give recordation caught:", error);
      const errorMessage =
        error.userMessage ||
        error.response?.data?.error?.message ||
        "There was an error recording the give.";
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: errorMessage,
        },
        -1,
      );
    }
  }

  constructGiveParam() {
    const recipientDid = this.givenToRecipient ? this.recipientDid : undefined;
    const projectId = this.givenToProject ? this.projectId : undefined;
    // const giveClaim = constructGive(
    const giveClaim = hydrateGive(
      this.prevCredToEdit?.claim as GiveVerifiableCredential,
      this.giverDid,
      recipientDid,
      this.description,
      parseFloat(this.amountInput),
      this.unitCode,
      projectId,
      this.offerId,
      this.isTrade,
      this.imageUrl,
      this.prevCredToEdit?.id as string,
    );
    const claimStr = JSON.stringify(giveClaim);
    return claimStr;
  }

  // Helper functions for readability

  /**
   * @param result response "data" from the server
   * @returns true if the result indicates an error
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  isGiveCreationError(result: any) {
    return result.status !== 201 || result.data?.error;
  }

  /**
   * @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
   * @returns best guess at an error message
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getGiveCreationErrorMessage(result: any) {
    return (
      result.error?.userMessage ||
      result.error?.error ||
      result.response?.data?.error?.message
    );
  }

  explainData() {
    this.$notify(
      {
        group: "alert",
        type: "success",
        title: "Data Sharing",
        text: libsUtil.PRIVACY_MESSAGE,
      },
      -1,
    );
  }
}
</script>
