<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 Is Offered</h1>

    <h1 class="text-xl font-bold text-center mb-4">
      <span>
        Offer to
        {{
          offeredToProject
            ? projectName
            : offeredToRecipient
              ? recipientName
              : "someone unidentified"
        }}</span
      >
    </h1>
    <textarea
      class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
      placeholder="What is offered"
      v-model="descriptionOfItem"
      data-testId="itemDescription"
    />
    <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"
        data-testId="inputOfferAmount"
      />
      <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 flex-row mt-2">
      <span
        class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center px-2 py-2"
      >
        Conditions
      </span>
      <textarea
        class="w-full border border-slate-400 px-3 py-2 rounded-r"
        placeholder="Prerequisites, other people to include, etc."
        v-model="descriptionOfCondition"
      />
    </div>

    <div class="flex flex-row mt-2">
      <span
        class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center px-2 py-2"
      >
        {{ validThroughDateInput ? "" : "No" }}&nbsp;Expiration
      </span>
      <input
        v-model="validThroughDateInput"
        type="date"
        class="w-full rounded border border-slate-400 px-3 py-2 rounded-r"
      />
    </div>

    <div class="h-7 mt-4 flex">
      <input
        v-if="projectId && !offeredToRecipient"
        type="checkbox"
        class="h-6 w-6 mr-2"
        v-model="offeredToProject"
      />
      <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 is offered to " + projectName
            : "No project was chosen"
        }}
      </label>
    </div>

    <div class="h-7 mt-4 flex">
      <input
        v-if="recipientDid && !offeredToProject"
        type="checkbox"
        class="h-6 w-6 mr-2"
        v-model="offeredToRecipient"
      />
      <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 is offered to " + recipientName
            : "No recipient was chosen."
        }}
      </label>
    </div>

    <div v-if="showGeneralAdvanced" class="mt-4 flex">
      <router-link
        :to="{
          name: 'claim-add-raw',
          query: {
            claim: constructOfferParam(),
          },
        }"
        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 QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import {
  createAndSubmitOffer,
  didInfo,
  editAndSubmitOffer,
  GenericCredWrapper,
  getPlanFromCache,
  hydrateOffer,
  OfferVerifiableCredential,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { Contact } from "@/db/tables/contacts";

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

  activeDid = "";
  apiServer = "";

  amountInput = "0";
  descriptionOfCondition = "";
  descriptionOfItem = "";
  destinationPathAfter = "";
  hideBackButton = false;
  message = "";
  offeredToProject = false;
  offeredToRecipient = false;
  offererDid: string | undefined;
  offerId = "";
  prevCredToEdit?: GenericCredWrapper<OfferVerifiableCredential>;
  projectId = "";
  projectName = "a project";
  recipientDid = "";
  recipientName = "";
  showGeneralAdvanced = false;
  unitCode = "HUR";
  validThroughDateInput = "";

  libsUtil = libsUtil;

  async mounted() {
    try {
      this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"]
        ? (JSON.parse(
            (this.$route as Router).query["prevCredToEdit"],
          ) as GenericCredWrapper<OfferVerifiableCredential>)
        : 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?.includesObject?.amountOfThisGood;
    this.amountInput =
      (this.$route as Router).query["amountInput"] ||
      (prevAmount ? String(prevAmount) : "") ||
      this.amountInput;
    this.unitCode = ((this.$route as Router).query["unitCode"] ||
      this.prevCredToEdit?.claim?.includesObject?.unitCode ||
      this.unitCode) as string;

    this.descriptionOfCondition =
      this.prevCredToEdit?.claim?.description || this.descriptionOfCondition;
    this.descriptionOfItem =
      (this.$route as Router).query["description"] ||
      this.prevCredToEdit?.claim?.itemOffered?.description ||
      this.descriptionOfItem;
    this.destinationPathAfter = (this.$route as Router).query[
      "destinationPathAfter"
    ];
    this.offererDid = ((this.$route as Router).query["offererDid"] ||
      this.prevCredToEdit?.claim?.agent?.identifier ||
      this.offererDid) as string;
    this.hideBackButton =
      (this.$route as Router).query["hideBackButton"] === "true";
    this.message = ((this.$route as Router).query["message"] as string) || "";

    // find any project ID
    let project;
    if (
      this.prevCredToEdit?.claim?.itemOffered?.isPartOf?.["@type"] ===
      "PlanAction"
    ) {
      project = this.prevCredToEdit?.claim?.itemOffered?.isPartOf;
    }
    this.projectId = ((this.$route as Router).query["projectId"] ||
      project?.identifier ||
      this.projectId) as string;
    this.projectName = ((this.$route as Router).query["projectName"] ||
      project?.name ||
      this.projectName) 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.validThroughDateInput =
      this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput;

    try {
      const settings = await retrieveSettingsForActiveAccount();
      this.apiServer = settings.apiServer ?? "";
      this.activeDid = settings.activeDid ?? "";
      this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false;

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

        await accountsDB.open();
        const allAccounts = await accountsDB.accounts.toArray();
        allMyDids = allAccounts.map((acc) => acc.did);
        this.recipientName = didInfo(
          this.recipientDid,
          this.activeDid,
          allMyDids,
          allContacts,
        );
      }
      // these should be functions but something's wrong with the syntax in the <> conditional
      this.offeredToProject = !!this.projectId;
      this.offeredToRecipient = !this.offeredToProject && !!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 && !this.projectName) {
      // 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() {
    if (this.destinationPathAfter) {
      (this.$router as Router).push({ path: this.destinationPathAfter });
    } else {
      (this.$router as Router).back();
    }
  }

  cancelBack() {
    (this.$router as Router).back();
  }

  async confirm() {
    if (!this.activeDid) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "You must select an identifier before you can record a offer.",
        },
        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.descriptionOfItem && !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 offer...",
        title: "",
      },
      1000,
    );

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

  notifyUserOfProject() {
    if (!this.projectId) {
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "Error",
          text: "To assign to a project, you must open this page through a project.",
        },
        3000,
      );
    } else {
      // must be because offeredToRecipient 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 page from a contact.",
        },
        3000,
      );
    } else {
      // must be because offeredToProject is true
      this.$notify(
        {
          group: "alert",
          type: "warning",
          title: "Error",
          text: "You cannot assign both to a recipient and to a project.",
        },
        3000,
      );
    }
  }

  /**
   *
   * @param offererDid 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 recordOffer() {
    try {
      const recipientDid = this.offeredToRecipient
        ? this.recipientDid
        : undefined;
      const projectId = this.offeredToProject ? 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 editAndSubmitOffer(
          this.axios,
          this.apiServer,
          this.prevCredToEdit,
          this.activeDid,
          this.descriptionOfItem,
          parseFloat(this.amountInput),
          this.unitCode,
          this.descriptionOfCondition,
          this.validThroughDateInput,
          recipientDid,
          projectId,
        );
      } else {
        result = await createAndSubmitOffer(
          this.axios,
          this.apiServer,
          this.activeDid,
          this.descriptionOfItem,
          parseFloat(this.amountInput),
          this.unitCode,
          this.descriptionOfCondition,
          this.validThroughDateInput,
          recipientDid,
          projectId,
        );
      }

      if (result.type === "error" || this.isCreationError(result.response)) {
        const errorMessage = this.getCreationErrorMessage(result);
        console.error("Error with offer creation result:", result);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: errorMessage || "There was an error creating the offer.",
          },
          -1,
        );
      } else {
        this.$notify(
          {
            group: "alert",
            type: "success",
            title: "Success",
            text: `That offer 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 offer recordation caught:", error);
      const errorMessage =
        error.userMessage ||
        error.response?.data?.error?.message ||
        "There was an error recording the offer.";
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: errorMessage,
        },
        -1,
      );
    }
  }

  constructOfferParam() {
    const recipientDid = this.offeredToRecipient
      ? this.recipientDid
      : undefined;
    const projectId = this.offeredToProject ? this.projectId : undefined;
    const offerClaim = hydrateOffer(
      this.prevCredToEdit?.claim as OfferVerifiableCredential,
      this.activeDid,
      recipientDid,
      this.descriptionOfItem,
      parseFloat(this.amountInput),
      this.unitCode,
      this.descriptionOfCondition,
      projectId,
      this.validThroughDateInput,
      this.prevCredToEdit?.id as string,
    );
    const claimStr = JSON.stringify(offerClaim);
    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
  isCreationError(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
  getCreationErrorMessage(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>
