<template>
  <QuickNav selected="Projects"></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">
        <!-- Cancel -->
        <router-link
          :to="{ name: 'project' }"
          class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
          ><fa icon="chevron-left" class="fa-fw"></fa
        ></router-link>
        Edit Idea
      </h1>
    </div>

    <!-- Project Details -->
    <!-- Image - (see design model) Empty -->

    <div>
      {{ errorMessage }}
    </div>

    <input
      type="text"
      placeholder="Idea Name"
      class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
      v-model="fullClaim.name"
    />

    <div class="flex justify-center mt-4">
      <span v-if="imageUrl" class="flex justify-between">
        <a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">
          <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" />

    <input
      type="text"
      placeholder="Other Authorized Representative"
      class="mt-4 block w-full rounded border border-slate-400 px-3 py-2"
      v-model="agentDid"
    />
    <div class="mb-4">
      <p v-if="activeDid != projectIssuerDid && agentDid != projectIssuerDid">
        <span class="text-red-500">Beware!</span>
        If you save this, the original project owner will no longer be able to
        edit it.
        <button @click="agentDid = projectIssuerDid" class="text-blue-500">
          Click here to make the original owner an authorized representative.
        </button>
      </p>
    </div>

    <textarea
      placeholder="Description"
      class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
      rows="5"
      v-model="fullClaim.description"
      maxlength="5000"
    ></textarea>
    <div class="text-xs text-slate-500 italic -mt-3 mb-4">
      {{ fullClaim.description?.length }}/5000 max. characters
    </div>

    <input
      v-model="fullClaim.url"
      placeholder="Website"
      autocapitalize="none"
      class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
    />

    <div class="flex mb-4 columns-3 w-full">
      <input
        v-model="startDateInput"
        placeholder="Start Date"
        type="date"
        class="col-span-1 w-full rounded border border-slate-400 px-3 py-2"
      />
      <input
        :disabled="!startDateInput"
        v-model="startTimeInput"
        placeholder="Start Time"
        type="time"
        class="col-span-1 w-full rounded border border-slate-400 ml-2 px-3 py-2"
      />
      <span class="col-span-1 w-full flex justify-center">{{ zoneName }}</span>
    </div>

    <div class="flex items-center mb-4">
      <input
        type="checkbox"
        class="mr-2"
        v-model="includeLocation"
        @click="includeLocation = !includeLocation"
      />
      <label for="includeLocation">Include Location</label>
    </div>
    <div v-if="includeLocation" class="mb-4 aspect-video">
      <p class="text-sm mb-2 text-slate-500">
        For your security, choose a location nearby but not exactly at the
        place.
      </p>

      <l-map
        ref="map"
        v-model:zoom="zoom"
        :center="[0, 0]"
        class="!z-40 rounded-md"
        @click="
          (event) => {
            latitude = event.latlng.lat;
            longitude = event.latlng.lng;
          }
        "
      >
        <l-tile-layer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          layer-type="base"
          name="OpenStreetMap"
        />
        <l-marker
          v-if="latitude && longitude"
          :lat-lng="[latitude, longitude]"
          @click="confirmEraseLatLong()"
        />
      </l-map>
    </div>

    <div class="mt-8">
      <div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
        <button
          :disabled="isHiddenSave"
          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 mb-2"
          @click="onSaveProjectClick()"
        >
          <!-- SHOW if in idle state -->
          <span :class="{ hidden: isHiddenSave }">Save Project</span>

          <!-- SHOW if in saving state; DISABLE button while in saving state -->
          <span :class="{ hidden: isHiddenSpinner }">
            <!-- icon no worky? -->
            <i class="fa-solid fa-spinner fa-spin-pulse"></i>
            Saving...</span
          >
        </button>
        <button
          type="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="onCancelClick()"
        >
          Cancel
        </button>
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import "leaflet/dist/leaflet.css";
import { AxiosError } from "axios";
import * as didJwt from "did-jwt";
import { DateTime } from "luxon";
import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator";
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";

import QuickNav from "@/components/QuickNav.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import * as libsUtil from "@/libs/util";
import { useAppStore } from "@/store/app";
import { PlanVerifiableCredential } from "@/libs/endorserServer";
import ImageMethodDialog from "@/components/ImageMethodDialog.vue";

@Component({
  components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav },
})
export default class NewEditProjectView extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;
  errNote(message) {
    this.$notify(
      { group: "alert", type: "danger", title: "Error", text: message },
      5000,
    );
  }

  activeDid = "";
  agentDid = "";
  apiServer = "";
  errorMessage = "";
  fullClaim: PlanVerifiableCredential = {
    "@context": "https://schema.org",
    "@type": "PlanAction",
    name: "",
    description: "",
  }; // this default is only to avoid errors before plan is loaded
  imageUrl = "";
  includeLocation = false;
  isHiddenSave = false;
  isHiddenSpinner = true;
  lastClaimJwtId = "";
  latitude = 0;
  longitude = 0;
  numAccounts = 0;
  projectId = localStorage.getItem("projectId") || "";
  projectIssuerDid = "";
  startDateInput?: string;
  startTimeInput?: string;
  zoneName = DateTime.local().zoneName;
  zoom = 2;

  libsUtil = libsUtil;

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

    if (!identity) {
      throw new Error(
        "Attempted to load project records with no identifier available.",
      );
    }
    return identity;
  }

  public async getHeaders(identity: IIdentifier) {
    const token = await accessToken(identity);
    const headers = {
      "Content-Type": "application/json",
      Authorization: "Bearer " + token,
    };
    return headers;
  }

  async mounted() {
    await accountsDB.open();
    this.numAccounts = await accountsDB.accounts.count();

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

    if (this.projectId) {
      if (this.numAccounts === 0) {
        this.errNote("There was a problem loading your account info.");
      } else {
        const identity = await this.getIdentity(this.activeDid);
        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.",
          );
        }
        this.loadProject(identity);
      }
    }
  }

  async loadProject(identity: IIdentifier) {
    const url =
      this.apiServer +
      "/api/claim/byHandle/" +
      encodeURIComponent(this.projectId);
    const token = await accessToken(identity);
    const headers = {
      "Content-Type": "application/json",
      Authorization: "Bearer " + token,
    };

    try {
      const resp = await this.axios.get(url, { headers });
      if (resp.status === 200) {
        this.projectIssuerDid = resp.data.issuer;
        this.fullClaim = resp.data.claim;
        this.imageUrl = resp.data.claim.image || "";
        this.lastClaimJwtId = resp.data.id;
        if (this.fullClaim?.location) {
          this.includeLocation = true;
          this.latitude = this.fullClaim.location.geo.latitude;
          this.longitude = this.fullClaim.location.geo.longitude;
        }
        if (this.fullClaim?.agent?.identifier) {
          this.agentDid = this.fullClaim.agent.identifier;
        }
        if (this.fullClaim.startTime) {
          const localDateTime = DateTime.fromISO(
            this.fullClaim.startTime as string,
          ).toLocal();
          this.startDateInput = localDateTime.toFormat("yyyy-MM-dd");
          this.startTimeInput = localDateTime.toFormat("HH:mm");
        }
      }
    } catch (error) {
      console.error("Got error retrieving that project", error);
      this.errNote("There was an error retrieving that project.");
    }
  }

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

  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 identity = await libsUtil.getIdentity(this.activeDid);
      const token = await accessToken(identity);
      const response = await this.axios.delete(
        DEFAULT_IMAGE_API_SERVER +
          "/image/" +
          encodeURIComponent(this.imageUrl),
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      );
      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,
        );
        return;
      }

      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);

        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,
        );
      }
    }
  }

  private async saveProject(identity: IIdentifier) {
    // Make a claim
    const vcClaim: PlanVerifiableCredential = this.fullClaim;
    if (this.projectId) {
      vcClaim.lastClaimId = this.lastClaimJwtId;
    }
    if (this.agentDid) {
      vcClaim.agent = {
        identifier: this.agentDid,
      };
    } else {
      delete vcClaim.agent;
    }
    if (this.imageUrl) {
      vcClaim.image = this.imageUrl;
    } else {
      delete vcClaim.image;
    }
    if (this.includeLocation) {
      vcClaim.location = {
        geo: {
          "@type": "GeoCoordinates",
          latitude: this.latitude,
          longitude: this.longitude,
        },
      };
    } else {
      delete vcClaim.location;
    }
    if (this.startDateInput) {
      try {
        const startTimeFull = this.startTimeInput || "00:00:00";
        const fullTimeString = this.startDateInput + " " + startTimeFull;
        // throw an error on an invalid date or time string
        vcClaim.startTime = new Date(fullTimeString).toISOString(); // ensure timezone is part of it
      } catch {
        // it's not a valid date so erase it and tell the user
        delete vcClaim.startTime;
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "The date was invalid so it was not set.",
          },
          5000,
        );
      }
    } else {
      delete vcClaim.startTime;
    }
    // Make a payload for the claim
    const vcPayload = {
      vc: {
        "@context": ["https://www.w3.org/2018/credentials/v1"],
        type: ["VerifiableCredential"],
        credentialSubject: vcClaim,
      },
    };
    // create a signature using private key of identity
    if (identity.keys[0].privateKeyHex != null) {
      const privateKeyHex: string = identity.keys[0].privateKeyHex;
      const signer = await SimpleSigner(privateKeyHex);
      const alg = undefined;
      // create a JWT for the request
      const vcJwt: string = await didJwt.createJWT(vcPayload, {
        alg: alg,
        issuer: identity.did,
        signer: signer,
      });

      // Make the xhr request payload

      const payload = JSON.stringify({ jwtEncoded: vcJwt });
      const url = this.apiServer + "/api/v2/claim";
      const token = await accessToken(identity);
      const headers = {
        "Content-Type": "application/json",
        Authorization: "Bearer " + token,
      };

      try {
        const resp = await this.axios.post(url, payload, { headers });
        if (resp.data?.success?.handleId) {
          this.errorMessage = "";

          useAppStore()
            .setProjectId(resp.data.success.handleId)
            .then(() => {
              this.$router.push({ name: "project" });
            });
        } else {
          console.error(
            "Got unexpected 'data' inside response from server",
            resp,
          );
          this.$notify(
            {
              group: "alert",
              type: "danger",
              title: "Error Saving Idea",
              text: "Server did not save the idea. Try again.",
            },
            -1,
          );
        }
      } catch (error) {
        let userMessage = "There was an error saving the project.";
        const serverError = error as AxiosError<{
          error?: { message?: string };
        }>;
        if (serverError) {
          console.error("Got error from server", serverError);
          if (Object.prototype.hasOwnProperty.call(serverError, "message")) {
            userMessage =
              (serverError.response?.data?.error?.message as string) ||
              userMessage;
            this.$notify(
              {
                group: "alert",
                type: "danger",
                title: "User Message",
                text: userMessage,
              },
              -1,
            );
          } else {
            this.$notify(
              {
                group: "alert",
                type: "danger",
                title: "Server Message",
                text: JSON.stringify(serverError.toJSON()),
              },
              -1,
            );
          }
        } else {
          console.error(
            "Here's the full error trying to save the claim:",
            error,
          );
          this.$notify(
            {
              group: "alert",
              type: "danger",
              title: "Claim Error",
              text: error as string,
            },
            -1,
          );
        }
        // Now set that error for the user to see.
        this.errorMessage = userMessage;
      }
    }
  }

  public async onSaveProjectClick() {
    this.isHiddenSave = true;
    this.isHiddenSpinner = false;

    if (this.numAccounts === 0) {
      console.error("Error: there is no account.");
    } else {
      const identity = await this.getIdentity(this.activeDid);
      this.saveProject(identity);
    }
  }

  confirmEraseLatLong() {
    this.$notify(
      {
        group: "modal",
        type: "confirm",
        title: "Erase Marker",
        text: "Are you sure you don't want to mark a location? This will erase the current location.",
        onYes: async () => {
          this.eraseLatLong();
        },
      },
      -1,
    );
  }

  public eraseLatLong() {
    this.latitude = 0;
    this.longitude = 0;
    this.includeLocation = false;
  }

  public onCancelClick() {
    this.$router.back();
  }
}
</script>
